Wednesday, March 31, 2010

Chat Server (Working for the most part)

/** ChatServerSubject object
-- implementation of the Subject class, containing the user list, an observer list, and the message buffer
-- This class will notify each child thread of the server whenever a change is made on either of these 2 objects.
Authors:  Jon Fidler & Daniel Tanner
March 31st, 2010
CMPT 352 - Networking
*/
import java.util.*;
import java.net.*;
import java.awt.*;

public class ChatServerSubject extends Observable
{
    //members
    private Vector<String> userList;
    private Vector<MessageObject> messageBuffer;
    private String removedUser;
   
    //flags
    /* whatHappened flag = int representing the state of the object.
    0 = nothing's changed
    1 = user added
    2 = user removed
    3 = message added
    */
    private int whatHappened;
   
    //constructors
    public ChatServerSubject()
    {
        userList = new Vector<String>();
        messageBuffer = new Vector<MessageObject>();
        removedUser = "NOBODY";
        whatHappened = 0;
    }
   
    /** userList changing functions
    */
    //add a user
    public void addUser(String userName)
    {
        userList.add(userName);
       
        //set flags
        whatHappened = 1;
        setChanged();
        notifyObservers();
    }
   
    //remove a user from the list
    public void removeUser(String userName)
    {
        removedUser = userName;
        userList.remove(userName);
       
        //set flags
        whatHappened = 2;
        setChanged();
        notifyObservers();
    }
   
    //check if a user is on the list or not
    public boolean userExists(String userName)
    {
        return userList.contains(userName);
    }
   
    //get user list
    public Vector<String> getUserList(){return userList;}
   
    //get the name of the user who just disconnected
    public String getDCedUser() {return removedUser;}
   
    //returns the last user on the list (most recently added user
    public String getNewUser() {return userList.lastElement();}
   
    /**Message Buffer functions
    */
    //get message function
    public MessageObject getMessage() {return messageBuffer.firstElement();}
   
    //add message function
    public void addMessage(MessageObject message)
    {
        messageBuffer.add(message);
        whatHappened = 3;
        setChanged();
        notifyObservers();
    }
   
    //remove a message
    public void removeMessage(MessageObject message)
    {
        messageBuffer.remove(message);
    }
   
    /* Flag checking functions*/
    public boolean userAdded()
    {
        if(whatHappened == 1)
            return true;
        return false;
    }
   
    public boolean userRemoved()
    {
        if(whatHappened == 2)
            return true;
        return false;
    }
   
    public boolean messageAdded()
    {
        if(whatHappened == 3)
            return true;
        return false;
    }
}

/**
Daniel Tanner & Jon Fidler
CMPT 352 - Computer Networking
March 29th, 2010
Handler class for threaded Chat Server
 */

import java.net.*;
import java.lang.Object.*;
import java.util.*;
import java.text.*;
import javax.activation.*;
import java.io.*;
import java.util.regex.Pattern;

public class Handler implements Observer
{       
    //Create variables
    private BufferedReader fromClient = null;
    private BufferedOutputStream toClient = null;
    private ChatServerSubject buffer = null;
    private String myUserName = null;
    private String command = null;
    private boolean connectedProperly = false;
    private static Pattern alphaNum = Pattern.compile("^[A-Za-z0-9\\*]+$");

