::::::::::::::
ChatApplet.java
::::::::::::::
import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class ChatApplet extends Applet {

  String name;
  Client c;
  Server s;
  Connection conn;

  final Color standardButtonColor = Color.red;
  final Color selectedButtonColor = Color.green;

  Hashtable buttons = new Hashtable();

  String confidential = null;
 TextArea transcript = new TextArea("Transcript:\n", 20, 70, 
    TextArea.SCROLLBARS_VERTICAL_ONLY);

 TextField msg = new TextField(50);

 Button quit = new Button("Quit");

 Panel panel = new Panel();
 Panel whoPanel = new Panel();

 PersonButtonListener pbl = new PersonButtonListener();
 class ChatClientImpl extends UnicastRemoteObject
    implements Client {

    public ChatClientImpl () throws RemoteException {}
    
    public void whoChanged() throws java.rmi.RemoteException {
        updateWho();
        }
    public void speak(String who, String msg) throws java.rmi.RemoteException
        {
        String o = who + ": " + msg + "\n";
        if (transcript != null) transcript.append(o);
        else System.out.print(o);
        }
    }

 class PersonButtonListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {    
        String s = e.getActionCommand();
        if (confidential != null && confidential.equals(s)) {
            Button b = (Button) buttons.get(confidential);
            if (b != null) 
                b.setBackground(standardButtonColor);
            confidential = null;
            }
        else {
            Button b;
            if (confidential != null) {
              b = (Button) buttons.get(confidential);
              if (b != null) b.setBackground(standardButtonColor);
              }
            confidential = s;
            b = (Button) buttons.get(confidential);
            if (b != null) 
                b.setBackground(selectedButtonColor);
            }
        }
    }
 public ChatApplet(String nm) {
      name = nm;
      try {
       c = new ChatClientImpl();
       s= (Server)Naming.lookup("//aufait.cs.umd.edu/ChatServer");
           conn = s.logon(name,c);
       synchronized(this) {
           notify();
           }
       }
      catch (Exception e) {
        System.err.println("Error starting applet:" + e);
        c = null;
        s = null;
        conn = null;
        }
      }

 public void init() {
    BorderLayout layout = new BorderLayout();
    setLayout(layout);
    add("Center",transcript);
    transcript.setEditable(false);
    panel.setLayout(new FlowLayout());
    add("South",panel);
    whoPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
    add("North",whoPanel);
    // add("South",msg);
    panel.add(new Label(name+":"));
    panel.add(msg);
    msg.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {    
            try {
                if (confidential != null) 
                    conn.say(confidential,e.getActionCommand());
                else conn.say(e.getActionCommand());
              }
            catch (Exception err) { System.out.println("Error while saying. error: " + err); }
            msg.setText("");
            }
        });
    // add("East",quit);
    panel.add(quit);
    quit.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {    
            doQuit();
            }
        });
    }

 synchronized void doQuit() { 
   try {
     conn.logoff();
     System.exit(0);
    }
   catch (Exception err) { System.out.println("Error while trying to quit. " + err); };
   }

 synchronized void updateWho() {
    while (conn == null) {
        try {
          wait();
         }
        catch (InterruptedException e) {
            System.out.println("Interrupted?? " + e);
            }
        }
    whoPanel.removeAll();
    try {
      String[] w = conn.who();
      for(int i = 0; i < w.length; i++) {
        if (!w[i].equals(name)) {
            Button b = (Button) buttons.get(w[i]);
            if (b == null) {
                b = new Button(w[i]);
                b.setBackground(standardButtonColor);
                b.addActionListener(pbl);
                buttons.put(w[i],b);
                };
            whoPanel.add(b);
            }
      }
     }
   catch (Exception err) { 
        System.out.println("Error while updating who. " + err); 
        err.printStackTrace(System.out);
        };
    whoPanel.setSize(whoPanel.getPreferredSize());
    whoPanel.validate(); 
    validate();
    }

 public static void main(String[] args) throws Exception {
      
      final ChatApplet applet = new ChatApplet(args[0]); 
      if (applet.conn == null) return;
      Frame aFrame = new Frame("Chat Client for " + args[0]);
      aFrame.addWindowListener(
       new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
         applet.doQuit();
         }
       });
      aFrame.add(applet, BorderLayout.CENTER);
      applet.setVisible(true);
      applet.validate();
      
      applet.validate();
      aFrame.validate();
      applet.init();
      aFrame.setVisible(true);
      aFrame.pack();
      applet.start();
      // aFrame.setSize(700,400);
 }
}
::::::::::::::
Client.java
::::::::::::::
public interface Client extends java.rmi.Remote {
    public void speak(String who, String msg) throws java.rmi.RemoteException;
    public void whoChanged() throws java.rmi.RemoteException;
}
::::::::::::::
ClientImpl.java
::::::::::::::
import java.util.*;
import java.io.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class ClientImpl extends UnicastRemoteObject
    implements Client {

    public ClientImpl () throws RemoteException {}
    
    public void whoChanged() throws java.rmi.RemoteException {}

    public void speak(String who, String msg) throws java.rmi.RemoteException
        {
        System.out.println(who + ": " + msg);
        }
    public static void main(String[] args) throws Exception {
        Client c = new ClientImpl();
        Server s= (Server)Naming.lookup("//aufait.cs.umd.edu/ChatServer");
        Connection conn = s.logon(args[0],c);
        if (conn == null) return;
        BufferedReader in =
            new BufferedReader(new InputStreamReader(System.in));
        String msg = null;
        inputLoop: while ((msg = in.readLine()) != null) {
            directedMsg: if (msg.charAt(0) == '(') {
                int i = msg.indexOf(')'); 
                if (i == -1) break directedMsg;
                String toWho = msg.substring(1,i-1);
                String contents 
                  = msg.substring(i+1,msg.length()-1).trim();
                conn.say(toWho,contents);
                continue inputLoop;
                }
            conn.say(msg);
        }
        conn.logoff();
        }
}
::::::::::::::
Connection.java
::::::::::::::
import java.rmi.*;
import java.util.*;


