diff --git a/code/simplechat1/ClientConsole.java b/code/simplechat1/ClientConsole.java
index c9bb4e9..e95f0f4 100644
--- a/code/simplechat1/ClientConsole.java
+++ b/code/simplechat1/ClientConsole.java
@@ -41,18 +41,13 @@ public class ClientConsole implements ChatIF
* @param host The host to connect to.
* @param port The port to connect on.
*/
- public ClientConsole(String host, int port)
- {
- try
- {
- client= new ChatClient(host, port, this);
- }
- catch(IOException exception)
- {
- System.out.println("Error: Can't setup connection!"
- + " Terminating client.");
- System.exit(1);
- }
+ public ClientConsole(String username, String host, int port) {
+ try {
+ client = new ChatClient(username, host, port, this);
+ }
+ catch (IOException exception) {
+ System.out.println("Cannot establish connection on port" + port + ". Input a command.");
+ }
}
@@ -79,10 +74,13 @@ public void accept()
catch (Exception ex)
{
System.out.println
- ("Unexpected error while reading from console!");
+ ("Terminating client.");
}
}
+
+
+
/**
* This method overrides the method in the ChatIF interface. It
* displays a message onto the screen.
@@ -91,7 +89,7 @@ public void accept()
*/
public void display(String message)
{
- System.out.println("> " + message);
+ System.out.println(" > " + message);
}
@@ -104,19 +102,45 @@ public void display(String message)
*/
public static void main(String[] args)
{
- String host = "";
- int port = 0; //The port number
+ String host;
+ int port;
+ String username;
- try
- {
- host = args[0];
- }
- catch(ArrayIndexOutOfBoundsException e)
- {
- host = "localhost";
- }
- ClientConsole chat= new ClientConsole(host, DEFAULT_PORT);
+ /*
+ * Modified for E51
+ * Require a login ID
+ */
+ try {
+ username = args[0];
+ }
+ catch (ArrayIndexOutOfBoundsException e) {
+ System.err.println("A userID is required to proceed.");
+ System.exit(1);
+ return;
+ }
+
+ try {
+ host = args[1];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ host = "localhost";
+ }
+
+ /*
+ * Modified for E49
+ * Allow more than just the default port
+ */
+ try {
+ port = Integer.parseInt(args[2]);
+ }
+ catch (Exception e) {
+ port = DEFAULT_PORT;
+ }
+
+ ClientConsole chat = new ClientConsole(username, host, port);
chat.accept(); //Wait for console data
}
}
+
+
+
//End of ConsoleChat class
diff --git a/code/simplechat1/EchoServer.java b/code/simplechat1/EchoServer.java
index d4f3a1a..dbed80b 100644
--- a/code/simplechat1/EchoServer.java
+++ b/code/simplechat1/EchoServer.java
@@ -3,15 +3,18 @@
// license found at www.lloseng.com
import java.io.*;
+import client.*;
import ocsf.server.*;
+
+
/**
* This class overrides some of the methods in the abstract
* superclass in order to give more functionality to the server.
*
* @author Dr Timothy C. Lethbridge
- * @author Dr Robert Laganière
- * @author François Bélanger
+ * @author Dr Robert Laganiegrave;re
+ * @author Franccedil;ois Beacute;langer
* @author Paul Holden
* @version July 2000
*/
@@ -48,9 +51,25 @@ public EchoServer(int port)
public void handleMessageFromClient
(Object msg, ConnectionToClient client)
{
+
System.out.println("Message received: " + msg + " from " + client);
this.sendToAllClients(msg);
}
+
+ public void clientDisconnected(ConnectionToClient client){
+
+ String msg = "Client disconnet at " + client;
+ System.out.println(msg);
+ this.sendToAllClients(msg);
+ }
+
+ public void clientConnected(ConnectionToClient client) {
+
+
+ String msg = "Client connected at " + client;
+ System.out.println(msg);
+ this.sendToAllClients(msg);
+ }
/**
* This method overrides the one in the superclass. Called
@@ -70,7 +89,88 @@ protected void serverStopped()
{
System.out.println
("Server has stopped listening for connections.");
+ this.sendToAllClients(null);
}
+
+ public void handleMessageFromServerUI(String message) {
+ if (message.startsWith("#")) {
+ String[] index = message.split(" ");
+ String command = index[0];
+ switch (command) {
+ case "#quit":
+ //closes the server and then exits it
+ try {
+ this.close();
+ } catch (IOException e) {
+ System.exit(1);
+ }
+ System.exit(0);
+ break;
+ case "#stop":
+ this.sendToAllClients("#stop");
+ this.stopListening();
+ break;
+ case "#close":
+ try {
+ this.close();
+ } catch (IOException e) {
+ }
+ break;
+ case "#setport":
+ if (!this.isListening() && this.getNumberOfClients() < 1) {
+ super.setPort(Integer.parseInt(index[1]));
+ System.out.println("Port set to " + Integer.parseInt(index[1]));
+ } else {
+ System.out.println("Connection already Established.");
+ }
+ break;
+ case "#start":
+ if (!this.isListening()) {
+ try {
+ this.listen();
+ } catch (IOException e) {
+ //error listening for clients
+ }
+ } else {
+ System.out.println("Server is up and running.");
+ }
+ break;
+ case "#getport":
+ System.out.println("Current port is " + this.getPort());
+ break;
+ default:
+ System.out.println("The command '" + command+ "' is not recognized.");
+ break;
+ }
+ } else {
+ this.sendToAllClients(message);
+ }
+ }
+
+ public void accept(){ //method for taking input
+
+ try
+ {
+ BufferedReader fromConsole =
+ new BufferedReader(new InputStreamReader(System.in));
+ String message;
+
+ while (true)
+ {
+ message = fromConsole.readLine();
+ handleMessageFromServerUI(message);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.out.println
+ ("Terminating.");
+ }
+ }
+
+
+
+
//Class methods ***************************************************
@@ -104,6 +204,17 @@ public static void main(String[] args)
{
System.out.println("ERROR - Could not listen for clients!");
}
+
+ sv.accept();
+ //serverClient.accept();
}
+
+
+
+
+
+
+
+
}
//End of EchoServer class
diff --git a/code/simplechat1/client/ChatClient.java b/code/simplechat1/client/ChatClient.java
index fe1401e..da92f5a 100644
--- a/code/simplechat1/client/ChatClient.java
+++ b/code/simplechat1/client/ChatClient.java
@@ -26,6 +26,7 @@ public class ChatClient extends AbstractClient
* the display method in the client.
*/
ChatIF clientUI;
+ String userID;
//Constructors ****************************************************
@@ -38,12 +39,15 @@ public class ChatClient extends AbstractClient
* @param clientUI The interface type variable.
*/
- public ChatClient(String host, int port, ChatIF clientUI)
+ public ChatClient(String username, String host, int port, ChatIF clientUI)
throws IOException
{
super(host, port); //Call the superclass constructor
this.clientUI = clientUI;
+ this.userID = username;
openConnection();
+
+ this.sendToServer("#login " + username); //E6 login command
}
@@ -56,9 +60,17 @@ public ChatClient(String host, int port, ChatIF clientUI)
*/
public void handleMessageFromServer(Object msg)
{
- clientUI.display(msg.toString());
+ if (msg.toString().charAt(0) == '#') {
+ return;
+ }
+ if (msg == null){
+ System.out.println("WARNING - The server has stopped listening for connections. \nSERVER SHUTTING DOWN! DISCONNECTING");
+ }
+
+ System.out.println("SERVER MSG> " + msg.toString());
}
+
/**
* This method handles all data coming from the UI
*
@@ -66,6 +78,58 @@ public void handleMessageFromServer(Object msg)
*/
public void handleMessageFromClientUI(String message)
{
+
+ if (message.charAt(0) == '#') { // detect '#'
+ String[] index = message.split(" "); //split message to factor for other inputs
+ String command = index[0];
+ switch (command) {
+ case "#quit":
+ quit();
+ break;
+ case "#logoff":
+ try {
+ closeConnection();
+ } catch (IOException e) {
+ System.out.println("Error closing connection!!!");
+ }
+ break;
+ case "#sethost":
+ if (this.isConnected()) {
+ System.out.println("There is already a connection established. Disconnect and try again.");
+ } else {
+ this.setHost((index[1]));
+ }
+ break;
+ case "#setport":
+ if (this.isConnected()) {
+ System.out.println("There is already a connection established. Disconnect and try again.");
+ } else {
+ this.setPort(Integer.parseInt(index[1]));
+ }
+ break;
+ case "#login":
+ if (this.isConnected()) {
+ System.out.println("There is already a connection established. Disconnect and try again.");
+ } else {
+ try {
+ this.openConnection();
+ } catch (IOException e) {
+ System.out.println("Could not establish connection to server.");
+ }
+ }
+ break;
+ case "#gethost":
+ System.out.println("Current host is " + this.getHost());
+ break;
+ case "#getport":
+ System.out.println("Current port is " + this.getPort());
+ break;
+ default:
+ System.out.println("IThe command '" + command+ "' is not recognized.");
+ break;
+ }
+ }
+ else{
try
{
sendToServer(message);
@@ -77,6 +141,21 @@ public void handleMessageFromClientUI(String message)
quit();
}
}
+ }
+
+ public String getUsername(){
+ return this.userID;
+ }
+
+
+ public void connectionException(Throwable exception){
+ System.out.println("WARNING - The server has stopped listening for connections.");
+ quit(); //displays information about the exception
+
+ }
+ public void connectionClosed(){
+ System.out.println("Connection Closed to Server.");
+ }
/**
* This method terminates the client.
@@ -87,8 +166,16 @@ public void quit()
{
closeConnection();
}
- catch(IOException e) {}
+ catch(IOException e) {
+ connectionException(e);
+ }
System.exit(0);
}
+
+
+
+
+
}
+
//End of ChatClient class
diff --git a/code/simplechat1/ocsf/client/AbstractClient.java b/code/simplechat1/ocsf/client/AbstractClient.java
new file mode 100644
index 0000000..e905636
--- /dev/null
+++ b/code/simplechat1/ocsf/client/AbstractClient.java
@@ -0,0 +1,333 @@
+// This file contains material supporting section 3.7 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.client;
+
+import java.io.*;
+import java.net.*;
+
+/**
+ * The AbstractClient contains all the methods necessary to set
+ * up the client side of a client-server architecture. When a client is thus
+ * connected to the server, the two programs can then exchange
+ * Object instances.
+ *
+ * Method handleMessageFromServer must be defined by a concrete
+ * subclass. Several other hook methods may also be overriden.
+ *
+ * Several public service methods are provided to application that use this + * framework. + *
+ * Project Name: OCSF (Object Client-Server Framework) + *
+ *
+ * @author Dr. Robert Laganière
+ * @author Dr. Timothy C. Lethbridge
+ * @author François Bél;langer
+ * @author Paul Holden
+ * @version February 2001 (2.12)
+ */
+public abstract class AbstractClient implements Runnable {
+
+ // INSTANCE VARIABLES ***********************************************
+
+ /**
+ * Sockets are used in the operating system as channels of communication
+ * between two processes.
+ *
+ * @see java.net.Socket
+ */
+ private Socket clientSocket;
+
+ /**
+ * The stream to handle data going to the server.
+ */
+ private ObjectOutputStream output;
+
+ /**
+ * The stream to handle data from the server.
+ */
+ private ObjectInputStream input;
+
+ /**
+ * The thread created to read data from the server.
+ */
+ private Thread clientReader;
+
+ /**
+ * Indicates if the thread is ready to stop. Needed so that the loop in the
+ * run method knows when to stop waiting for incoming messages.
+ */
+ private boolean readyToStop = false;
+
+ /**
+ * The server's host name.
+ */
+ private String host;
+
+ /**
+ * The port number.
+ */
+ private int port;
+
+ // CONSTRUCTORS *****************************************************
+
+ /**
+ * Constructs the client.
+ *
+ * @param host
+ * the server's host name.
+ * @param port
+ * the port number.
+ */
+ public AbstractClient(String host, int port) {
+ // Initialize variables
+ this.host = host;
+ this.port = port;
+ }
+
+ // INSTANCE METHODS *************************************************
+
+ /**
+ * Opens the connection with the server. If the connection is already
+ * opened, this call has no effect.
+ *
+ * @exception IOException
+ * if an I/O error occurs when opening.
+ */
+ final public void openConnection() throws IOException {
+ // Do not do anything if the connection is already open
+ if (isConnected())
+ return;
+
+ // Create the sockets and the data streams
+ try {
+ clientSocket = new Socket(host, port);
+ output = new ObjectOutputStream(clientSocket.getOutputStream());
+ input = new ObjectInputStream(clientSocket.getInputStream());
+ } catch (IOException ex)
+ // All three of the above must be closed when there is a failure
+ // to create any of them
+ {
+ try {
+ closeAll();
+ } catch (Exception exc) {
+ }
+
+ throw ex; // Rethrow the exception.
+ }
+
+ clientReader = new Thread(this); // Create the data reader thread
+ readyToStop = false;
+ clientReader.start(); // Start the thread
+ }
+
+ /**
+ * Sends an object to the server. This is the only way that methods should
+ * communicate with the server.
+ *
+ * @param msg
+ * The message to be sent.
+ * @exception IOException
+ * if an I/O error occurs when sending
+ */
+ final public void sendToServer(Object msg) throws IOException {
+ if (clientSocket == null || output == null)
+ throw new SocketException("socket does not exist");
+
+ output.writeObject(msg);
+ }
+
+ /**
+ * Reset the object output stream so we can use the same
+ * buffer repeatedly. This would not normally be used, but is necessary
+ * in some circumstances when Java refuses to send data that it thinks has been sent.
+ */
+ final public void forceResetAfterSend() throws IOException {
+ output.reset();
+ }
+
+ /**
+ * Closes the connection to the server.
+ *
+ * @exception IOException
+ * if an I/O error occurs when closing.
+ */
+ final public void closeConnection() throws IOException {
+ // Prevent the thread from looping any more
+ readyToStop = true;
+
+ try {
+ closeAll();
+ } finally {
+ // Call the hook method
+ connectionClosed();
+ }
+ }
+
+ // ACCESSING METHODS ------------------------------------------------
+
+ /**
+ * @return true if the client is connnected.
+ */
+ final public boolean isConnected() {
+ return clientReader != null && clientReader.isAlive();
+ }
+
+ /**
+ * @return the port number.
+ */
+ final public int getPort() {
+ return port;
+ }
+
+ /**
+ * Sets the server port number for the next connection. The change in port
+ * only takes effect at the time of the next call to openConnection().
+ *
+ * @param port
+ * the port number.
+ */
+ final public void setPort(int port) {
+ this.port = port;
+ }
+
+ /**
+ * @return the host name.
+ */
+ final public String getHost() {
+ return host;
+ }
+
+ /**
+ * Sets the server host for the next connection. The change in host only
+ * takes effect at the time of the next call to openConnection().
+ *
+ * @param host
+ * the host name.
+ */
+ final public void setHost(String host) {
+ this.host = host;
+ }
+
+ /**
+ * returns the client's description.
+ *
+ * @return the client's Inet address.
+ */
+ final public InetAddress getInetAddress() {
+ return clientSocket.getInetAddress();
+ }
+
+ // RUN METHOD -------------------------------------------------------
+
+ /**
+ * Waits for messages from the server. When each arrives, a call is made to
+ * handleMessageFromServer(). Not to be explicitly called.
+ */
+ final public void run() {
+ connectionEstablished();
+
+ // The message from the server
+ Object msg;
+
+ // Loop waiting for data
+
+ try {
+ while (!readyToStop) {
+ // Get data from Server and send it to the handler
+ // The thread waits indefinitely at the following
+ // statement until something is received from the server
+ msg = input.readObject();
+
+ // Concrete subclasses do what they want with the
+ // msg by implementing the following method
+ handleMessageFromServer(msg);
+ }
+ } catch (Exception exception) {
+ if (!readyToStop) {
+ try {
+ closeAll();
+ } catch (Exception ex) {
+ }
+
+ connectionException(exception);
+ }
+ } finally {
+ clientReader = null;
+ }
+ }
+
+ // METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES ---------
+
+ /**
+ * Hook method called after the connection has been closed. The default
+ * implementation does nothing. The method may be overriden by subclasses to
+ * perform special processing such as cleaning up and terminating, or
+ * attempting to reconnect.
+ */
+ protected void connectionClosed() {
+ }
+
+ /**
+ * Hook method called each time an exception is thrown by the client's
+ * thread that is waiting for messages from the server. The method may be
+ * overridden by subclasses.
+ *
+ * @param exception
+ * the exception raised.
+ */
+ protected void connectionException(Exception exception) {
+ }
+
+ /**
+ * Hook method called after a connection has been established. The default
+ * implementation does nothing. It may be overridden by subclasses to do
+ * anything they wish.
+ */
+ protected void connectionEstablished() {
+ }
+
+ /**
+ * Handles a message sent from the server to this client. This MUST be
+ * implemented by subclasses, who should respond to messages.
+ *
+ * @param msg
+ * the message sent.
+ */
+ protected abstract void handleMessageFromServer(Object msg);
+
+ // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------
+
+ /**
+ * Closes all aspects of the connection to the server.
+ *
+ * @exception IOException
+ * if an I/O error occurs when closing.
+ */
+ private void closeAll() throws IOException {
+ try {
+ // Close the socket
+ if (clientSocket != null)
+ clientSocket.close();
+
+ // Close the output stream
+ if (output != null)
+ output.close();
+
+ // Close the input stream
+ if (input != null)
+ input.close();
+ } finally {
+ // Set the streams and the sockets to NULL no matter what
+ // Doing so allows, but does not require, any finalizers
+ // of these objects to reclaim system resources if and
+ // when they are garbage collected.
+ output = null;
+ input = null;
+ clientSocket = null;
+ }
+ }
+}
+// end of AbstractClient class
\ No newline at end of file
diff --git a/code/simplechat1/ocsf/client/AdaptableClient.java b/code/simplechat1/ocsf/client/AdaptableClient.java
new file mode 100644
index 0000000..143592e
--- /dev/null
+++ b/code/simplechat1/ocsf/client/AdaptableClient.java
@@ -0,0 +1,82 @@
+// This file contains material supporting section 6.13 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.client;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+* The AdaptableClient is a class
+* that extends the AbstractClient in place of
+* the ObservableClient .
+* +* Project Name: OCSF (Object Client-Server Framework)
+*
+* @author Dr. Robert Laganière
+* @version Febuary 2001
+*/
+class AdaptableClient extends AbstractClient
+{
+ //Instance variables **********************************************
+
+ /**
+ * The proxy used to simulate multiple class inheritance.
+ */
+ private ObservableClient client;
+
+// CONSTRUCTORS *****************************************************
+
+ /**
+ * Constructs the client adapter.
+ *
+ * @param host the server's host name.
+ * @param port the port number.
+ */
+ public AdaptableClient(String host, int port, ObservableClient client)
+ {
+ super(host, port);
+ this.client = client;
+ }
+
+// OVERRIDDEN METHODS *************************************************
+
+ /**
+ * Hook method called after the connection has been closed.
+ */
+ final protected void connectionClosed()
+ {
+ client.connectionClosed();
+ }
+
+ /**
+ * Hook method called after an exception
+ * is raised by the client listening thread.
+ *
+ * @param exception the exception raised.
+ */
+ final protected void connectionException(Exception exception)
+ {
+ client.connectionException(exception);
+ }
+
+ /**
+ * Hook method called after a connection has been established.
+ */
+ final protected void connectionEstablished()
+ {
+ client.connectionEstablished();
+ }
+
+ /**
+ * Handles a message sent from the server to this client.
+ *
+ * @param msg the message sent.
+ */
+ final protected void handleMessageFromServer(Object msg)
+ {
+ client.handleMessageFromServer(msg);
+ }
+}
diff --git a/code/simplechat1/ocsf/client/ObservableClient.java b/code/simplechat1/ocsf/client/ObservableClient.java
new file mode 100644
index 0000000..a816488
--- /dev/null
+++ b/code/simplechat1/ocsf/client/ObservableClient.java
@@ -0,0 +1,181 @@
+// This file contains material supporting section 6.13 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.client;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * This class acts as a subclass of AbstractClient
+ * and is also an Observable class.
+ * Each time a new message is received, observers are notified.
+ *
+ * @author Dr Robert Laganière
+ * @author Dr Timothy C. Lethbridge
+ * @author François Bélange
+ * @version Febuary 2001
+ */
+public class ObservableClient extends Observable
+{
+ // Class variables ***************************************************
+
+ /**
+ * Indicates occurence of a connection exception.
+ */
+ public static final String CONNECTION_EXCEPTION = "#OC:Connection error.";
+
+ /**
+ * Indicates a close of the connection to server.
+ */
+ public static final String CONNECTION_CLOSED = "#OC:Connection closed.";
+
+ /**
+ * Indicates establishment of a connection to server.
+ */
+ public static final String CONNECTION_ESTABLISHED = "#OC:Connection established.";
+
+ //Instance variables **********************************************
+
+ /**
+ * The service instance used to simulate multiple class inheritance.
+ */
+ private AdaptableClient service;
+
+ //Constructor *****************************************************
+
+ public ObservableClient(String host, int port)
+ {
+ service = new AdaptableClient(host, port, this);
+ }
+
+ //Instance methods ************************************************
+
+ /**
+ * Opens the connections with the server.
+ */
+ final public void openConnection() throws IOException
+ {
+ service.openConnection();
+ }
+
+ /**
+ * Closes the connection to the server.
+ */
+ final public void closeConnection() throws IOException
+ {
+ service.closeConnection();
+ }
+
+ /**
+ * Sends an object to the server. This is the only way that
+ * methods should communicate with the server.
+ *
+ * @param msg The message to be sent.
+ */
+ final public void sendToServer(Object msg) throws IOException
+ {
+ service.sendToServer(msg);
+ }
+
+// ACCESSING METHODS ------------------------------------------------
+
+ /**
+ * @used to find out if the client is connnected.
+ */
+ final public boolean isConnected()
+ {
+ return service.isConnected();
+ }
+
+ /**
+ * @return the port number.
+ */
+ final public int getPort()
+ {
+ return service.getPort();
+ }
+
+ /**
+ * Sets the server port number for the next connection.
+ * Only has effect if the client is not currently connected.
+ *
+ * @param port the port number.
+ */
+ final public void setPort(int port)
+ {
+ service.setPort(port);
+ }
+
+ /**
+ * @return the host name.
+ */
+ final public String getHost()
+ {
+ return service.getHost();
+ }
+
+ /**
+ * Sets the server host for the next connection.
+ * Only has effect if the client is not currently connected.
+ *
+ * @param host the host name.
+ */
+ final public void setHost(String host)
+ {
+ service.setHost(host);
+ }
+
+ /**
+ * @return the client's Inet address.
+ */
+ final public InetAddress getInetAddress()
+ {
+ return service.getInetAddress();
+ }
+
+
+ /**
+ * This method is used to handle messages from the server. This method
+ * can be overriden but should always call notifyObservers().
+ *
+ * @param message The message received from the client.
+ */
+ protected void handleMessageFromServer(Object message)
+ {
+ setChanged();
+ notifyObservers(message);
+ }
+
+ /**
+ * Hook method called after the connection has been closed.
+ */
+ protected void connectionClosed()
+ {
+ setChanged();
+ notifyObservers(CONNECTION_CLOSED);
+ }
+
+ /**
+ * Hook method called each time an exception
+ * is raised by the client listening thread.
+ *
+ * @param exception the exception raised.
+ */
+ protected void connectionException(Exception exception)
+ {
+ setChanged();
+ notifyObservers(CONNECTION_EXCEPTION);
+ }
+
+ /**
+ * Hook method called after a connection has been established.
+ */
+ protected void connectionEstablished()
+ {
+ setChanged();
+ notifyObservers(CONNECTION_ESTABLISHED);
+ }
+}
diff --git a/code/simplechat1/ocsf/index.html b/code/simplechat1/ocsf/index.html
new file mode 100644
index 0000000..13025a0
--- /dev/null
+++ b/code/simplechat1/ocsf/index.html
@@ -0,0 +1,22 @@
+
+
To install OCSF, simply compile all the .java files in the client and +server directories.
+ +OCSF is a Framework, so this directory contains no main program. To +learn how it works, consult the book +"Object-Oriented Software Engineering: Practical Software Development +using UML and Java" by Lethbridge and Laganière.
+ +To use OCSF, import the "ocsf.client" or "ocsf.server" package in your +application code. Make sure that the ocsf directory is in your classpath +when you compile your application.
+ + + + + diff --git a/code/simplechat1/ocsf/server/AbstractServer.java b/code/simplechat1/ocsf/server/AbstractServer.java new file mode 100644 index 0000000..0134b22 --- /dev/null +++ b/code/simplechat1/ocsf/server/AbstractServer.java @@ -0,0 +1,459 @@ +// This file contains material supporting section 3.8 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +import java.net.*; +import java.util.*; +import java.io.*; + +/** +* The AbstractServer class maintains a thread that waits
+* for connection attempts from clients. When a connection attempt occurs
+* it creates a new ConnectionToClient instance which
+* runs as a thread. When a client is thus connected to the
+* server, the two programs can then exchange Object
+* instances.
+*
+* Method handleMessageFromClient must be defined by
+* a concrete subclass. Several other hook methods may also be
+* overriden.
+* +* Several public service methods are provided to applications that use +* this framework, and several hook methods are also available
+* +* Project Name: OCSF (Object Client-Server Framework)
+*
+* @author Dr Robert Laganière
+* @author Dr Timothy C. Lethbridge
+* @author François Bélanger
+* @author Paul Holden
+* @version February 2001 (2.12)
+* @see ocsf.server.ConnectionToClient
+*/
+public abstract class AbstractServer implements Runnable
+{
+ // INSTANCE VARIABLES *********************************************
+
+ /**
+ * The server socket: listens for clients who want to connect.
+ */
+ private ServerSocket serverSocket = null;
+
+ /**
+ * The connection listener thread.
+ */
+ private Thread connectionListener;
+
+ /**
+ * The port number
+ */
+ private int port;
+
+ /**
+ * The server timeout while for accepting connections.
+ * After timing out, the server will check to see if a command to
+ * stop the server has been issued; it not it will resume accepting
+ * connections.
+ * Set to half a second by default.
+ */
+ private int timeout = 500;
+
+ /**
+ * The maximum queue length; i.e. the maximum number of clients that
+ * can be waiting to connect.
+ * Set to 10 by default.
+ */
+ private int backlog = 10;
+
+ /**
+ * The thread group associated with client threads. Each member of the
+ * thread group is a
+*
+* Project Name: OCSF (Object Client-Server Framework)
+*
+* @author Dr. Robert Laganière
+* @version Febuary 2001
+*/
+class AdaptableServer extends AbstractServer
+{
+ //Instance variables **********************************************
+
+ /**
+ * The adapter used to simulate multiple class inheritance.
+ */
+ private ObservableServer server;
+
+// CONSTRUCTORS *****************************************************
+
+ /**
+ * Constructs the server adapter.
+ *
+ * @param host the server's host name.
+ * @param port the port number.
+ */
+ public AdaptableServer(int port, ObservableServer server)
+ {
+ super(port);
+ this.server = server;
+ }
+
+// OVERRIDDEN METHODS ---------
+
+ /**
+ * Hook method called each time a new client connection is
+ * accepted.
+ *
+ * @param client the connection connected to the client.
+ */
+ final protected void clientConnected(ConnectionToClient client)
+ {
+ server.clientConnected(client);
+ }
+
+ /**
+ * Hook method called each time a client disconnects.
+ *
+ * @param client the connection with the client.
+ */
+ final protected void clientDisconnected(ConnectionToClient client)
+ {
+ server.clientDisconnected(client);
+ }
+
+ /**
+ * Hook method called each time an exception
+ * is raised in a client thread.
+ *
+ * @param client the client that raised the exception.
+ * @param exception the exception raised.
+ */
+ final protected void clientException(ConnectionToClient client,
+ Throwable exception)
+ {
+ server.clientException(client, exception);
+ }
+
+ /**
+ * Hook method called when the server stops accepting
+ * connections because an exception has been raised.
+ *
+ * @param exception the exception raised.
+ */
+ final protected void listeningException(Throwable exception)
+ {
+ server.listeningException(exception);
+ }
+
+ /**
+ * Hook method called when the server stops accepting
+ * connections.
+ */
+ final protected void serverStopped()
+ {
+ server.serverStopped();
+ }
+
+ /**
+ * Hook method called when the server starts listening for
+ * connections.
+ */
+ final protected void serverStarted()
+ {
+ server.serverStarted();
+ }
+
+ /**
+ * Hook method called when the server is closed.
+ */
+ final protected void serverClosed()
+ {
+ server.serverClosed();
+ }
+
+ /**
+ * Handles a command sent from the client to the server.
+ *
+ * @param msg the message sent.
+ * @param client the connection connected to the client that
+ * sent the message.
+ */
+ final protected void handleMessageFromClient(Object msg,
+ ConnectionToClient client)
+ {
+ server.handleMessageFromClient(msg, client);
+ }
+}
diff --git a/code/simplechat1/ocsf/server/ConnectionToClient.java b/code/simplechat1/ocsf/server/ConnectionToClient.java
new file mode 100644
index 0000000..4394b37
--- /dev/null
+++ b/code/simplechat1/ocsf/server/ConnectionToClient.java
@@ -0,0 +1,270 @@
+// This file contains material supporting section 3.8 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.server;
+
+import java.io.*;
+import java.net.*;
+import java.util.HashMap;
+
+/**
+ * An instance of this class is created by the server when a client connects. It
+ * accepts messages coming from the client and is responsible for sending data
+ * to the client since the socket is private to this class. The AbstractServer
+ * contains a set of instances of this class and is responsible for adding and
+ * deleting them.
+ *
+ * Project Name: OCSF (Object Client-Server Framework)
+ *
+ *
+ * @author Dr Robert Laganière
+ * @author Dr Timothy C. Lethbridge
+ * @author François Bélanger
+ * @author Paul Holden
+ * @version February 2001 (2.12)
+ */
+public class ConnectionToClient extends Thread {
+ // INSTANCE VARIABLES ***********************************************
+
+ /**
+ * A reference to the Server that created this instance.
+ */
+ private AbstractServer server;
+
+ /**
+ * Sockets are used in the operating system as channels of communication
+ * between two processes.
+ *
+ * @see java.net.Socket
+ */
+ private Socket clientSocket;
+
+ /**
+ * Stream used to read from the client.
+ */
+ private ObjectInputStream input;
+
+ /**
+ * Stream used to write to the client.
+ */
+ private ObjectOutputStream output;
+
+ /**
+ * Indicates if the thread is ready to stop. Set to true when closing of the
+ * connection is initiated.
+ */
+ private boolean readyToStop;
+
+ /**
+ * Map to save information about the client such as its login ID. The
+ * initial size of the map is small since it is not expected that concrete
+ * servers will want to store many different types of information about each
+ * client. Used by the setInfo and getInfo methods.
+ */
+ private HashMap savedInfo = new HashMap(10);
+
+ // CONSTRUCTORS *****************************************************
+
+ /**
+ * Constructs a new connection to a client.
+ *
+ * @param group
+ * the thread groupSystem.out.println("Client at "+ client +
+ * "connected"); that contains the connections.
+ * @param clientSocket
+ * contains the client's socket.
+ * @param server
+ * a reference to the server that created this instance
+ * @exception IOException
+ * if an I/O error occur when creating the connection.
+ */
+ ConnectionToClient(ThreadGroup group, Socket clientSocket, AbstractServer server) throws IOException {
+ super(group, (Runnable) null);
+ // Initialize variables
+ this.clientSocket = clientSocket;
+ this.server = server;
+
+ clientSocket.setSoTimeout(0); // make sure timeout is infinite
+
+ // Initialize the objects streams
+ try {
+ input = new ObjectInputStream(clientSocket.getInputStream());
+ output = new ObjectOutputStream(clientSocket.getOutputStream());
+ } catch (IOException ex) {
+ try {
+ closeAll();
+ } catch (Exception exc) {
+ }
+
+ throw ex; // Rethrow the exception.
+ }
+
+ readyToStop = false;
+ start(); // Start the thread waits for data from the socket
+ }
+
+ // INSTANCE METHODS *************************************************
+
+ /**
+ * Sends an object to the client.
+ *
+ * @param msg
+ * the message to be sent.
+ * @exception IOException
+ * if an I/O error occur when sending the message.
+ */
+ final public void sendToClient(Object msg) throws IOException {
+ if (clientSocket == null || output == null)
+ throw new SocketException("socket does not exist");
+
+ output.writeObject(msg);
+ }
+
+ /**
+ * Reset the output stream so we can use the same
+ * buffer repeatedly. This would not normally be used, but is necessary
+ * in some circumstances when Java refuses to send data that it thinks has been sent.
+ */
+ final public void forceResetAfterSend() throws IOException {
+ output.reset();
+ }
+
+ /**
+ * Closes the client. If the connection is already closed, this call has no
+ * effect.
+ *
+ * @exception IOException
+ * if an error occurs when closing the socket.
+ */
+ final public void close() throws IOException {
+ readyToStop = true; // Set the flag that tells the thread to stop
+
+ try {
+ closeAll();
+ } finally {
+ server.clientDisconnected(this);
+ }
+ }
+
+ // ACCESSING METHODS ------------------------------------------------
+
+ /**
+ * Returns the address of the client.
+ *
+ * @return the client's Internet address.
+ */
+ final public InetAddress getInetAddress() {
+ return clientSocket == null ? null : clientSocket.getInetAddress();
+ }
+
+ /**
+ * Returns a string representation of the client.
+ *
+ * @return the client's description.
+ */
+ public String toString() {
+ return clientSocket == null ? null : clientSocket.getInetAddress().getHostName() + " ("
+ + clientSocket.getInetAddress().getHostAddress() + ")";
+ }
+
+ /**
+ * Saves arbitrary information about this client. Designed to be used by
+ * concrete subclasses of AbstractServer. Based on a hash map.
+ *
+ * @param infoType
+ * identifies the type of information
+ * @param info
+ * the information itself.
+ */
+ public void setInfo(String infoType, Object info) {
+ savedInfo.put(infoType, info);
+ }
+
+ /**
+ * Returns information about the client saved using setInfo. Based on a hash
+ * map.
+ *
+ * @param infoType
+ * identifies the type of information
+ */
+ public Object getInfo(String infoType) {
+ return savedInfo.get(infoType);
+ }
+
+ // RUN METHOD -------------------------------------------------------
+
+ /**
+ * Constantly reads the client's input stream. Sends all objects that are
+ * read to the server. Not to be called.
+ */
+ final public void run() {
+ server.clientConnected(this);
+
+ // This loop reads the input stream and responds to messages
+ // from clients
+ try {
+ // The message from the client
+ Object msg;
+
+ while (!readyToStop) {
+ // This block waits until it reads a message from the client
+ // and then sends it for handling by the server
+ msg = input.readObject();
+ server.receiveMessageFromClient(msg, this);
+ }
+ } catch (Exception exception) {
+ if (!readyToStop) {
+ try {
+ closeAll();
+ } catch (Exception ex) {
+ }
+
+ server.clientException(this, exception);
+ }
+ }
+ }
+
+ // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------
+
+ /**
+ * Closes all connection to the server.
+ *
+ * @exception IOException
+ * if an I/O error occur when closing the connection.
+ */
+ private void closeAll() throws IOException {
+ try {
+ // Close the socket
+ if (clientSocket != null)
+ clientSocket.close();
+
+ // Close the output stream
+ if (output != null)
+ output.close();
+
+ // Close the input stream
+ if (input != null)
+ input.close();
+ } finally {
+ // Set the streams and the sockets to NULL no matter what
+ // Doing so allows, but does not require, any finalizers
+ // of these objects to reclaim system resources if and
+ // when they are garbage collected.
+ output = null;
+ input = null;
+ clientSocket = null;
+ }
+ }
+
+ /**
+ * This method is called by garbage collection.
+ */
+ protected void finalize() {
+ try {
+ closeAll();
+ } catch (IOException e) {
+ }
+ }
+}
+// End of ConnectionToClient class
\ No newline at end of file
diff --git a/code/simplechat1/ocsf/server/ObservableOriginatorServer.java b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java
new file mode 100644
index 0000000..4333694
--- /dev/null
+++ b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java
@@ -0,0 +1,165 @@
+// This file contains material supporting the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.server;
+
+/**
+* The
+*
+* @author Dr Robert Laganière
+* @author Dr Timothy C. Lethbridge
+* @author François Bélanger
+* @author Paul Holden
+* @version February 2001 (2.12)
+* @see ocsf.server.OriginatorMessage
+*/
+public class ObservableOriginatorServer extends ObservableServer
+{
+ // Constructor ******************************************************
+
+ /**
+ * Constructs a new server.
+ *
+ * @param port the port on which to listen.
+ */
+ public ObservableOriginatorServer(int port)
+ {
+ super(port);
+ }
+
+ // Instance methods ************************************************
+
+ /**
+ * This method is used to handle messages coming from the client.
+ * Observers are notfied by receiveing an instance of OriginatorMessage
+ * that contains both the message received and a reference to the
+ * client who sent the message.
+ *
+ * @param message The message received from the client.
+ * @param client The connection to the client.
+ */
+ protected synchronized void handleMessageFromClient
+ (Object message, ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(client, message));
+ }
+
+ /**
+ * Method called each time a new client connection is
+ * accepted. It notifies observers by sending an
+ * ConnectionToClient .
+ */
+ private ThreadGroup clientThreadGroup;
+
+ /**
+ * Indicates if the listening thread is ready to stop. Set to
+ * false by default.
+ */
+ private boolean readyToStop = false;
+
+
+// CONSTRUCTOR ******************************************************
+
+ /**
+ * Constructs a new server.
+ *
+ * @param port the port number on which to listen.
+ */
+ public AbstractServer(int port)
+ {
+ this.port = port;
+
+ this.clientThreadGroup =
+ new ThreadGroup("ConnectionToClient threads")
+ {
+ // All uncaught exceptions in connection threads will
+ // be sent to the clientException callback method.
+ public void uncaughtException(
+ Thread thread, Throwable exception)
+ {
+ clientException((ConnectionToClient)thread, exception);
+ }
+ };
+ }
+
+
+// INSTANCE METHODS *************************************************
+
+ /**
+ * Begins the thread that waits for new clients.
+ * If the server is already in listening mode, this
+ * call has no effect.
+ *
+ * @exception IOException if an I/O error occurs
+ * when creating the server socket.
+ */
+ final public void listen() throws IOException
+ {
+ if (!isListening())
+ {
+ if (serverSocket == null)
+ {
+ serverSocket = new ServerSocket(getPort(), backlog);
+ }
+
+ serverSocket.setSoTimeout(timeout);
+ readyToStop = false;
+ connectionListener = new Thread(this);
+ connectionListener.start();
+ }
+ }
+
+ /**
+ * Causes the server to stop accepting new connections.
+ */
+ final public void stopListening()
+ {
+ readyToStop = true;
+ }
+
+ /**
+ * Closes the server socket and the connections with all clients.
+ * Any exception thrown while closing a client is ignored.
+ * If one wishes to catch these exceptions, then clients
+ * should be individually closed before calling this method.
+ * The method also stops listening if this thread is running.
+ * If the server is already closed, this
+ * call has no effect.
+ *
+ * @exception IOException if an I/O error occurs while
+ * closing the server socket.
+ */
+ final synchronized public void close() throws IOException
+ {
+ if (serverSocket == null)
+ return;
+ stopListening();
+ try
+ {
+ serverSocket.close();
+ }
+ finally
+ {
+ // Close the client sockets of the already connected clients
+ Thread[] clientThreadList = getClientConnections();
+ for (int i=0; iConnectionToClient instances.
+ */
+ synchronized final public Thread[] getClientConnections()
+ {
+ Thread[] clientThreadList = new
+ Thread[clientThreadGroup.activeCount()];
+
+ clientThreadGroup.enumerate(clientThreadList);
+
+ return clientThreadList;
+ }
+
+ /**
+ * Counts the number of clients currently connected.
+ *
+ * @return the number of clients currently connected.
+ */
+ final public int getNumberOfClients()
+ {
+ return clientThreadGroup.activeCount();
+ }
+
+ /**
+ * Returns the port number.
+ *
+ * @return the port number.
+ */
+ final public int getPort()
+ {
+ return port;
+ }
+
+ /**
+ * Sets the port number for the next connection.
+ * The server must be closed and restarted for the port
+ * change to be in effect.
+ *
+ * @param port the port number.
+ */
+ final public void setPort(int port)
+ {
+ this.port = port;
+ }
+
+ /**
+ * Sets the timeout time when accepting connections.
+ * The default is half a second. This means that stopping the
+ * server may take up to timeout duration to actually stop.
+ * The server must be stopped and restarted for the timeout
+ * change to be effective.
+ *
+ * @param timeout the timeout time in ms.
+ */
+ final public void setTimeout(int timeout)
+ {
+ this.timeout = timeout;
+ }
+
+ /**
+ * Sets the maximum number of waiting connections accepted by the
+ * operating system. The default is 20.
+ * The server must be closed and restarted for the backlog
+ * change to be in effect.
+ *
+ * @param backlog the maximum number of connections.
+ */
+ final public void setBacklog(int backlog)
+ {
+ this.backlog = backlog;
+ }
+
+// RUN METHOD -------------------------------------------------------
+
+ /**
+ * Runs the listening thread that allows clients to connect.
+ * Not to be called.
+ */
+ final public void run()
+ {
+ // call the hook method to notify that the server is starting
+ serverStarted();
+
+ try
+ {
+ // Repeatedly waits for a new client connection, accepts it, and
+ // starts a new thread to handle data exchange.
+ while(!readyToStop)
+ {
+ try
+ {
+ // Wait here for new connection attempts, or a timeout
+ Socket clientSocket = serverSocket.accept();
+
+ // When a client is accepted, create a thread to handle
+ // the data exchange, then add it to thread group
+
+ synchronized(this)
+ {
+ ConnectionToClient c = new ConnectionToClient(
+ this.clientThreadGroup, clientSocket, this);
+ }
+ }
+ catch (InterruptedIOException exception)
+ {
+ // This will be thrown when a timeout occurs.
+ // The server will continue to listen if not ready to stop.
+ }
+ }
+
+ // call the hook method to notify that the server has stopped
+ serverStopped();
+ }
+ catch (IOException exception)
+ {
+ if (!readyToStop)
+ {
+ // Closing the socket must have thrown a SocketException
+ listeningException(exception);
+ }
+ else
+ {
+ serverStopped();
+ }
+ }
+ finally
+ {
+ readyToStop = true;
+ connectionListener = null;
+ }
+ }
+
+
+// METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES ---------
+
+ /**
+ * Hook method called each time a new client connection is
+ * accepted. The default implementation does nothing.
+ * @param client the connection connected to the client.
+ */
+ protected void clientConnected(ConnectionToClient client) {}
+
+ /**
+ * Hook method called each time a client disconnects.
+ * The default implementation does nothing. The method
+ * may be overridden by subclasses but should remains synchronized.
+ *
+ * @param client the connection with the client.
+ */
+ synchronized protected void clientDisconnected(
+ ConnectionToClient client) {}
+
+ /**
+ * Hook method called each time an exception is thrown in a
+ * ConnectionToClient thread.
+ * The method may be overridden by subclasses but should remains
+ * synchronized.
+ *
+ * @param client the client that raised the exception.
+ * @param Throwable the exception thrown.
+ */
+ synchronized protected void clientException(
+ ConnectionToClient client, Throwable exception) {}
+
+ /**
+ * Hook method called when the server stops accepting
+ * connections because an exception has been raised.
+ * The default implementation does nothing.
+ * This method may be overriden by subclasses.
+ *
+ * @param exception the exception raised.
+ */
+ protected void listeningException(Throwable exception) {}
+
+ /**
+ * Hook method called when the server starts listening for
+ * connections. The default implementation does nothing.
+ * The method may be overridden by subclasses.
+ */
+ protected void serverStarted() {}
+
+ /**
+ * Hook method called when the server stops accepting
+ * connections. The default implementation
+ * does nothing. This method may be overriden by subclasses.
+ */
+ protected void serverStopped() {}
+
+ /**
+ * Hook method called when the server is clased.
+ * The default implementation does nothing. This method may be
+ * overriden by subclasses. When the server is closed while still
+ * listening, serverStopped() will also be called.
+ */
+ protected void serverClosed() {}
+
+ /**
+ * Handles a command sent from one client to the server.
+ * This MUST be implemented by subclasses, who should respond to
+ * messages.
+ * This method is called by a synchronized method so it is also
+ * implcitly synchronized.
+ *
+ * @param msg the message sent.
+ * @param client the connection connected to the client that
+ * sent the message.
+ */
+ protected abstract void handleMessageFromClient(
+ Object msg, ConnectionToClient client);
+
+
+// METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------
+
+ /**
+ * Receives a command sent from the client to the server.
+ * Called by the run method of ConnectionToClient
+ * instances that are watching for messages coming from the server
+ * This method is synchronized to ensure that whatever effects it has
+ * do not conflict with work being done by other threads. The method
+ * simply calls the handleMessageFromClient slot method.
+ *
+ * @param msg the message sent.
+ * @param client the connection connected to the client that
+ * sent the message.
+ */
+ final synchronized void receiveMessageFromClient(
+ Object msg, ConnectionToClient client)
+ {
+ this.handleMessageFromClient(msg, client);
+ }
+}
+// End of AbstractServer Class
diff --git a/code/simplechat1/ocsf/server/AdaptableServer.java b/code/simplechat1/ocsf/server/AdaptableServer.java
new file mode 100644
index 0000000..24462d4
--- /dev/null
+++ b/code/simplechat1/ocsf/server/AdaptableServer.java
@@ -0,0 +1,129 @@
+// This file contains material supporting section 6.13 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.server;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+* The AdaptableServer is an adapter class
+* that extends the AbstractServer class in place of
+* the AbstractObservableServer . ObservableOriginatorServer is a subclass
+* of ObservableServer that sends
+* OriginatorMessage instances to its observers.
+* This class should be used when the observers need to know
+* the orginator of the messages received. The originator
+* is null when the message sent concerns the server.
+*
+* Project Name: OCSF (Object Client-Server Framework) OriginatorMessage instance
+ * containing a reference to that client and
+ * the message defined by the static variable CLIENT_CONNECTED.
+ *
+ * @param client the connection connected to the client.
+ */
+ protected synchronized void clientConnected(ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(client, CLIENT_CONNECTED));
+ }
+
+ /**
+ * Method called each time a client connection is
+ * disconnected. It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing a reference to that client and
+ * the message defined by the static variable CLIENT_DISCONNECTED.
+ *
+ * @param client the connection connected to the client.
+ */
+ synchronized protected void clientDisconnected(ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(client, CLIENT_DISCONNECTED));
+ }
+
+
+ /**
+ * Method called each time an exception is raised
+ * by a client connection.
+ * It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing a reference to that client and
+ * the message defined by the static variable CLIENT_EXCEPTION
+ * to which is appended the exception message.
+ *
+ * @param client the client that raised the exception.
+ * @param Throwable the exception thrown.
+ */
+ synchronized protected void clientException(
+ ConnectionToClient client, Throwable exception)
+ {
+ setChanged();
+ notifyObservers(
+ new OriginatorMessage(client,
+ CLIENT_EXCEPTION + exception.getMessage()));
+ }
+
+ /**
+ * Method called each time an exception is raised
+ * while listening.
+ * It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing the message defined by the static variable LISTENING_EXCEPTION
+ * to which is appended the exception message.
+ * The originator is set to null.
+ *
+ * @param exception the exception raised.
+ */
+ protected synchronized void listeningException(Throwable exception)
+ {
+ setChanged();
+ notifyObservers(
+ new OriginatorMessage(null,
+ LISTENING_EXCEPTION + exception.getMessage()));
+ }
+
+ /**
+ * Method called each time the server is started.
+ * It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing the message defined by the static variable SERVER_STARTED.
+ * The originator is set to null.
+ */
+ protected synchronized void serverStarted()
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(null, SERVER_STARTED));
+ }
+
+ /**
+ * Method called each time the server is stopped.
+ * It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing the message defined by the static variable SERVER_STOPPED.
+ * The originator is set to null.
+ */
+ synchronized protected void serverStopped()
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(null, SERVER_STOPPED));
+ }
+
+ /**
+ * Method called each time the server is closed.
+ * It notifies observers by sending an
+ * OriginatorMessage instance
+ * containing the message defined by the static variable SERVER_CLOSED.
+ * The originator is set to null.
+ */
+ synchronized protected void serverClosed()
+ {
+ setChanged();
+ notifyObservers(new OriginatorMessage(null, SERVER_CLOSED));
+ }
+}
diff --git a/code/simplechat1/ocsf/server/ObservableServer.java b/code/simplechat1/ocsf/server/ObservableServer.java
new file mode 100644
index 0000000..5b16752
--- /dev/null
+++ b/code/simplechat1/ocsf/server/ObservableServer.java
@@ -0,0 +1,309 @@
+// This file contains material supporting section 6.13 of the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.server;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+
+/**
+ * This class acts as a subclass of AbstractServer
+ * and is also an Observable class.
+ * This means that when a message is received, all observers
+ * are notified.
+ *
+ * @author François Bélange
+ * @author Dr Timothy C. Lethbridge
+ * @author Dr Robert Laganière
+ * @version August 2000
+ */
+
+public class ObservableServer extends Observable
+{
+ // Class variables ************************************************
+
+ /**
+ * The string sent to the observers when a client has connected.
+ */
+ public static final String CLIENT_CONNECTED= "#OS:Client connected.";
+
+ /**
+ * The string sent to the observers when a client has disconnected.
+ */
+ public static final String CLIENT_DISCONNECTED= "#OS:Client disconnected.";
+
+ /**
+ * The string sent to the observers when an exception occurred with a client.
+ * The error message of that exception will be appended to this string.
+ */
+ public static final String CLIENT_EXCEPTION= "#OS:Client exception.";
+
+ /**
+ * The string sent to the observers when a listening exception occurred.
+ * The error message of that exception will be appended to this string.
+ */
+ public static final String LISTENING_EXCEPTION= "#OS:Listening exception.";
+
+ /**
+ * The string sent to the observers when the server has closed.
+ */
+ public static final String SERVER_CLOSED= "#OS:Server closed.";
+
+ /**
+ * The string sent to the observers when the server has started.
+ */
+ public static final String SERVER_STARTED= "#OS:Server started.";
+
+ /**
+ * The string sent to the observers when the server has stopped.
+ */
+ public static final String SERVER_STOPPED= "#OS:Server stopped.";
+
+
+ //Instance variables **********************************************
+
+ /**
+ * The service used to simulate multiple class inheritance.
+ */
+ private AdaptableServer service;
+
+
+ //Constructor *****************************************************
+
+ /**
+ * Constructs a new server.
+ *
+ * @param port the port on which to listen.
+ */
+ public ObservableServer(int port)
+ {
+ service = new AdaptableServer(port, this);
+ }
+
+ //Instance methods ************************************************
+
+ /**
+ * Begins the thread that waits for new clients
+ */
+ final public void listen() throws IOException
+ {
+ service.listen();
+ }
+
+ /**
+ * Causes the server to stop accepting new connections.
+ */
+ final public void stopListening()
+ {
+ service.stopListening();
+ }
+
+ /**
+ * Closes the server's connections with all clients.
+ */
+ final public void close() throws IOException
+ {
+ service.close();
+ }
+
+ /**
+ * Sends a message to every client connected to the server.
+ *
+ * @param msg The message to be sent
+ */
+ public void sendToAllClients(Object msg)
+ {
+ service.sendToAllClients(msg);
+ }
+
+// ACCESSING METHODS ------------------------------------------------
+
+ /**
+ * Used to find out if the server is accepting new clients.
+ */
+ final public boolean isListening()
+ {
+ return service.isListening();
+ }
+
+ /**
+ * Returns an array of containing the existing
+ * client connections. This can be used by
+ * concrete subclasses to implement messages that do something with
+ * each connection (e.g. kill it, send a message to it etc.)
+ *
+ * @return an array of Thread containing
+ * ConnectionToClient instances.
+ */
+ final public Thread[] getClientConnections()
+ {
+ return service.getClientConnections();
+ }
+
+ /**
+ * @return the number of clients currently connected.
+ */
+ final public int getNumberOfClients()
+ {
+ return service.getNumberOfClients();
+ }
+
+ /**
+ * @return the port number.
+ */
+ final public int getPort()
+ {
+ return service.getPort();
+ }
+
+ /**
+ * Sets the port number for the next connection.
+ * Only has effect if the server is not currently listening.
+ *
+ * @param port the port number.
+ */
+ final public void setPort(int port)
+ {
+ service.setPort(port);
+ }
+
+ /**
+ * Sets the timeout time when accepting connection.
+ * The default is half a second.
+ * The server must be stopped and restarted for the timeout
+ * change be in effect.
+ *
+ * @param timeout the timeout time in ms.
+ */
+ final public void setTimeout(int timeout)
+ {
+ service.setTimeout(timeout);
+ }
+
+ /**
+ * Sets the maximum number of
+ * waiting connections accepted by the operating system.
+ * The default is 20.
+ * The server must be closed and restart for the backlog
+ * change be in effect.
+ *
+ * @param backlog the maximum number of connections.
+ */
+ final public void setBacklog(int backlog)
+ {
+ service.setBacklog(backlog);
+ }
+
+ /**
+ * Hook method called each time a new client connection is
+ * accepted. The method may be overridden by subclasses.
+ *
+ * @param client the connection connected to the client.
+ */
+ protected synchronized void clientConnected(ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(CLIENT_CONNECTED);
+ }
+
+ /**
+ * Hook method called each time a client disconnects.
+ * The method may be overridden by subclasses.
+ *
+ * @param client the connection with the client.
+ */
+ protected synchronized void clientDisconnected(ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(CLIENT_DISCONNECTED);
+ }
+
+ /**
+ * Hook method called each time an exception
+ * is raised in a client thread.
+ * This implementation simply closes the
+ * client connection, ignoring any exception.
+ * The method may be overridden by subclasses.
+ *
+ * @param client the client that raised the exception.
+ * @param exception the exception raised.
+ */
+ protected synchronized void clientException(ConnectionToClient client,
+ Throwable exception)
+ {
+ setChanged();
+ notifyObservers(CLIENT_EXCEPTION);
+ try
+ {
+ client.close();
+ }
+ catch (Exception e) {}
+ }
+
+ /**
+ * This method is called when the server stops accepting
+ * connections because an exception has been raised.
+ * This implementation
+ * simply calls stopListening.
+ * This method may be overriden by subclasses.
+ *
+ * @param exception the exception raised.
+ */
+ protected synchronized void listeningException(Throwable exception)
+ {
+ setChanged();
+ notifyObservers(LISTENING_EXCEPTION);
+ stopListening();
+ }
+
+ /**
+ * This method is called when the server stops accepting
+ * connections for any reason. This method may be overriden by
+ * subclasses.
+ */
+ synchronized protected void serverStopped()
+ {
+ setChanged();
+ notifyObservers(SERVER_STOPPED);
+ }
+
+ /**
+ * This method is called when the server is closed.
+ * This method may be overriden by subclasses.
+ */
+ synchronized protected void serverClosed()
+ {
+ setChanged();
+ notifyObservers(SERVER_CLOSED);
+ }
+
+ /**
+ * This method is called when the server starts listening for
+ * connections. The method may be overridden by subclasses.
+ */
+ protected synchronized void serverStarted()
+ {
+ setChanged();
+ notifyObservers(SERVER_STARTED);
+ }
+
+ /**
+ * This method is used to handle messages coming from the client.
+ * Observers are notfied by receiveing the transmitted message.
+ * Note that, in this implementation, the information concerning
+ * the client that sent the message is lost.
+ * It can be overriden, but is still expected to call notifyObservers().
+ *
+ * @param message The message received from the client.
+ * @param client The connection to the client.
+ * @see ocsf.server.ObservableOriginatorServer
+ */
+ protected synchronized void handleMessageFromClient
+ (Object message, ConnectionToClient client)
+ {
+ setChanged();
+ notifyObservers(message);
+ }
+}
diff --git a/code/simplechat1/ocsf/server/OriginatorMessage.java b/code/simplechat1/ocsf/server/OriginatorMessage.java
new file mode 100644
index 0000000..a25d2b4
--- /dev/null
+++ b/code/simplechat1/ocsf/server/OriginatorMessage.java
@@ -0,0 +1,61 @@
+// This file contains material supporting the textbook:
+// "Object Oriented Software Engineering" and is issued under the open-source
+// license found at www.lloseng.com
+
+package ocsf.server;
+
+/**
+ * A message class used by the Observable layer of the OCSF in order to conserve
+ * information about the originator of a message.
+ *
+ * @author Dr. Robert Laganière
+ * @version July 2001
+ */
+public class OriginatorMessage
+{
+ /**
+ * The connection that originated the message
+ */
+ private ConnectionToClient originator;
+
+ /**
+ * The message.
+ */
+ private Object message;
+
+// Constructor ***************************************************************
+
+ /**
+ * Constructs an instance of an OriginatorMessage
+ *
+ * @param originator The client who created this message
+ * @param message The contents of the message
+ */
+ public OriginatorMessage(ConnectionToClient originator, Object message)
+ {
+ this.originator = originator;
+ this.message = message;
+ }
+
+// Accessor methods *********************************************************
+
+ /**
+ * Returns the originating connection.
+ *
+ * @return The connection from which the message originated.
+ */
+ public ConnectionToClient getOriginator()
+ {
+ return originator;
+ }
+
+ /**
+ * Returns the message's contents.
+ *
+ * @return The content of the message.
+ */
+ public Object getMessage()
+ {
+ return message;
+ }
+}