    //This method invoked as a separate thread
    public void process(Socket client, ChatServerSubject buffer) throws IOException
    {   
        //set up the method
        this.buffer = buffer;
       
        //try block for opening streams and getting hostname
        try
        {
            //open Streams
            fromClient = new BufferedReader(new InputStreamReader(client.getInputStream()));
            toClient = new BufferedOutputStream(client.getOutputStream());       
       
            //initial connection state
            command = fromClient.readLine();
            command = command.toLowerCase();
            if(!command.equalsIgnoreCase("connect"))
            {
                error("Bad Request.  Please input a proper command.", 2);
                return;
            }
           
            //get username, check against userList
            myUserName = fromClient.readLine();
            myUserName = myUserName.toUpperCase();
            if(buffer.userExists(myUserName))
            {
                error("Duplicate Handle.  Please try another name.", 1);
                return;
            }
           
            //check that username is within bounds
            if(!(myUserName.length() <= 16 && alphaNum.matcher(myUserName).matches()))
            {
                error("Invalid handle.  Handle must be an alphanumeric string with size less than or euqal to 16.", 3);
                return;
            }
            connectedProperly = true;
           
            //add username to userList
            buffer.addUser(myUserName);
            buffer.addObserver(this);
           
            //print userList to client
            printUserList();
           
            //listen for commands
            do
            {
                command = fromClient.readLine();
                command = command.toUpperCase();
               
                //getHandles command
                if(command.equalsIgnoreCase("gethandles"))
                    printUserList();
               
                //Post command
                else if(command.equalsIgnoreCase("post"))
                    constructMessage();
               
                //invalid command
                else if(!command.equalsIgnoreCase("disconnect"))
                    error("Invalid request.  Please input a proper command.", 2);
            }
            while(!command.equalsIgnoreCase("disconnect"));
        }
       
        catch(IOException ioe)
        {
            System.err.println(ioe);
            return;
        }
       
        finally
                {
                    //disconnection state
                    if(connectedProperly)
                    {
                            buffer.deleteObserver(this);
                            buffer.removeUser(myUserName);
                    }
                    // close streams and socket
                    if (fromClient != null)
                        fromClient.close();
                    if (toClient != null)
                        toClient.close();
                    if (client != null)
                        client.close();
                }
    }
   
    //broadcast method - this method is the basic broadcasting method
    //it assumes that we're sending a public message
    private void broadcast(MessageObject message) throws IOException
    {
        System.out.println("You've reached the broadcast method!");
        toClient.write("posted\n".getBytes());
        toClient.write((message.getFromUser() + "\n").getBytes());
        toClient.write("public\n".getBytes());
        toClient.write((message.getMessage() + "\n").getBytes());
        toClient.flush();
    }
   
    //private message broadcast method
    private void privateMessage(MessageObject message) throws IOException
    {
        System.out.println("privateMessage method!");
        toClient.write("posted\n".getBytes());
        toClient.write((message.getFromUser() + "\n").getBytes());
        toClient.write("private\n".getBytes());
        toClient.write((message.getMessage() + "/n").getBytes());
        toClient.flush();
    }
   
    //connected/disconnected message
    private void userConnected(String userName, boolean connected) throws IOException
    {
        if(connected)
            toClient.write("connected\n".getBytes());
        else
            toClient.write("disconnected\n".getBytes());
        toClient.write((userName + "\n").getBytes());
        toClient.flush();
    }
   
    //Error message
    private void error(String errorMessage, int errorNum) throws IOException
    {
        toClient.write("error\n".getBytes());
        toClient.write((errorNum + "\n").getBytes());
        toClient.write((errorMessage + "\n").getBytes());
        toClient.flush();
    }
   
    //Print user list function
    private void printUserList() throws IOException
    {
        Vector<String> currentUserList = new Vector<String>(buffer.getUserList());
        toClient.write("handles\n".getBytes());
        toClient.write((currentUserList.size() + "\n").getBytes());
        for(int index = 0; index < currentUserList.size(); index++)
            toClient.write((currentUserList.get(index) + "\n").getBytes());
        toClient.flush();
    }
   
    //CreateMessage method - goes to this state after receiving the post command from the user
    private void constructMessage() throws IOException
    {
        MessageObject newMessage = new MessageObject();
        newMessage.setFromUser(myUserName);
        String temp;
       
        //read the toUser
        temp = fromClient.readLine();
        temp = temp.toUpperCase();
        if(!(alphaNum.matcher(temp).matches() && temp.length() <= 16))
        {
            error("Invalid username entered.  Try again!.", 3);
            return;
        }
        if(!buffer.userExists(temp) && !temp.equalsIgnoreCase("*"))
        {
            error("User not found.", 4);
            return;
        }
        newMessage.setToUser(temp);
           
           
        //read the message and check to make sure it's within bounds
        temp = fromClient.readLine();
        if(temp.length() > 1024)
        {
            error("Message too Long", 5);
            return;
        }
        newMessage.setMessage(temp);
        buffer.addMessage(newMessage);
    }
   
