HW6 Solutions Page

Return to homework page.

//Server.java - server in chat document program cs151 hw6
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.net.*;
import java.util.*;

/**
   This is the Server in our client server chat document program.
   As soon as rmiregistry is running on the appropriate port (54322),
   it can be run from the command line on Unix with a line like:
  
   java Server &

   or

   java Server socketPortNumber rmiPortNumber &

   where these port numbers are the port numbers the server is listening
   to. This program should be compiled with
   javac Server.java
   rmic -v1.2 Server
*/
public class Server extends UnicastRemoteObject 
                    implements TextServer, Runnable, ChatConstants
{
   /**
      Constructor. sets up the the server, what ports it listens to,
      rebinds to rmiregistry, and set up HashMap's of parties
      for both socket and RMI.
 
      @throws RemoteException - super() below
      @throws MalformedURLException - rebind below
   */
   public Server(int socketPort, int rmiPort) throws RemoteException,
      MalformedURLException
   { 
      super();

      this.socketPort = socketPort;
      this.rmiPort = rmiPort;
      //LocateRegistry.createRegistry(rmiPort);
      /*can uncomment the above line if only one object in rmiregistry
        and don't want to have to type at command prompt rmiregistry& */

      Naming.rebind("//localhost:"+rmiPort+"/ChatServer", this);
      chatParties = new HashMap();
      rmiParties = new HashMap();
   }

   /**
      Called by main after calls above Constructor.
      Sets ups ServerSocket. Launches a new thread
      using this object for run() whenever a new connection 
      received. 
   */
   public void start()
   {
      try
      {

          ServerSocket s = new ServerSocket(socketPort);
          while (true)
          {
             socket = s.accept();
             System.out.println("Establishing connection.");
             new Thread(this).start();
          }
      }

      catch(IOException ie)
      {
         System.err.println("ServerSocket or Socket.accept error.");
         ie.printStackTrace();
      }
   }
 
   /**
      Used by a Client to notify server it should be added to the
      given party for rmi communication

      @param party - chat party Client belongs to
      @param obj - stub for client
      @throws RemoteException
   */
   public void addToRMIParty(String party, TextPushClient obj) 
      throws RemoteException
   {
      HashSet rmiNames = (HashSet)rmiParties.get(party);
      if(rmiNames == null) rmiNames = new HashSet();
      rmiNames.add(obj);
      rmiParties.put(party, rmiNames);

   }

   /**
      Used by a Client to set the text document accociated with a 
      given patry. Server then broadcasts using Client's textUpdate
      method this document to all the Client's.

      @param s - text document to send to server to broadcast to party
      @param party - chat party Client belongs to
      @throws RemoteException
   */
   public void setText(String s, String rmiParty) throws RemoteException
   {
       HashSet rmiNames = (HashSet)rmiParties.get(rmiParty);
       Iterator partyIterator = rmiNames.iterator();

       while(partyIterator.hasNext())
       {
          TextPushClient obj = (TextPushClient)partyIterator.next();
          obj.textUpdate(s);
       }
   }

   /**
      Used by a Client to notify server it should be deleted to the
      given party for rmi communication

      @param party - chat party Client belongs to
      @param obj - stub for client
      @throws RemoteException
   */
   public void deleteFromRMIParty(String party, TextPushClient obj)
      throws RemoteException
   {
      HashSet rmiNames = (HashSet)rmiParties.get(party);
      if(rmiNames == null) return;
      rmiNames.remove(obj);
      rmiParties.put(party, rmiNames);
   }

   /**
      After a new socket connection has been established in
      @see start(), a new Thread is launched which uses the 
      code below to run. This Thread sets up i/o with this
      particular client and adds the client to the appropriate
      chat party. It then reads any communication from this client
      and broadcasts it to other members in the Client's party.
      When the Client disconnect, this Thread removes it from
      the given chat party and finishes. 
   */
   public void run()
   {
      Socket socket = this.socket;
      if(socket == null) return;

      BufferedReader in;
      PrintWriter out;

      try
      {
         in = new BufferedReader(new InputStreamReader(
           socket.getInputStream()));
         out = new PrintWriter(new OutputStreamWriter(
           socket.getOutputStream()));

      }
      catch(IOException ie)
      {
         System.err.println("Get streams error.");
         return;
      }

      String party = modifyPartyMembers(in, out);
      String line;

      if( party == null) return;

      try
      {
         while(true)
         {
            line = in.readLine();
            if(line == null) break;
            if(line.startsWith(endString))
            {
               broadcastParty("Goodbye, " + 
                  line.substring(endString.length()), party);
               break;
            }
            broadcastParty(line, party);
         }
         socket.close();
      }
      catch(IOException ie)
      {
         ie.printStackTrace();
      }
      deleteFromSocketParty(party, out);

   }  

   /*
     Driver for application.
     Check if we want to use non-default ports. Then sets up
     a Server and starts it running.
   */
   public static void main(String[] args)
   {
      int socketPort;
      int rmiPort;

      if(args.length >= 2)
      {
         socketPort = Integer.parseInt(args[0]);
         rmiPort = Integer.parseInt(args[1]);
      }
      else
      {
         socketPort = SOCKET_PORT;
         rmiPort = RMI_PORT;
      }

      try
      {
         Server server = new Server(socketPort, rmiPort);
         server.start();
      }
      catch(Exception e)
      {
         System.err.println("Server not initialized correctly.");
         e.printStackTrace();
      }      
   
   }

   /*
     Uses sockets to send the given line to all the Client's
     in a given chat party. We have PrintWriter's to each of
     these Client's through the HashSet in associated to
     party in chatParties. 
   */
   protected void broadcastParty(String line, String party)
   {
       HashSet chatNames = (HashSet)chatParties.get(party);
       Iterator partyIterator = chatNames.iterator();

       while(partyIterator.hasNext())
       {
          PrintWriter out = (PrintWriter)partyIterator.next();
          out.println(line);
          out.flush();
       }
   }

   /*
     Removes the PrintWriter out from the HashSet corresponding
     to the String party in the ArrayList of chatParties.
     Called by thread used to handle a particular Client when
     Client disconnects. 
   */
   protected void deleteFromSocketParty(String party, PrintWriter out)
   {
      HashSet chatNames = (HashSet)chatParties.get(party);
      chatNames.remove(out);
      chatParties.put(party, chatNames);        
   }

   /*
     Reads from in to find out who established in's Socket
     (echoed but not used) and what party they belong to then adds
     out to the HashSet associated with that party in chatParties. 
   */
   protected String modifyPartyMembers(BufferedReader in, PrintWriter out)
   {
      String party;
      String name;

      try
      {
         party = in.readLine();
         name = in.readLine();
         System.out.println("Name: "+name);
         System.out.println("Adding to:"+party);
      }
      catch(IOException ie)
      {
         System.err.println("Get chat party error.");
         return null;
      }

      HashSet chatNames = (HashSet)chatParties.get(party);
      if(chatNames == null) chatNames = new HashSet();
      chatNames.add(out);
      chatParties.put(party, chatNames);

      broadcastParty(name+" has joined the group.", party);
      return party;
   }

   protected Socket socket; //most recent socket
   protected int socketPort; /* port socket communication goes through
                                default is SOCKET_PORT */
   protected int rmiPort; /* port rmi communication goes through
                             default is RMI_PORT */

   protected HashMap rmiParties; /* map of parties and rmi object's
                                    of Client's in parties. 
                                 */
   protected HashMap chatParties; /* map of parties and HashSet of
                                     PrintWriter's through Sockets 
                                     to Client's in parties
                                  */
}