public interface Connection extends Remote, Cloneable {
    public void say(String msg) throws RemoteException;
    public void say(String who, String msg) throws RemoteException;
    public String [] who() throws RemoteException;
    public void logoff() throws RemoteException;
}
::::::::::::::
ConnectionImpl.java
::::::::::::::
import java.util.*;
import java.rmi.*;
import java.rmi.server.*;
import java.net.URL;
import java.rmi.Remote;

public class ConnectionImpl extends UnicastRemoteObject implements Cloneable, Connection {
    private String name;
    private Hashtable connections;
    private Client client;
    private ServerImpl server;

    public ConnectionImpl(String nm, Client c, ServerImpl s, Hashtable conn) throws RemoteException {
        name = nm;
        client = c;
        connections = conn;
        server = s;
        System.out.println(name + " logged on");
        };    


    public void say(String msg) throws RemoteException {
        Hashtable sendTo;
        System.out.println(name + ": " + msg);
        synchronized (connections) {
         sendTo = (Hashtable)connections.clone();
         }
        for (Enumeration e = sendTo.elements(); e.hasMoreElements(); ) 
         ((Client)e.nextElement()).speak(name,msg);
        }

    public void say(String who, String msg) throws RemoteException {
        Client c;
        synchronized (connections) {
            c = (Client) connections.get(who);
            };
         msg = "[ " + msg + " ]";
         c.speak(name, msg);
         client.speak(name, msg);
        }
        
    public String[] who() throws RemoteException {
        int sz;
        String[] w;
        synchronized (connections) {
          sz = connections.size();
         w = new String[sz];
         int i = 0;
         for(Enumeration e = connections.keys(); e.hasMoreElements(); i++ )
            w[i] = (String)e.nextElement();
         }
        return w;
        }

    public void logoff() throws RemoteException {
        synchronized (connections) {
         connections.remove(name);
         }
        client.speak("Server","good bye");
        server.notifyWhoChanged();
        System.out.println(name + " logged off");
        }
        
        
}
::::::::::::::
Server.java
::::::::::::::
public interface Server extends java.rmi.Remote {
    public Connection logon(String name, Client c) throws java.rmi.RemoteException;
}
::::::::::::::
ServerImpl.java
::::::::::::::
import java.util.*;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class ServerImpl
    extends UnicastRemoteObject
    implements Server
{
    Hashtable connections = new Hashtable();

    public ServerImpl() throws RemoteException {
        }

    public void notifyWhoChanged() {
        // OK. We had to fix a little problem here.
        // We need to tell all connections that a new user has logged on
        // However, the user logging on might not be fully establish,
        // and be unable to accept the message that whoChanged until
        // such time as the log-on is complete.
        // We can fix this either Server side or Client Side.
        // The Server side fix is to run this in a seperate thread,
        // so that if it blocks, we won't delay the return from logon
        // (which could create deadlock)
        (new Thread() {
         public void run() {
          Hashtable sendTo;
          synchronized (connections) {
           sendTo = (Hashtable)connections.clone();
          }
          for (Enumeration e = sendTo.elements(); e.hasMoreElements(); ) {
           try {
              ((Client)e.nextElement()).whoChanged();
             }
           catch(Exception err) {
                System.out.println(err);
                }
           }
          }
        }).start();
        }

    public Connection logon(String name, Client c) throws java.rmi.RemoteException {
        Connection conn;
        synchronized (connections) {
         if (connections.get(name) != null) {
            c.speak("Server","Sorry, but that name is already in use");
            return null;
            }
         conn = new ConnectionImpl(name, c, this, connections);
         connections.put(name,c);
         }
        c.speak("Server","Welcome");
        notifyWhoChanged();
        return conn;
        }
        
    public static void main(String args[])
    {
        // Create and install a security manager
        System.setSecurityManager(new RMISecurityManager());

        try {
            ServerImpl obj = new ServerImpl();
            Naming.rebind("//aufait.cs.umd.edu/ChatServer", obj);
            System.out.println("ChatServer bound in registry");
        } catch (Exception e) {
            System.out.println("ChatServerImpl err: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