    //update function - this is the observer functionanility implemented
    //it will check the flags when the subject has changed, and act appropriately
    public void update(Observable o, Object arg)
    {
        System.out.println("your observers are being updated!");
        try
        {
            //check if a user was added or removed
            if(buffer.userAdded())
                userConnected(buffer.getNewUser(), buffer.userAdded());
            else if(buffer.userRemoved())
                userConnected(buffer.getDCedUser(), buffer.userAdded());
               
           
            //here's the routine when we get a new message
            else if(buffer.messageAdded())
            {
                MessageObject newMessage = buffer.getMessage();
                //check if it's a private message
                if(newMessage.getToUser().equals(myUserName))
                    privateMessage(newMessage);
               
                //now check if it's a broadcast and not from me
                else if(newMessage.getToUser().equals("*") && !newMessage.getFromUser().equals(myUserName))
                    broadcast(newMessage);
               
                //now decrement and check if the counter is at 0, remove from the list if it is
                newMessage.viewed();
                if(newMessage.getCounter() == 0)
                    buffer.removeMessage(newMessage);
            }
        }
        catch(IOException ioe)
        {
            System.err.println(ioe);
            command = "disconnect";
            return;
        }
           
    }
}

/** Connection Class for threaded Chat Server
Daniel Tanner & John Fidler
CMPT 352 - Computer Networking
March 29th, 2010
 */

import java.net.*;
import java.io.*;
import java.text.*;
import java.util.*;

public class Connection implements Runnable
{
    //create variables
    private Socket    client;
    private ChatServerSubject buffer;
    private Handler handler = new Handler();

    public Connection(Socket client, ChatServerSubject buffer)
    {
        this.client = client;
        this.buffer = buffer;
    }

    //Separate thread
    public void run()
    {
        try
        {
            handler.process(client, buffer);
        }
       
        catch (java.io.IOException ioe)
        {
            System.err.println(ioe);
        }
    }
}

/** Chat Server
Authors: Daniel J. Tanner and Jon Fidler
March 29th, 2010
CMPT 352 - Networking

This program implements the Spring 2010 Networking class' Chat Protocol
**/

import java.io.*;
import java.net.*;
import java.util.*;


public class ChatServer
{
    //constants & globals
    public static final int PORT = 2222;
    private static ChatServerSubject buffer = null;
   
   
    public static void main(String[] args) throws java.io.IOException
    {
        //load everything up
        buffer = new ChatServerSubject();
       
        //Create socket
        ServerSocket sock = null;
        try
        {
            //establish socket
            sock = new ServerSocket(PORT);
           
            while(true)
            {
                Socket client = sock.accept();
                //debug testing
                InetAddress ipAddr = client.getInetAddress();
                System.out.print(ipAddr.getHostAddress() + " : ");
                System.out.println(client.getPort() + " connected.");
                Thread worker = new Thread(new Connection(client, buffer));
               
                worker.start();
            }
        }
        //catch any errors opening sockets
        catch(IOException ioe)
        {
            System.err.println(ioe);
            System.exit(0);
        }
       
        finally
        {
            //close streams
            if(sock != null)
                sock.close();
        }
    }
   
    //getUserList function - returns a pointer to the user List
    public static Vector<String> getUserList() {return buffer.getUserList();}
}

/**
BufferKeyEntry class

Contain fields to put into the key of the message buffer
The value will be the unformatted message

Members of the BufferEntry are as follows:

-Who sent the message (so we don't echo it back)

-A non-static counter which will tell us when all the threads have sent the message
so we know when to remove the message from our buffer.

-Who the message is to (* for broadcasts)
*/

/**
Authors: Daniel Tanner & Jon Fidler
March 29th, 2010
CMPT 352 - Networking
*/

import java.net.*;
import java.io.*;
import java.text.*;
import java.util.*;

public class MessageObject
{
    //create variables
    private String toUser;
    private String fromUser;
    private int counter;
    private String message;
   