//Client.java -- client in chat document program cs151 hw6

import java.rmi.*;
import java.rmi.server.*;
import java.io.*;
import java.net.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.event.*;

/**
   This is the client in our client server chat document program.
   It can be run from the command line on Unix with a line like:
  
   java Client &

   or

   java Client socketPortNumber rmiPortNumber &

   where these port numbers are the port numbers the server is listening
   to. This program should be compiled with
   javac Client.java
   rmic -v1.2 Client

*/
public class Client extends JFrame implements TextPushClient, 
   ActionListener, Runnable, ChatConstants
{
   /**
      Constructor. Creates a Client with the given socket and rmi ports.
      @param socketPort - the socket port number
      @param rmiPort - the rmiPort number
   */
   public Client(int socketPort, int rmiPort)
   {
      super("Chat Client");

      this.socketPort = socketPort;
      this.rmiPort = rmiPort;

      Box box = Box.createVerticalBox();
      connected = false;
 
      setUpTextComponents(box);
      setUpButtons(box);
   }

   /**
      Sets the text in the clients text document area.
      Called by Server.

      @param s -text to set in client text document area
      @throws RemoteException
   */
   public void textUpdate(String s) throws RemoteException
   {
      documentArea.setText(s);
   }

   /**
      Handles all button, text events for this app.
      Calls the appropriate handler method.

      @param e - event we're processing.
   */
   public void actionPerformed(ActionEvent e)
   {
      Object o = e.getSource();
      if(o == chatInput) transmitChat();
      else if(o == connectButton) connect();
      else if (o == setTextButton) setText();
      else if (o == disconnectButton) disconnect(); 
   }

   /**
      Once a connection is established there is a Client Thread
      which just listens for chat rebroadcast from the Server that
      are being ``chatted'' about by other Client's in this
      Client's chat party. This method is the code that this
      dedicated Client thread runs.
   */
   public void run()
   {
      String line;

      while(connected)
      {
         try
         {
            line = in.readLine();
            chatArea.append(line+"\n");
         }
         catch(IOException ie)
         {
            
            ie.printStackTrace();
            disconnect();
            break;

         }

      }

   }

   /**
      check if non-default ports provided in the command line and 
      otherwise creates and starts a Client application.
   */
   public static void main(String[] args)
   {
       int socketPort;
       int rmiPort;

       if(args.length >= 2)
       {
          socketPort = Integer.parseInt(args[0]);
          rmiPort = Integer.parseInt(args[1]);
       }
       else
       {
          socketPort = SOCKET_PORT;
          rmiPort = RMI_PORT;
       }
       
       Client frame = new Client(socketPort, rmiPort);
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       frame.pack();
       frame.show();
   }

   /*
     Sets up all the text components in our client application.
     Adds them to the passed box.
     Also sets up chatInput's listener.  
   */
   protected void setUpTextComponents(Box box)
   {
      chatName = (JTextField)addTextComponent(TEXTFIELD,
         "Chat Name: ", true, box);          
      chatParty = (JTextField)addTextComponent(TEXTFIELD,
         "Chat Party: ", true, box);
      serverURL = (JTextField)addTextComponent(TEXTFIELD,
         "Server URL: ", true, box);
      documentArea= (JTextArea)addTextComponent(TEXTAREA,
         "Text Document: ", false, box);
      chatArea = (JTextArea)addTextComponent(TEXTAREA,
         "Chat Text: ", false, box);
      chatInput = (JTextField)addTextComponent(TEXTFIELD,
         "Chat Input Line: ", true, box);
      getContentPane().add(box);

      chatInput.addActionListener( this);
   }

   /*
     Sets up all the button and their listeners for this application
     and adds them to the passed box
   */
   protected void setUpButtons(Box box)
   {
      Box b = Box.createHorizontalBox();
      
      connectButton = setUpButton("Connect", b);
      setTextButton = setUpButton("Set Text", b);
      disconnectButton = setUpButton("Disconnect", b);
     
      box.add(b);
   }

   /*
     Set up an individual JButton with String name using this 
     application as its listener. Add this button to Box box.   
   */
   protected JButton setUpButton(String name, Box box)
   {
      JButton button = new JButton(name);
      button.addActionListener(this);
      box.add(button);
      return button;
   }

   /*
     Adds a JLabel with name followed by
     either a JTextField or a JTextArea (depending on type)
     to the given Box box. editable says whether or not
     this compoent can be changed by the user.      
   */
   protected JTextComponent addTextComponent(int type, String name,
                  boolean editable, Box box)
   {
      JTextComponent comp;
      Box b= Box.createHorizontalBox();
      b.add(new JLabel(name));
      b.add(Box.createHorizontalGlue());
      box.add(b);

      if(type == TEXTAREA)
      {
         comp = 
            new JTextArea(TEXTAREA_HEIGHT, TEXTAREA_WIDTH);
         box.add(new JScrollPane(comp));         
      }         
      else
      {
         comp = new JTextField(TEXTFIELD_WIDTH);
         b.add(comp);
      }

      comp.setEditable(editable);
      box.add(Box.createVerticalStrut(SEPARATION_HEIGHT));
      return comp;
   }

   /*
     Called when return hit in chat input field. Send current
     chat input line to server then clear this field.
   */
   protected void transmitChat()
   {
      if(!connected)
      {
        chatArea.append("You must establish a connection to chat.\n");
         return;
      }

      out.println(chatName.getText()+": "+chatInput.getText());
         //note we send who we are as well as what we are saying
      out.flush();
      chatInput.setText("");
   
   }

   /*
     Called when connect button clicked. Sets up both socket and
     RMI connection with server. Tells server which socket and
     rmi party it should be added to. Starts the thread to listen to 
     socket for what is being ``chatted'' about in this chat party.
   */
   protected void connect()
   {
      if(chatName.getText().length() == 0 ||
         chatParty.getText().length() == 0 ||
         serverURL.getText().length() == 0
        )
      {
         chatArea.append(
            "You must fill the first three fields to connect.\n");
         return;
      }         

      if(connected)
      {
         chatArea.append(
            "Please disconnect from current connection before\n"
            +"making a new connection.\n");
         return;
      }

      try
      {
         socket = new Socket( serverURL.getText(), socketPort);
         out = new PrintWriter(new OutputStreamWriter(
            socket.getOutputStream()));
         in = new BufferedReader(new InputStreamReader(
            socket.getInputStream()));

         out.println(chatParty.getText());
         out.println(chatName.getText());
         out.flush();

         connected = true;
         chatThread = new Thread(this);
         chatThread.start();
      }
      catch(IOException ie)
      {
         chatArea.append(
            "Error Connecting to Server.\n"+
            "Please check the URL.\n");
         ie.printStackTrace();
      }

      try
      {
         UnicastRemoteObject.exportObject(this);
         server = (TextServer)Naming.lookup("rmi://"+serverURL.getText()+
            ":"+rmiPort+"/ChatServer");
         server.addToRMIParty(chatParty.getText(), this);
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }

   /*
     Called when Set Text button clicked. Uses a JFileChooser
     to select a text document. Method then reads in document
     and calls TextServer's setText method to send to Server.
   */
   protected void setText()
   {
      if(server == null)
      {
         documentArea.append("Must connect to server first.\n");
         return;
      }

      JFileChooser choose = new JFileChooser();
      choose.setFileSelectionMode( JFileChooser.FILES_ONLY);
        
      int result;

      result = choose.showOpenDialog(this);

      if(result == JFileChooser.CANCEL_OPTION)
            return;
      File file = choose.getSelectedFile();

      if(file == null)
      { 
         JOptionPane.showMessageDialog( 
            this,
            "Bad File Name", "Bad File Name",
            JOptionPane.ERROR_MESSAGE );
         return;
      }

     String fileData = "";
     String line;

     try
     {
        BufferedReader in = new BufferedReader(
            new FileReader(file));
        while((line= in.readLine()) != null)
        {
           fileData += line + "\n";
        }
        in.close();
        server.setText(fileData, chatParty.getText());
      }
      catch(RemoteException re)
      {
         documentArea.append("Couldn't set text to server.\n");
         re.printStackTrace();
      }
      catch(Exception e)
      {
         documentArea.append("Couldn't read file.\n");
         e.printStackTrace();
      }
   }

   /*
      Called when disconnect button clicked.
      Notifies server that no longer connect by sending endString
      then closes socket.
   */
   protected void disconnect()
   {
     if(!connected)
     {
        chatArea.append("You were not connected.\n");
        return;
     }
     connected = false;
     try
     {
        out.println(endString+chatName.getText());
        out.flush();
        socket.close();
        chatArea.append("Disconnected.\n");
     }
     catch(IOException ie)
     {
        chatArea.append("Error disconnecting.\n");
        ie.printStackTrace();
     }
   }

   protected Socket socket; /* socket used for chat communication
                               through server */
   protected int socketPort; /* port we do socket communication with
                                server. By default SOCKET_PORT */

   protected TextServer server; /* rmi stub for text document server 
                                   communicating with */
   protected int rmiPort; /* port on which rmi communication done
                             By default RMI_PORT */

   protected PrintWriter out; //to write out through socket
   protected BufferedReader in; //to read in through socket

   protected Thread chatThread; //handles reading chat coming from server
   protected boolean connected; //tells chatThread if still connected

   /*
     The next group of fields correspond to the
     GUI components on the application 
   */
   protected JTextField chatName;
   protected JTextField chatParty;
   protected JTextField serverURL;
   protected JTextField chatInput;

   protected JTextArea documentArea;
   protected JTextArea chatArea; 

   protected JButton connectButton;
   protected JButton setTextButton;
   protected JButton disconnectButton;

   /*
     Constants -- probably should make final 
   */
   protected int TEXTAREA = 0; //tells addTextComponent to make textareas
   protected int TEXTFIELD = 1;//tells addTextComponent to make textfields
   protected int TEXTFIELD_WIDTH=15; //default text field size

   protected int TEXTAREA_WIDTH=30; //default dimensions of textareas
   protected int TEXTAREA_HEIGHT=7;

   protected int SEPARATION_HEIGHT=8; // vertical gap between GUI's   
}


//TextServer.java - interface of Server that Client uses
import java.rmi.*;

/**
   RMI server interface via which Client can send a text document
   to the Server and via which it can let the Server know it wants
   to be notified of any documents sent by other members of Client's
   chat party.   
*/
public interface TextServer extends Remote
{
   /**
      Used by a Client to notify server it should be added to the
      given party for rmi communication

      @param party - chat party Client belongs to
      @param obj - stub for client
      @throws RemoteException
   */
   public void addToRMIParty(String party, TextPushClient obj) 
      throws RemoteException;

   /**
      Used by a Client to set the text document accociated with a 
      given patry. Server then broadcasts using Client's textUpdate
      method this document to all the Client's.

      @param s - text document to send to server to broadcast to party
      @param party - chat party Client belongs to
      @throws RemoteException
   */
   public void setText(String s, String chatParty)
      throws RemoteException;

   /**
      Used by a Client to notify server it should be deleted to the
      given party for rmi communication

      @param party - chat party Client belongs to
      @param obj - stub for client
      @throws RemoteException
   */
   public void deleteFromRMIParty(String party, TextPushClient obj)
      throws RemoteException;
}


//TextPushClient.java - interface server uses to set client text doc

import java.rmi.*;

/**
   This interface is extended by our Client class and is used by
   the Server class when it broadcasts updates to the text document
   that is to be viewed by everyone in a chat party
*/
public interface TextPushClient extends Remote
{
   /**
      Sets the text in the clients text document area

      @param s -text to set in client text document area
      @throws RemoteException 
   */
   public void textUpdate(String s)
      throws RemoteException;
}


//ChatConstants.java

/**
   These are constants which are used by both the Server and Client
   classes.
*/
public interface ChatConstants
{
   public final int SOCKET_PORT = 54321; // port for socket communication
   public final int RMI_PORT = SOCKET_PORT+1; // port for rmi communication
   public final String endString = "BYE::::"; /* text used to terminate
                                                 socket communication */
}