    //constructor
    MessageObject()
    {
        counter = ChatServer.getUserList().size();
        toUser = null;
        fromUser = null;
        message = null;
    }
   
    //copy constructor
    MessageObject(MessageObject copy)
    {
        this.toUser = copy.toUser;
        this.fromUser = copy.fromUser;
        this.counter = copy.counter;
        this.message = copy.message;
    }
       
    //Constructor with arguments
    MessageObject(String toUser, String fromUser, String message)
    {
        counter = ChatServer.getUserList().size();
        this.toUser = toUser;
        this.fromUser = fromUser;
        this.message = message;
    }
   
    //get functions
    int getCounter() {return counter;}
    String getToUser() {return toUser;}
    String getFromUser() {return fromUser;}
    String getMessage() {return message;}
   
    //set functions
    void setToUser(String toUser) {this.toUser = toUser;}
    void setFromUser(String fromUser) {this.fromUser = fromUser;}
    void setMessage(String message) {this.message = message;}
   
    //viewed function - This function will decrement the counter
    void viewed() {counter--;}
   
    //equals function
    boolean equals(MessageObject rhs)
    {
        if(toUser.equals(rhs.toUser) && fromUser.equals(rhs.fromUser) && counter == rhs.counter && message.equals(rhs.message))
            return true;
        else
            return false;
    }
}
       
/**
Daniel Tanner & Jon Fidler
CMPT 352 - Computer Networking
March 29th, 2010
Handler class for threaded Chat Server
 */

import java.net.*;
import java.lang.Object.*;
import java.util.*;
import java.text.*;
import javax.activation.*;
import java.io.*;
import java.util.regex.Pattern;

public class Handler implements Observer
{       
    //Create variables
    private BufferedReader fromClient = null;
    private BufferedOutputStream toClient = null;
    private ChatServerSubject buffer = null;
    private String myUserName = null;
    private String command = null;
    private boolean connectedProperly = false;
    private static Pattern alphaNum = Pattern.compile("^[A-Za-z0-9\\*]+$");

    //This method invoked as a separate thread
    public void process(Socket client, ChatServerSubject buffer) throws IOException
    {   
        //set up the method
        this.buffer = buffer;
       
        //try block for opening streams and getting hostname
        try
        {
            //open Streams
            fromClient = new BufferedReader(new InputStreamReader(client.getInputStream()));
            toClient = new BufferedOutputStream(client.getOutputStream());       
       
            //initial connection state
            command = fromClient.readLine();
            command = command.toLowerCase();
            if(!command.equalsIgnoreCase("connect"))
            {
                error("Bad Request.  Please input a proper command.", 2);
                return;
            }
           
            //get username, check against userList
            myUserName = fromClient.readLine();
            myUserName = myUserName.toUpperCase();
            if(buffer.userExists(myUserName))
            {
                error("Duplicate Handle.  Please try another name.", 1);
                return;
            }
           
            //check that username is within bounds
            if(!(myUserName.length() <= 16 && alphaNum.matcher(myUserName).matches()))
            {
                error("Invalid handle.  Handle must be an alphanumeric string with size less than or euqal to 16.", 3);
                return;
            }
            connectedProperly = true;
           
            //add username to userList
            buffer.addUser(myUserName);
            buffer.addObserver(this);
           
            //print userList to client
            printUserList();
           
            //listen for commands
            do
            {
                command = fromClient.readLine();
                command = command.toUpperCase();
               
                //getHandles command
                if(command.equalsIgnoreCase("gethandles"))
                    printUserList();
               
                //Post command
                else if(command.equalsIgnoreCase("post"))
                    constructMessage();
               
                //invalid command
                else if(!command.equalsIgnoreCase("disconnect"))
                    error("Invalid request.  Please input a proper command.", 2);
            }
            while(!command.equalsIgnoreCase("disconnect"));
        }
       
        catch(IOException ioe)
        {
            System.err.println(ioe);
            return;
        }
       
        finally
                {
                    //disconnection state
                    if(connectedProperly)
                    {
                            buffer.deleteObserver(this);
                            buffer.removeUser(myUserName);
                    }
                    // close streams and socket
                    if (fromClient != null)
                        fromClient.close();
                    if (toClient != null)
                        toClient.close();
                    if (client != null)
                        client.close();
                }
    }
   
    //broadcast method - this method is the basic broadcasting method
    //it assumes that we're sending a public message
    private void broadcast(MessageObject message) throws IOException
    {
        System.out.println("You've reached the broadcast method!");
        toClient.write("posted\n".getBytes());
        toClient.write((message.getFromUser() + "\n").getBytes());
        toClient.write("public\n".getBytes());
        toClient.write((message.getMessage() + "\n").getBytes());
        toClient.flush();
    }
   
    //private message broadcast method
    private void privateMessage(MessageObject message) throws IOException
    {
        System.out.println("privateMessage method!");
        toClient.write("posted\n".getBytes());
        toClient.write((message.getFromUser() + "\n").getBytes());
        toClient.write("private\n".getBytes());
        toClient.write((message.getMessage() + "/n").getBytes());
        toClient.flush();
    }
   
    //connected/disconnected message
    private void userConnected(String userName, boolean connected) throws IOException
    {
        if(connected)
            toClient.write("connected\n".getBytes());
        else
            toClient.write("disconnected\n".getBytes());
        toClient.write((userName + "\n").getBytes());
        toClient.flush();
    }
   
    //Error message
    private void error(String errorMessage, int errorNum) throws IOException
    {
        toClient.write("error\n".getBytes());
        toClient.write((errorNum + "\n").getBytes());
        toClient.write((errorMessage + "\n").getBytes());
        toClient.flush();
    }
   
    //Print user list function
    private void printUserList() throws IOException
    {
        Vector<String> currentUserList = new Vector<String>(buffer.getUserList());
        toClient.write("handles\n".getBytes());
        toClient.write((currentUserList.size() + "\n").getBytes());
        for(int index = 0; index < currentUserList.size(); index++)
            toClient.write((currentUserList.get(index) + "\n").getBytes());
        toClient.flush();
    }
   
    //CreateMessage method - goes to this state after receiving the post command from the user
    private void constructMessage() throws IOException
    {
        MessageObject newMessage = new MessageObject();
        newMessage.setFromUser(myUserName);
        String temp;
       
        //read the toUser
        temp = fromClient.readLine();
        temp = temp.toUpperCase();
        if(!(alphaNum.matcher(temp).matches() && temp.length() <= 16))
        {
            error("Invalid username entered.  Try again!.", 3);
            return;
        }
        if(!buffer.userExists(temp) && !temp.equalsIgnoreCase("*"))
        {
            error("User not found.", 4);
            return;
        }
        newMessage.setToUser(temp);
           
           
        //read the message and check to make sure it's within bounds
        temp = fromClient.readLine();
        if(temp.length() > 1024)
        {
            error("Message too Long", 5);
            return;
        }
        newMessage.setMessage(temp);
        buffer.addMessage(newMessage);
    }
   
    //update function - this is the observer functionanility implemented
    //it will check the flags when the subject has changed, and act appropriately
    public void update(Observable o, Object arg)
    {
        System.out.println("your observers are being updated!");
        try
        {
            //check if a user was added or removed
            if(buffer.userAdded())
                userConnected(buffer.getNewUser(), buffer.userAdded());
            else if(buffer.userRemoved())
                userConnected(buffer.getDCedUser(), buffer.userAdded());
               
           
            //here's the routine when we get a new message
            else if(buffer.messageAdded())
            {
                MessageObject newMessage = buffer.getMessage();
                //check if it's a private message
                if(newMessage.getToUser().equals(myUserName))
                    privateMessage(newMessage);
               
                //now check if it's a broadcast and not from me
                else if(newMessage.getToUser().equals("*") && !newMessage.getFromUser().equals(myUserName))
                    broadcast(newMessage);
               
                //now decrement and check if the counter is at 0, remove from the list if it is
                newMessage.viewed();
                if(newMessage.getCounter() == 0)
                    buffer.removeMessage(newMessage);
            }
        }
        catch(IOException ioe)
        {
            System.err.println(ioe);
            command = "disconnect";
            return;
        }
           
    }
}