Second Summer 2004


The Net Worth of an Object-Oriented Pattern: Practical Implications of the Java RMI
The purpose of this document is to be a tutorial.

Part One. Deriving the pattern.

There will be a server, implemented as follows:

class ServerImplementation {

}
There will be many clients, implemented as follows:
class ClientImplementation {

}
The server has a business card:
public interface Server {
  
}
And it implements it:
class ServerImplementation implements Server {

}
The client also has a business card
public interface Client {
  
}
And, like the server, it makes a promise to fulfil it:
class ClientImplementation implements Client {

}
The server keeps track of clients:
class ServerImplementation implements Server {
    Client[] clients = new Client[100]; 
    int index = -1; 
}
(The index of the most recently allocated cell is -1 if the array is empty).

Notice that

What else does the server do?

It allows clients to register with it, whatever that means.

public interface Server {
    public void register(Client client);   
}
Although that in this case amounts to:
class ServerImplementation implements Server {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
}
So we see that, apparently, a client: This changes the client business card a little (that the server knows):
public interface Client {
    public void setID(Integer index);     
    public void register(Integer index, Client client); 
}
Let's see how these promises can be fulfiled.

First off, every client will have an own numeric id:

class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
}
Secondly, the world of peers initially registered with the server is replicated in each client:
class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
}
Let's go back to the server.

How does it get started and what does it do?

The server has no constructor.

The server is implemented as a thread.

In its run() method the server constantly broadcasts to clients.

class ServerImplementation implements Server, Runnable {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
}
Broadcasting amounts to the following:
class ServerImplementation implements Server, Runnable {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized public void broadcast() {
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(false)); 
	for (int i = 0, check = 0; i <= this.index; i++) 
	    check += clients[i].getBalance(); 
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(true)); 
    }
}
During broadcasting:

Here's how we accomplish these steps.

Setting the availability and returning the current balance are advertised by the clients:

public interface Client {
    public void setID(Integer index);     
    public void register(Integer index, Client client); 
    public void setAvailable(Boolean availability); 
    public int getBalance();
}
The implementation of these two functions is immediate:
class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
}
What else does the server do?

Broadcasting is a just little bit more complicated but other than that, that's it.

class ServerImplementation implements Server, Runnable {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized public void broadcast() {
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(false)); 
        String report = ""; 
	String calculation = ""; 
        int check = 0;  
	for (int i = 0; i <= this.index; i++) { 
	    report += clients[i].report() + "\n"; 
	    calculation += "(" + clients[i].getBalance() + ")..";
	    check += clients[i].getBalance(); 
	}
	System.out.print("Server report indicates: \n" + report); 
	System.out.println(calculation + " ---> " + check);  
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(true)); 
    }
}
Clients need to provide the added level of functionality:
public interface Client {
    public void setID(Integer index);     
    public void register(Integer index, Client client); 
    public void setAvailable(Boolean availability); 
    public int getBalance(); 
    public String report(); 
}
Implementation for this extra feature is immediate, as well:
class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
}
How does one start the server?

The server provides a method for that:

class ServerImplementation implements Server, Runnable {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized public void broadcast() {
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(false)); 
        String report = ""; 
	String calculation = ""; 
        int check = 0; 
	for (int i = 0; i <= this.index; i++) { 
	    report += clients[i].report() + "\n"; 
	    calculation += "(" + clients[i].getBalance() + ").."; 
	    check += clients[i].getBalance(); 
	}
	System.out.print("Server report indicates: \n" + report); 
	System.out.println(calculation + " ---> " + check); 
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(true)); 
    }
    public void startAsLocalServer() {
	new Thread(this).start(); 
    }
}
If we were to try this program out we'd do something like this:
class LocalSetup {
    public static void main(String[] args) {

    }
}
A server needs to be created, then started:
class LocalSetup {
    public static void main(String[] args) {
	ServerImplementation server = new ServerImplementation(); 
	server.startAsLocalServer();
    }
}
We need some clients, too.

But how does one create a client?

And, if started, what do clients do?

class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
}
So we can create a few clients now:
class LocalSetup {
    public static void main(String[] args) {
	ServerImplementation server = new ServerImplementation(); 
	server.startAsLocalServer(); 
        for (int i = 0; i < 6; i++) {
	    ClientImplementation dealer = new ClientImplementation("Dealer_" + i); 
	    dealer.startAsClientOf(server); 
	}
    }
}
In describing how we start the clients we acknowledge for the first time the distributed nature of our goal.

class ClientImplementation implements Client {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
}
This addition however has significant ramifications.

First the server needs to match the type:

public interface Server extends java.rmi.Remote {
    public void register(Client client);   
}
In addition to that clients need to be described in their active behaviour:
class ClientImplementation implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
	    try {
		Thread.sleep((int) Math.random() * 1000 + 1000); 
		if (this.available.booleanValue())
		    this.initiateTransfer(); 
	    } catch (Exception e) { } 
	}
    } 
}
So that's what clients typically do: wait, then initiate a transfer.

Initiating a transfer has the following description:

class ClientImplementation implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
	    try {
		Thread.sleep((int) Math.random() * 1000 + 1000); 
		if (this.available.booleanValue())
		    this.initiateTransfer(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized private void initiateTransfer() {
	if (this.index > 0) {
	    int chosen = (int) (Math.random() * (this.index + 1)); 
	    if (chosen == this.id // always deal with someone else
                || peer[chosen] == null) return; 
            this.balance += peer[chosen].process(
			                   new Transaction(
					     this.name, 
					     this.id, 
					     (int)(Math.random() * 10 + 1), 
					     Math.random() > 0.5 ? "sent" 
					                         : "requested"));
	}
    }
}
A description of process and of Transaction follows:
public class Transaction {
    String initiatorName; 
    int initiatorID;
    int amount;
    String direction; 
    public Transaction(String name, int id, int amount, String direction) {
	this.initiatorName = name; 
	this.initiatorID = id; 
	this.amount = amount; 
	this.direction = direction; 
    }
}
Transactions are passive, but by the end of this tutorial they will become visitors.

Processing is not very complicated:

class ClientImplementation implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
	    try {
		Thread.sleep((int) Math.random() * 1000 + 1000); 
		if (this.available.booleanValue())
		    this.initiateTransfer(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized private void initiateTransfer() {
	if (this.index > 0) {
	    int chosen = (int) (Math.random() * (this.index + 1)); 
	    if (chosen == this.id // always deal with someone else
                || peer[chosen] == null) return; 
            this.balance += peer[chosen].process(
			                   new Transaction(
					     this.name, 
					     this.id, 
					     (int)(Math.random() * 10 + 1), 
					     Math.random() > 0.5 ? "sent" 
					                         : "requested"));
	}
    }
    synchronized public int process(Transaction transaction) {
	if (this.available.booleanValue()) 
	    if (transaction.direction.equals("sent")) {
		this.balance += transaction.amount;
                return - (transaction.amount); 
	    } else {
		this.balance -= transaction.amount; 
		return (transaction.amount); 
	    }
	else return 0; // object unavailable: method idempotent 
    }
}
And process must be added to Client's business card:
public interface Client {
    public void setID(Integer index);     
    public void register(Integer index, Client client); 
    public void setAvailable(Boolean availability); 
    public int getBalance(); 
    public String report(); 
    public int process(Transaction transaction); 
}
So our application has been developed now:
burrowww.cs.indiana.edu% javac *.java
burrowww.cs.indiana.edu% java LocalSetup
Server report indicates: 
Dealer_0: -7
Dealer_1: 15
Dealer_2: -12
Dealer_3: -3
Dealer_4: 13
Dealer_5: -6
(-7)..(15)..(-12)..(-3)..(13)..(-6).. ---> 0
Server report indicates: 
Dealer_0: -16
Dealer_1: 24
Dealer_2: -24
Dealer_3: -26
Dealer_4: 15
Dealer_5: 27
(-16)..(24)..(-24)..(-26)..(15)..(27).. ---> 0
Server report indicates: 
Dealer_0: -23
Dealer_1: 44
Dealer_2: -16
Dealer_3: -23
Dealer_4: 5
Dealer_5: 13
(-23)..(44)..(-16)..(-23)..(5)..(13).. ---> 0
Server report indicates: 
Dealer_0: -39
Dealer_1: 28
Dealer_2: -6
Dealer_3: -14
Dealer_4: 9
Dealer_5: 22
(-39)..(28)..(-6)..(-14)..(9)..(22).. ---> 0
^Cburrowww.cs.indiana.edu% 
Is it possible to have avoided using java.rmi.Remote at all?

Perhaps so, but this is a bit more general, since we could have more than one kind of server.

That is, we could easily rewrite startAsClientOf as follows:

public void startAsClientOf(java.rmi.Remote peer) {
    if (peer instanceof Server) { 
        Server server = (Server)peer;
        server.register(this);
        this.server = server;
        new Thread(this).start(); 
    } else { 
        // throw an exception or something... 
    }
}
Now we will show how we distribute the program without touching the application logic developed thus far.

Let's introduce this abstraction:

public abstract class NetworkPeer implements java.rmi.Remote {
    public void exportMethods() throws java.rmi.RemoteException {
        java.rmi.server.UnicastRemoteObject.exportObject(this);
    }
    public java.rmi.Remote locatePeer(String peerHost,
                                      int peerPort,
                                      String peerName) throws Exception {
        return java.rmi.Naming.lookup("rmi://" + peerHost + ":" + peerPort + "/" + peerName);
    }
    public void startAsNetworkClientOf(String peerHost,
                                       int peerPort,
                                       String peerName) throws Exception {
        this.exportMethods();
        java.rmi.Remote peer = this.locatePeer(peerHost, peerPort, peerName);
        this.startAsClientOf(peer); // see below ...
    }
    public abstract void startAsClientOf(java.rmi.Remote peer) throws java.rmi.RemoteException;
    public void startAsNetworkServer(String name, int port) {
        System.setSecurityManager(new java.rmi.RMISecurityManager());
        try {
            this.exportMethods();
            java.rmi.registry.Registry registry = java.rmi.registry.LocateRegistry.createRegistry(port);
            registry.bind(name, this);
            this.startAsLocalServer(); // see below ...
            System.out.println("Server is ready ... ");
        } catch (Exception e) {
            System.out.println("Server error: " + e + " ... ");
        }
    }
    public abstract void startAsLocalServer(); // perhaps a better name would be startAsServer...
}
This abstraction encapsulates and provides everything else that was needed.

First, for the server:

class ServerImplementation extends NetworkPeer implements Server, Runnable {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) {
        clients[++this.index] = client;
        client.setID(new Integer(this.index)); 
        for (int i = 0; i < this.index; i++) {
            clients[i].register(new Integer(this.index), client); 
        }
    }
    public void run() {
        while (true) {
            try {
                Thread.sleep((int)(Math.random() * 5000 + 5000)); 
                this.broadcast(); 
            } catch (Exception e) { } 
        }
    }
    synchronized public void broadcast() {
        for (int i = 0; i <= this.index; i++) 
            clients[i].setAvailable(new Boolean(false)); 
        String report = ""; 
        String calculation = ""; 
        int check = 0; 
        for (int i = 0; i <= this.index; i++) { 
            report += clients[i].report() + "\n"; 
            calculation += "(" + clients[i].getBalance() + ").."; 
            check += clients[i].getBalance(); 
        }
        System.out.print("Server report indicates: \n" + report); 
        System.out.println(calculation + " ---> " + check); 
        for (int i = 0; i <= this.index; i++) 
            clients[i].setAvailable(new Boolean(true)); 
    }
    public void startAsLocalServer() {
        new Thread(this).start(); 
    }
    public static void main(String[] args) {
        String 
            portNumber = args[0],
            ownName = args[1]; 
        ServerImplementation here = new ServerImplementation(); 
        here.startAsNetworkServer(ownName, 
                                  Integer.parseInt(portNumber)); // startAsLocalServer implicitly called ... 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
        // not needed, shows why a server is a client that has a public address (home, host) 
    }
}
The client implementation also includes the ability to start a client as a standalone application:
class ClientImplementation extends NetworkPeer implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
        this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
        this.index = index.intValue(); 
        this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
        this.available = availability; 
    }
    int balance; 
    public int getBalance() {
        return this.balance; 
    }
    String name; 
    public String report() {
        return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
        this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
        Server server = (Server)peer;
        server.register(this); 
        this.server = server; 
        new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
            try {
                Thread.sleep((int) Math.random() * 1000 + 1000); 
                if (this.available.booleanValue())
                    this.initiateTransfer(); 
            } catch (Exception e) { } 
        }
    }
    synchronized private void initiateTransfer() {
        if (this.index > 0) {
            int chosen = (int) (Math.random() * (this.index + 1)); 
            if (chosen == this.id // always deal with someone else
                || peer[chosen] == null) return; 
            this.balance += peer[chosen].process(
                                           new Transaction(
                                             this.name, 
                                             this.id, 
                                             (int)(Math.random() * 10 + 1), 
                                             Math.random() > 0.5 ? "sent" 
                                                                 : "requested"));
        }
    }
    synchronized public int process(Transaction transaction) {
        if (this.available.booleanValue()) 
            if (transaction.direction.equals("sent")) {
                this.balance += transaction.amount;
                return - (transaction.amount); 
            } else {
                this.balance -= transaction.amount; 
                return (transaction.amount); 
            }
        else return 0; // object unavailable: method idempotent 
    }
    public static void main(String[] args) throws Exception {
        String
            ownName = args[0],
            serverHostName = args[1], 
            serverPortNumber = args[2], 
            serverName = args[3];

        ClientImplementation client = new ClientImplementation(ownName); 
        client.startAsNetworkClientOf(serverHostName, 
                                      Integer.parseInt(serverPortNumber), 
                                      serverName); // startAsClientOf(...) will be called implicitly ... 
    }
    public void startAsLocalServer() {
        // not needed, a client is an anonymous figure, a guest without a permanent address ...
    }
}
And there's one last change we need to make:
public class Transaction implements java.io.Serializable {
    String initiatorName; 
    int initiatorID;
    int amount;
    String direction; 
    public Transaction(String name, int id, int amount, String direction) {
        this.initiatorName = name; 
        this.initiatorID = id; 
        this.amount = amount; 
        this.direction = direction; 
    }
}
But being able to perform as a network peer requires additional decoration:
burrowww.cs.indiana.edu% pwd
/nfs/paca/san/r1a0l1/dgerman/pasadena
burrowww.cs.indiana.edu% ls -ld * 
-rw-r--r--  1 dgerman faculty  290 Jul  2 14:26 Client.java
-rw-r--r--  1 dgerman faculty 2666 Jul  3 01:15 ClientImplementation.java
-rw-r--r--  1 dgerman faculty  321 Jul  2 13:45 LocalSetup.java
-rw-r--r--  1 dgerman faculty 1378 Jul  3 00:13 NetworkPeer.java
-rw-r--r--  1 dgerman faculty   96 Jul  2 13:57 Server.java
-rw-r--r--  1 dgerman faculty 1683 Jul  3 01:34 ServerImplementation.java
-rw-r--r--  1 dgerman faculty  338 Jul  3 01:28 Transaction.java
burrowww.cs.indiana.edu% javac *.java
burrowww.cs.indiana.edu% rmic NetworkPeer
burrowww.cs.indiana.edu% java ServerImplementation 36091 theServer
Server error: java.rmi.StubNotFoundException: Stub class not found: ServerImplementation_Stub; nested exception is: 
        java.lang.ClassNotFoundException: ServerImplementation_Stub ... 
burrowww.cs.indiana.edu% 
That means we need to create stubs for both the ServerImplementation class (a first round of changes)
class ServerImplementation extends NetworkPeer implements Server, Runnable, java.rmi.Remote { // need stub(s) ... 
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) throws java.rmi.RemoteException { // rmic imposes this on Server ... 
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized public void broadcast() {
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(false)); 
        String report = ""; 
	String calculation = ""; 
        int check = 0; 
	for (int i = 0; i <= this.index; i++) { 
	    report += clients[i].report() + "\n"; 
	    calculation += "(" + clients[i].getBalance() + ").."; 
	    check += clients[i].getBalance(); 
	}
	System.out.print("Server report indicates: \n" + report); 
	System.out.println(calculation + " ---> " + check); 
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(true)); 
    }
    public void startAsLocalServer() {
	new Thread(this).start(); 
    }
    public static void main(String[] args) {
        String 
	    portNumber = args[0],
	    ownName = args[1]; 
	ServerImplementation here = new ServerImplementation(); 
	here.startAsNetworkServer(ownName, 
				  Integer.parseInt(portNumber)); // startAsLocalServer implicitly called ... 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
        // not needed, shows why a server is a client that has a public address (home, host) 
    }
}

public interface Server extends java.rmi.Remote {
    public void register(Client client) throws java.rmi.RemoteException;  // rmic requires it ... (Server/ClientImplementations comply) 
}

class ClientImplementation extends NetworkPeer implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) throws java.rmi.RemoteException { // because of register(...) ... 
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
	    try {
		Thread.sleep((int) Math.random() * 1000 + 1000); 
		if (this.available.booleanValue())
		    this.initiateTransfer(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized private void initiateTransfer() {
	if (this.index > 0) {
	    int chosen = (int) (Math.random() * (this.index + 1)); 
	    if (chosen == this.id // always deal with someone else
                || peer[chosen] == null) return; 
            this.balance += peer[chosen].process(
			                   new Transaction(
					     this.name, 
					     this.id, 
					     (int)(Math.random() * 10 + 1), 
					     Math.random() > 0.5 ? "sent" 
					                         : "requested"));
	}
    }
    synchronized public int process(Transaction transaction) {
	if (this.available.booleanValue()) 
	    if (transaction.direction.equals("sent")) {
		this.balance += transaction.amount;
                return - (transaction.amount); 
	    } else {
		this.balance -= transaction.amount; 
		return (transaction.amount); 
	    }
	else return 0; // object unavailable: method idempotent 
    }
    public static void main(String[] args) throws Exception {
        String
	    ownName = args[0],
	    serverHostName = args[1], 
	    serverPortNumber = args[2], 
	    serverName = args[3];

	ClientImplementation client = new ClientImplementation(ownName); 
	client.startAsNetworkClientOf(serverHostName, 
				      Integer.parseInt(serverPortNumber), 
				      serverName); // startAsClientOf(...) will be called implicitly ... 
    }
    public void startAsLocalServer() {
	// not needed, a client is an anonymous figure, a guest without a permanent address ...
    }
}

class LocalSetup {
    public static void main(String[] args) throws /*java.rmi.Remote*/Exception { // startAsClientOf(...)'s influence ... 
	ServerImplementation server = new ServerImplementation(); 
	server.startAsLocalServer(); 

        for (int i = 0; i < 6; i++) {
	    ClientImplementation dealer = new ClientImplementation("Dealer_" + i); 
	    dealer.startAsClientOf(server); 
	}

    }
}
So now we can javac everything and rmic ServerImplementation.

And we need to take care of the ClientImplementation as well:

public interface Client extends java.rmi.Remote {
    public void setID(Integer index) throws java.rmi.RemoteException;     
    public void register(Integer index, Client client) throws java.rmi.RemoteException; 
    public void setAvailable(Boolean availability) throws java.rmi.RemoteException; 
    public int getBalance() throws java.rmi.RemoteException; 
    public String report() throws java.rmi.RemoteException; 
    public int process(Transaction transaction) throws java.rmi.RemoteException; 
}

class ClientImplementation extends NetworkPeer implements Client, Runnable {
    int id; 
    synchronized public void setID(Integer index) {
	this.id = index.intValue(); 
    }
    Client[] peer = new Client[100]; 
    int index = -1; 
    synchronized public void register(Integer index, Client client) {
	this.index = index.intValue(); 
	this.peer[this.index] = client; 
    }
    Boolean available = new Boolean(true); 
    synchronized public void setAvailable(Boolean availability) {
	this.available = availability; 
    }
    int balance; 
    public int getBalance() {
	return this.balance; 
    }
    String name; 
    public String report() {
	return this.name + ": " + this.balance; 
    }
    public ClientImplementation(String name) {
	this.name = name; 
    }
    public void startAsClientOf(java.rmi.Remote peer) throws java.rmi.RemoteException {
	Server server = (Server)peer;
	server.register(this); 
	this.server = server; 
	new Thread(this).start(); 
    }
    Server server; 
    public void run() {
        while (true) {
	    try {
		Thread.sleep((int) Math.random() * 1000 + 1000); 
		if (this.available.booleanValue())
		    this.initiateTransfer(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized private void initiateTransfer() throws java.rmi.RemoteException {
	if (this.index > 0) {
	    int chosen = (int) (Math.random() * (this.index + 1)); 
	    if (chosen == this.id // always deal with someone else
                || peer[chosen] == null) return; 
            this.balance += peer[chosen].process(
			                   new Transaction(
					     this.name, 
					     this.id, 
					     (int)(Math.random() * 10 + 1), 
					     Math.random() > 0.5 ? "sent" 
					                         : "requested"));
	}
    }
    synchronized public int process(Transaction transaction) {
	if (this.available.booleanValue()) 
	    if (transaction.direction.equals("sent")) {
		this.balance += transaction.amount;
                return - (transaction.amount); 
	    } else {
		this.balance -= transaction.amount; 
		return (transaction.amount); 
	    }
	else return 0; // object unavailable: method idempotent 
    }
    public static void main(String[] args) throws Exception {
        String
	    ownName = args[0],
	    serverHostName = args[1], 
	    serverPortNumber = args[2], 
	    serverName = args[3];

	ClientImplementation client = new ClientImplementation(ownName); 
	client.startAsNetworkClientOf(serverHostName, 
				      Integer.parseInt(serverPortNumber), 
				      serverName); // startAsClientOf(...) will be called implicitly ... 
    }
    public void startAsLocalServer() {
	// not needed, a client is an anonymous figure, a guest without a permanent address ...
    }
}

class ServerImplementation extends NetworkPeer implements Server, Runnable, java.rmi.Remote {
    Client[] clients = new Client[100]; 
    int index = -1; 
    synchronized public void register(Client client) throws java.rmi.RemoteException {
	clients[++this.index] = client;
	client.setID(new Integer(this.index)); 
	for (int i = 0; i < this.index; i++) {
	    clients[i].register(new Integer(this.index), client); 
	}
    }
    public void run() {
	while (true) {
	    try {
		Thread.sleep((int)(Math.random() * 5000 + 5000)); 
		this.broadcast(); 
	    } catch (Exception e) { } 
	}
    }
    synchronized public void broadcast() throws java.rmi.RemoteException {
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(false)); 
        String report = ""; 
	String calculation = ""; 
        int check = 0; 
	for (int i = 0; i <= this.index; i++) { 
	    report += clients[i].report() + "\n"; 
	    calculation += "(" + clients[i].getBalance() + ").."; 
	    check += clients[i].getBalance(); 
	}
	System.out.print("Server report indicates: \n" + report); 
	System.out.println(calculation + " ---> " + check); 
	for (int i = 0; i <= this.index; i++) 
	    clients[i].setAvailable(new Boolean(true)); 
    }
    public void startAsLocalServer() {
	new Thread(this).start(); 
    }
    public static void main(String[] args) {
        String 
	    portNumber = args[0],
	    ownName = args[1]; 
	ServerImplementation here = new ServerImplementation(); 
	here.startAsNetworkServer(ownName, 
				  Integer.parseInt(portNumber)); // startAsLocalServer implicitly called ... 
    }
    public void startAsClientOf(java.rmi.Remote peer) {
        // not needed, shows why a server is a client that has a public address (home, host) 
    }
}
So, as we can see, the code we developed originally has not been touched, and is being used.

Now we can start the programs in distributed fashion.

On tucotuco.cs.indiana.edu we start theServer as a server on port 36091:

tucotuco.cs.indiana.edu% java ServerImplementation 36091 theServer
Server is ready ... 
Server report indicates: 
 ---> 0
Server report indicates: 
 ---> 0
Server report indicates: 
 ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 0
(0).. ---> 0
Server report indicates: 
larry: 17
michael: -17
(17)..(-17).. ---> 0
Server report indicates: 
larry: 18
michael: -18
(18)..(-18).. ---> 0
Server report indicates: 
larry: 30
michael: -30
(30)..(-30).. ---> 0
Server report indicates: 
larry: 40
michael: -40
(40)..(-40).. ---> 0
Server report indicates: 
larry: 26
michael: -26
(26)..(-26).. ---> 0
Server report indicates: 
larry: 15
michael: -15
(15)..(-15).. ---> 0
Server report indicates: 
larry: 38
michael: -51
toni: 13
(38)..(-51)..(13).. ---> 0
Server report indicates: 
larry: 40
michael: -51
toni: 11
(40)..(-51)..(11).. ---> 0
Server report indicates: 
larry: 39
michael: -54
toni: 15
(39)..(-54)..(15).. ---> 0
Server report indicates: 
larry: 41
michael: -66
toni: 25
(41)..(-66)..(25).. ---> 0
Server report indicates: 
larry: 72
michael: -111
toni: 39
(72)..(-111)..(39).. ---> 0
Server report indicates: 
larry: 82
michael: -89
toni: 7
(82)..(-89)..(7).. ---> 0
Server report indicates: 
larry: 70
michael: -85
toni: 15
(70)..(-85)..(15).. ---> 0
Server report indicates: 
larry: 74
michael: -85
toni: 11
(74)..(-85)..(11).. ---> 0
Server report indicates: 
larry: 67
michael: -71
toni: 4
(67)..(-71)..(4).. ---> 0
Server report indicates: 
larry: 69
michael: -69
toni: 12
richard: -12
(69)..(-69)..(12)..(-12).. ---> 0
Server report indicates: 
larry: 64
michael: -69
toni: 11
richard: -6
(64)..(-69)..(11)..(-6).. ---> 0
Server report indicates: 
larry: 71
michael: -62
toni: 9
richard: -18
(71)..(-62)..(9)..(-18).. ---> 0
Server report indicates: 
larry: 74
michael: -56
toni: 4
richard: -22
(74)..(-56)..(4)..(-22).. ---> 0
Server report indicates: 
larry: 81
michael: -53
toni: 3
richard: -31
(81)..(-53)..(3)..(-31).. ---> 0
Server report indicates: 
larry: 41
michael: -45
toni: 19
richard: -15
(41)..(-45)..(19)..(-15).. ---> 0
^Ctucotuco.cs.indiana.edu%
This, of course, is done after compiling everything and running rmic on the two implementations.

The server output listed above is a consequence of starting the following clients, in sequence.

On burrowww.cs.indiana.edu:

burrowww.cs.indiana.edu% java ClientImplementation larry tucotuco.cs.indiana.edu 36091 theServer
On blesmol.cs.indiana.edu:
blesmol.cs.indiana.edu% java ClientImplementation michael tucotuco.cs.indiana.edu 36091 theServer
On bobac.cs.indiana.edu:
bobac.cs.indiana.edu% java ClientImplementation toni tucotuco.cs.indiana.edu 36091 theServer
And finally, on molerat.cs.indiana.edu:
molerat.cs.indiana.edu% java ClientImplementation richard tucotuco.cs.indiana.edu 36091 theServer
And the local setup still works as before.

Now we need to show how we one could simply use this pattern from the outset.

OOP is really about distributed programming.

That's why the pattern above says nothing about (and cannot make use of) static members.

Part Two. Transactions as visitors.

We do exactly what we did earlier, changing only one thing.

But the development is without exploration: we know what we want and we simply go for it.

So there's a NetworkPeer abstraction one needs to have in mind (and start from).

It can be compiled, packaged. Let's keep moving.

There is a server (with an interface and a server implementation).

There's also a type of client (of which we have many instances).

Every client implements the client interface in a specific (same) way.

The transaction process has changed a bit (in client implementation) and the transaction object is now an agent (visitor).

That's basically it. The local set up is just for testing (although it's extremely important). So now we try things out:

frilled.cs.indiana.edu%pwd      
/nfs/grouchy/home/user2/www/classes/a348-dger/sum2004/pasadena/partTwo
frilled.cs.indiana.edu%ls -ld *
-rw-r--r--  1 dgerman faculty  506 Jul  3 23:41 Client.java
-rw-r--r--  1 dgerman faculty 2683 Jul  4 00:07 ClientImplementation.java
-rw-r--r--  1 dgerman faculty  358 Jul  4 00:01 LocalSetup.java
-rw-r--r--  1 dgerman faculty 1575 Jul  3 23:11 NetworkPeer.java
-rw-r--r--  1 dgerman faculty  128 Jul  3 23:36 Server.java
-rw-r--r--  1 dgerman faculty 1720 Jul  3 23:37 ServerImplementation.java
-rw-r--r--  1 dgerman faculty  882 Jul  4 00:06 Transaction.java
frilled.cs.indiana.edu%javac *.java
frilled.cs.indiana.edu%rmic ClientImplementation 
frilled.cs.indiana.edu%rmic ServerImplementation
frilled.cs.indiana.edu%ls -ld *
-rw-------  1 dgerman faculty  464 Jul  4 00:10 Client.class
-rw-r--r--  1 dgerman faculty  506 Jul  3 23:41 Client.java
-rw-------  1 dgerman faculty 2728 Jul  4 00:10 ClientImplementation.class
-rw-r--r--  1 dgerman faculty 2683 Jul  4 00:07 ClientImplementation.java
-rw-------  1 dgerman faculty 2968 Jul  4 00:10 ClientImplementation_Skel.class
-rw-------  1 dgerman faculty 5381 Jul  4 00:10 ClientImplementation_Stub.class
-rw-------  1 dgerman faculty  764 Jul  4 00:10 LocalSetup.class
-rw-r--r--  1 dgerman faculty  358 Jul  4 00:01 LocalSetup.java
-rw-------  1 dgerman faculty 1943 Jul  4 00:10 NetworkPeer.class
-rw-r--r--  1 dgerman faculty 1575 Jul  3 23:11 NetworkPeer.java
-rw-------  1 dgerman faculty  202 Jul  4 00:10 Server.class
-rw-r--r--  1 dgerman faculty  128 Jul  3 23:36 Server.java
-rw-------  1 dgerman faculty 2275 Jul  4 00:10 ServerImplementation.class
-rw-r--r--  1 dgerman faculty 1720 Jul  3 23:37 ServerImplementation.java
-rw-------  1 dgerman faculty 1625 Jul  4 00:10 ServerImplementation_Skel.class
-rw-------  1 dgerman faculty 2906 Jul  4 00:10 ServerImplementation_Stub.class
-rw-------  1 dgerman faculty 1254 Jul  4 00:10 Transaction.class
-rw-r--r--  1 dgerman faculty  882 Jul  4 00:06 Transaction.java
frilled.cs.indiana.edu%
Let's see the local setup in action first:
frilled.cs.indiana.edu%java LocalSetup
0 chooses 2
1 chooses 3
Visiting Dealer_3: 0 sent by 1
10 points sent
Transaction now coming through...
Visiting Dealer_2: 0 sent by 0
3 points requested
Transaction now coming through...
3 chooses 2
Visiting Dealer_2: -3 sent by 3
6 points requested
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: 3 sent by 4
9 points sent
Transaction now coming through...
5 chooses 2
Visiting Dealer_2: -9 sent by 5
10 points sent
Transaction now coming through...
1 chooses 4
Visiting Dealer_4: -9 sent by 1
10 points requested
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: 12 sent by 4
10 points requested
Transaction now coming through...
0 chooses 4
Visiting Dealer_4: -9 sent by 0
8 points requested
Transaction now coming through...
2 chooses 1
Visiting Dealer_1: 0 sent by 2
7 points sent
Transaction now coming through...
5 chooses 4
Visiting Dealer_4: -17 sent by 5
7 points sent
Transaction now coming through...
3 chooses 0
Visiting Dealer_0: 10 sent by 3
2 points sent
Transaction now coming through...
1 chooses 5
Visiting Dealer_5: -17 sent by 1
2 points requested
Transaction now coming through...
0 chooses 3
Visiting Dealer_3: 14 sent by 0
1 points sent
Transaction now coming through...
4 chooses 2
Visiting Dealer_2: -6 sent by 4
2 points sent
Transaction now coming through...
2 chooses 5
Visiting Dealer_5: -19 sent by 2
7 points sent
Transaction now coming through...
5 chooses 0
Visiting Dealer_0: 11 sent by 5
10 points sent
Transaction now coming through...
3 chooses 0
Visiting Dealer_0: 21 sent by 3
8 points sent
Transaction now coming through...
1 chooses 0
Visiting Dealer_0: 29 sent by 1
9 points requested
Transaction now coming through...
4 chooses 3
Visiting Dealer_3: 7 sent by 4
7 points requested
Transaction now coming through...
0 chooses 4
Visiting Dealer_4: -5 sent by 0
6 points sent
Transaction now coming through...
5 chooses 0
Visiting Dealer_0: 14 sent by 5
1 points requested
Transaction now coming through...
1 chooses 3
Visiting Dealer_3: 0 sent by 1
7 points requested
Transaction now coming through...
2 chooses 4
Visiting Dealer_4: 1 sent by 2
9 points requested
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: 13 sent by 4
9 points requested
Transaction now coming through...
5 chooses 1
Visiting Dealer_1: 25 sent by 5
8 points sent
Transaction now coming through...
3 chooses 4
Visiting Dealer_4: 1 sent by 3
5 points requested
Transaction now coming through...
0 chooses 5
Visiting Dealer_5: -29 sent by 0
4 points sent
Transaction now coming through...
4 chooses 0
2 chooses 5
Visiting Dealer_5: -25 sent by 2
7 points requested
Transaction now coming through...
Visiting Dealer_0: 0 sent by 4
7 points requested
Transaction now coming through...
5 chooses 3
Visiting Dealer_3: -2 sent by 5
9 points requested
Transaction now coming through...
3 chooses 4
Visiting Dealer_4: 3 sent by 3
5 points requested
Transaction now coming through...
0 chooses 1
Visiting Dealer_1: 33 sent by 0
7 points requested
Transaction now coming through...
1 chooses 0
Visiting Dealer_0: 0 sent by 1
3 points requested
Transaction now coming through...
2 chooses 3
Visiting Dealer_3: -6 sent by 2
2 points requested
Transaction now coming through...
5 chooses 1
Visiting Dealer_1: 29 sent by 5
5 points requested
Transaction now coming through...
3 chooses 5
Visiting Dealer_5: -18 sent by 3
8 points sent
Transaction now coming through...
Server report indicates: 
Dealer_0: -3
Dealer_1: 24
Dealer_2: 7
Dealer_3: -16
Dealer_4: -2
Dealer_5: -10
(-3)..(24)..(7)..(-16)..(-2)..(-10).. ---> 0
1 chooses 2
Visiting Dealer_2: 7 sent by 1
4 points sent
Transaction now coming through...
4 chooses 2
Visiting Dealer_2: 11 sent by 4
3 points requested
Transaction now coming through...
5 chooses 4
Visiting Dealer_4: 1 sent by 5
3 points sent
Transaction now coming through...
3 chooses 4
Visiting Dealer_4: 4 sent by 3
2 points sent
Transaction now coming through...
2 chooses 1
Visiting Dealer_1: 20 sent by 2
3 points sent
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: -3 sent by 4
6 points sent
Transaction now coming through...
5 chooses 0
Visiting Dealer_0: 3 sent by 5
7 points sent
Transaction now coming through...
3 chooses 2
Visiting Dealer_2: 5 sent by 3
9 points sent
Transaction now coming through...
0 chooses 3
Visiting Dealer_3: -27 sent by 0
10 points requested
Transaction now coming through...
1 chooses 2
Visiting Dealer_2: 14 sent by 1
7 points sent
Transaction now coming through...
2 chooses 5
Visiting Dealer_5: -20 sent by 2
2 points requested
Transaction now coming through...
4 chooses 2
Visiting Dealer_2: 23 sent by 4
1 points requested
Transaction now coming through...
5 chooses 4
Visiting Dealer_4: 1 sent by 5
3 points requested
Transaction now coming through...
3 chooses 0
Visiting Dealer_0: 20 sent by 3
1 points sent
Transaction now coming through...
0 chooses 2
Visiting Dealer_2: 22 sent by 0
8 points requested
Transaction now coming through...
2 chooses 4
Visiting Dealer_4: -2 sent by 2
4 points requested
Transaction now coming through...
1 chooses 4
Visiting Dealer_4: -6 sent by 1
1 points sent
Transaction now coming through...
4 chooses 5
Visiting Dealer_5: -19 sent by 4
1 points sent
Transaction now coming through...
5 chooses 3
Visiting Dealer_3: -38 sent by 5
3 points sent
Transaction now coming through...
3 chooses 5
Visiting Dealer_5: -21 sent by 3
10 points sent
Transaction now coming through...
0 chooses 1
Visiting Dealer_1: 15 sent by 0
1 points requested
Transaction now coming through...
1 chooses 4
Visiting Dealer_4: -6 sent by 1
1 points sent
Transaction now coming through...
2 chooses 0
Visiting Dealer_0: 30 sent by 2
5 points requested
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: 25 sent by 4
9 points requested
Transaction now coming through...
5 chooses 4
Visiting Dealer_4: 4 sent by 5
8 points requested
Transaction now coming through...
3 chooses 4
Visiting Dealer_4: -4 sent by 3
6 points requested
Transaction now coming through...
0 chooses 4
Visiting Dealer_4: -10 sent by 0
5 points sent
Transaction now coming through...
1 chooses 0
Visiting Dealer_0: 11 sent by 1
8 points sent
Transaction now coming through...
4 chooses 1
Visiting Dealer_1: 5 sent by 4
8 points requested
Transaction now coming through...
5 chooses 1
Visiting Dealer_1: -3 sent by 5
2 points sent
Transaction now coming through...
0 chooses 3
Visiting Dealer_3: -39 sent by 0
3 points sent
Transaction now coming through...
2 chooses 5
Visiting Dealer_5: -5 sent by 2
6 points sent
Transaction now coming through...
1 chooses 2
Visiting Dealer_2: 17 sent by 1
5 points requested
Transaction now coming through...
4 chooses 0
Visiting Dealer_0: 16 sent by 4
5 points sent
Transaction now coming through...
3 chooses 2
Visiting Dealer_2: 12 sent by 3
10 points sent
Transaction now coming through...
5 chooses 1
Visiting Dealer_1: 4 sent by 5
3 points requested
Transaction now coming through...
0 chooses 5
Visiting Dealer_5: 4 sent by 0
10 points sent
Transaction now coming through...
2 chooses 1
Visiting Dealer_1: 1 sent by 2
3 points requested
Transaction now coming through...
4 chooses 5
Visiting Dealer_5: 14 sent by 4
7 points sent
Transaction now coming through...
3 chooses 1
Visiting Dealer_1: -2 sent by 3
1 points sent
Transaction now coming through...
5 chooses 2
Visiting Dealer_2: 25 sent by 5
1 points sent
Transaction now coming through...
Server report indicates: 
Dealer_0: 11
Dealer_1: -1
Dealer_2: 26
Dealer_3: -47
Dealer_4: -9
Dealer_5: 20
(11)..(-1)..(26)..(-47)..(-9)..(20).. ---> 0
0 chooses 4
Visiting Dealer_4: -9 sent by 0
1 points sent
Transaction now coming through...
1 chooses 0
Visiting Dealer_0: 10 sent by 1
4 points sent
Transaction now coming through...
2 chooses 1
Visiting Dealer_1: -5 sent by 2
4 points requested
Transaction now coming through...
3 chooses 1
Visiting Dealer_1: -9 sent by 3
8 points requested
Transaction now coming through...
5 chooses 1
Visiting Dealer_1: -17 sent by 5
6 points requested
Transaction now coming through...
0 chooses 1
Visiting Dealer_1: -23 sent by 0
10 points sent
Transaction now coming through...
2 chooses 3
Visiting Dealer_3: -39 sent by 2
10 points sent
Transaction now coming through...
4 chooses 1
Visiting Dealer_1: -13 sent by 4
10 points sent
Transaction now coming through...
3 chooses 0
Visiting Dealer_0: 4 sent by 3
7 points sent
Transaction now coming through...
5 chooses 0
Visiting Dealer_0: 11 sent by 5
3 points requested
Transaction now coming through...
^Cfrilled.cs.indiana.edu%
Same thing should happen if we start things in a distributed fashion.

This is how you start the server (dave):

frilled.cs.indiana.edu%java ServerImplementation 36091 dave
This is how you start the first client (larry)
burrowww.cs.indiana.edu% java ClientImplementation larry frilled.cs.indiana.edu 36091 dave
This is how you start the second client (michael):
tucotuco.cs.indiana.edu% java ClientImplementation michael frilled.cs.indiana.edu 36091 dave
This is how you start a third client (toni):
blesmol.cs.indiana.edu% java ClientImplementation toni frilled.cs.indiana.edu 36091 dave
And the four entities do work as expected.

Part Three. A simple automated chat system.

Take this as a potential exercise, although a solution will be presented.

Here's the local version of a (simple, automated) chat system:

Here's how this works:

frilled.cs.indiana.edu%javac *.java
frilled.cs.indiana.edu%java LocalSetup    
Server being initialized ... 
                                                                    clients: larry, michael, toni

larry receives: ***(toni says: Howdy!)***                                                    toni 
michael receives: ***(toni says: Howdy!)*** 
toni receives: ***(toni says: Howdy!)*** 

larry receives: ***(michael says: Howdy!)***                                        michael 
michael receives: ***(michael says: Howdy!)*** 
toni receives: ***(michael says: Howdy!)*** 

larry receives: ***(larry says: Howdy!)***                                   larry 
michael receives: ***(larry says: Howdy!)*** 
toni receives: ***(larry says: Howdy!)*** 

larry receives: ***(toni says: Howdy!)***                                                    toni 
michael receives: ***(toni says: Howdy!)*** 
toni receives: ***(toni says: Howdy!)*** 

larry receives: ***(michael says: Howdy!)***                                        michael 
michael receives: ***(michael says: Howdy!)*** 
toni receives: ***(michael says: Howdy!)*** 

larry receives: ***(larry says: Howdy!)***                                   larry 
michael receives: ***(larry says: Howdy!)*** 
toni receives: ***(larry says: Howdy!)*** 

larry receives: ***(toni says: Howdy!)***                                                    toni 
michael receives: ***(toni says: Howdy!)*** 
toni receives: ***(toni says: Howdy!)*** 

larry receives: ***(michael says: Howdy!)***                                        michael 
michael receives: ***(michael says: Howdy!)*** 
toni receives: ***(michael says: Howdy!)*** 

larry receives: ***(larry says: Howdy!)***                                   larry 
michael receives: ***(larry says: Howdy!)*** 
toni receives: ***(larry says: Howdy!)*** 

larry receives: ***(toni says: Howdy!)***                                                    toni 
michael receives: ***(toni says: Howdy!)*** 
toni receives: ***(toni says: Howdy!)*** 
^Cfrilled.cs.indiana.edu%
This was just an example.

It doesn't always go in this order.

Now let's see what it takes to make this application distributed.

frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/sum2004/pasadena/partThree/two
frilled.cs.indiana.edu%ls -ld *.java
-rw-------  1 dgerman faculty  121 Jul  4 17:20 Client.java
-rw-------  1 dgerman faculty 1295 Jul  4 17:25 ClientImplementation.java
-rw-------  1 dgerman faculty  407 Jul  4 17:26 LocalSetup.java
-rw-------  1 dgerman faculty 1575 Jul  4 17:21 NetworkPeer.java
-rw-------  1 dgerman faculty  195 Jul  4 17:20 Server.java
-rw-------  1 dgerman faculty  967 Jul  4 17:26 ServerImplementation.java
-rw-------  1 dgerman faculty  189 Jul  4 17:01 Update.java
frilled.cs.indiana.edu%diff Client.java ../one/Server.java
1,2c1,3
< public interface Client extends java.rmi.Remote {
<   public void update(Update event) throws java.rmi.RemoteException; 
---
> public interface Server {
>   public int register(Client client); 
>   public void broadcast(Update event); 
frilled.cs.indiana.edu%clear
frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/sum2004/pasadena/partThree/two
frilled.cs.indiana.edu%ls -ld *.java
-rw-------  1 dgerman faculty  121 Jul  4 17:20 Client.java
-rw-------  1 dgerman faculty 1295 Jul  4 17:25 ClientImplementation.java
-rw-------  1 dgerman faculty  407 Jul  4 17:26 LocalSetup.java
-rw-------  1 dgerman faculty 1575 Jul  4 17:21 NetworkPeer.java
-rw-------  1 dgerman faculty  195 Jul  4 17:20 Server.java
-rw-------  1 dgerman faculty  967 Jul  4 17:26 ServerImplementation.java
-rw-------  1 dgerman faculty  189 Jul  4 17:01 Update.java
frilled.cs.indiana.edu%diff Server.java ../one/Server.java
1,3c1,3
< public interface Server extends java.rmi.Remote {
<   public int register(Client client) throws java.rmi.RemoteException; 
<   public void broadcast(Update event) throws java.rmi.RemoteException; 
---
> public interface Server {
>   public int register(Client client); 
>   public void broadcast(Update event); 
frilled.cs.indiana.edu%diff ServerImplementation.java ../one/ServerImplementation.java
1c1
< public class ServerImplementation extends NetworkPeer implements Server, java.rmi.Remote {
---
> public class ServerImplementation implements Server {
4c4
<   synchronized public int register(Client client) throws java.rmi.RemoteException {
---
>   synchronized public int register(Client client) {
8c8
<   synchronized public void broadcast(Update event) throws java.rmi.RemoteException {
---
>   synchronized public void broadcast(Update event) {
20,30d19
<   public static void main(String[] args) {
<     String 
<       portNumber = args[0], 
<       ownName = args[1]; 
<     ServerImplementation here = new ServerImplementation(ownName); 
<     here.startAsNetworkServer(ownName, 
<                             Integer.parseInt(portNumber)); 
<   }
<   public void startAsClientOf(java.rmi.Remote peer) {
< 
<   }
frilled.cs.indiana.edu%diff Client.java ../one/Client.java
1,2c1,2
< public interface Client extends java.rmi.Remote {
<   public void update(Update event) throws java.rmi.RemoteException; 
---
> public interface Client {
>   public void update(Update event); 
frilled.cs.indiana.edu%diff ClientImplementation.java ../one/ClientImplementation.java
1c1
< public class ClientImplementation extends NetworkPeer implements Runnable, Client { 
---
> public class ClientImplementation extends Thread implements Client { 
8c8
<   public void update(Update event) throws java.rmi.RemoteException  {
---
>   public void update(Update event) {
14c14
<         Thread.sleep((int)(Math.random() * 6000 + 10000)); 
---
>         sleep((int)(Math.random() * 6000 + 10000)); 
19,22c19,22
<   public void startAsClientOf(java.rmi.Remote server) throws java.rmi.RemoteException {
<     this.id = ((Server)server).register(this); 
<     this.server = (Server)server; 
<     (new Thread(this)).start(); 
---
>   public void startAsClientOf(Server server) {
>     this.id = server.register(this); 
>     this.server = server; 
>     this.start(); 
24,36d23
<   public static void main(String[] args) throws Exception {
<      String
<        ownName = args[0],
<        serverHostName = args[1], 
<        serverPortNumber = args[2], 
<        serverName = args[3];
< 
<      ClientImplementation client = new ClientImplementation(ownName); 
<      client.startAsNetworkClientOf(serverHostName, 
<                                    Integer.parseInt(serverPortNumber), 
<                                    serverName); // calls startAsClientOf
<   }
<   public void startAsLocalServer() { } 
frilled.cs.indiana.edu%diff Update.java ../one/Update.java
1c1
< public class Update implements java.io.Serializable {
---
> public class Update {
frilled.cs.indiana.edu%diff LocalSetup.java ../one/LocalSetup.java
2c2
<   public static void main(String[] args) throws /*Remote*/Exception {
---
>   public static void main(String[] args) {
frilled.cs.indiana.edu%
Although the modified source code is available the reader should take the above as a decisive hint.

And build the distributed application as an exercise.

Part Five. A simple distributed whiteboard.

The structure of this application is similar to the one just presented.

Clients are a bit more complicated, but not from the network point of view.

There's a little bit of GUI in them, and that's all the extra complexity.

Here's the code we start from:

One should compile all this and run Simulation.

Question is: how do we turn this into a distributed application?

Easy. We just apply our pattern. Here's the result:

I am here...

Epilogue. The essence of it all.

The minimal possible exercise: odd/even, the old chestnut.

Consider the following class of objects:

class Alpha {
  Beta other; 
  public boolean odd(int n) {
    if (n == 0) return false; 
    else return other.even(n-1); 
  }
} 
It implements half of an equation.

The other half is implemented by:

class Beta {
  Alpha other; 
  public boolean even(int n) {
    if (n == 0) return true; 
    else return other.odd(n-1); 
  }  
}
How do we put the two halves in motion?

First off, one of the two will start as a server, the other as a client.

The symmetry is perfect though, as we will see.

Each one of them would advertise a number of services:

interface AlphaServices {
  boolean odd(int n); 
} 

interface BetaServices {
  boolean even(int n); 
}
Now we also need to start them up:
class Beta implements BetaServices {
  Alpha other; 
  public boolean even(int n) {
    if (n == 0) return false; 
    else return other.odd(n-1); 
  }  
  void startAsLocalServer() {

  }  
  void startAsClientOf(java.rmi.Remote peer) {
    ((Alpha)peer).register(this); 
  }
  void register(Alpha alpha) {
    this.other = alpha;
  }
}
Alpha has the exact same attitude:
class Alpha implements AlphaServices {
  Beta other; 
  public boolean odd(int n) {
    if (n == 0) return true; 
    else return other.even(n-1); 
  }  
  void startAsLocalServer() {

  }  
  void startAsClientOf(java.rmi.Remote peer) {
    ((Beta)peer).register(this); 
  }
  void register(Beta beta) {
    this.other = beta;
  }
}
The local setup now looks as follows:
class LocalSetup {
  public static void main(String[] args) {
    Beta beta = new Beta(); 
    beta.startAsLocalServer(); 

    Alpha alpha = new Alpha(); 
    alpha.startAsLocalServer(); 
        
    beta.startAsClientOf(alpha); 
    alpha.startAsClientOf(beta); // start is a bit too much to say 

    System.out.println(alpha.odd(5)); 
    System.out.println(beta.even(5)); 

  }
}
Of course, for this to work we need he interfaces to match the generic type:
interface AlphaServices extends java.rmi.Remote {
  boolean odd(int n); 
} 

interface BetaServices extends java.rmi.Remote {
  boolean even(int n); 
}
And here's the end of the experiment:
frilled.cs.indiana.edu%ls -ld *.java
-rw-------  1 dgerman faculty 335 Jul  5 14:16 Alpha.java
-rw-------  1 dgerman faculty  76 Jul  5 14:07 AlphaServices.java
-rw-------  1 dgerman faculty 337 Jul  5 14:15 Beta.java
-rw-------  1 dgerman faculty  75 Jul  5 14:08 BetaServices.java
-rw-------  1 dgerman faculty 386 Jul  5 14:14 LocalSetup.java
frilled.cs.indiana.edu%more *.java
::::::::::::::
Alpha.java
::::::::::::::
class Alpha implements AlphaServices {
  Beta other; 
  public boolean odd(int n) {
    if (n == 0) return false; 
    else return other.even(n-1); 
  }  
  void startAsLocalServer() {

  }  
  void startAsClientOf(java.rmi.Remote peer) {
    ((Beta)peer).register(this); 
  }
  void register(Beta beta) {
    this.other = beta;
  }
}
::::::::::::::
AlphaServices.java
::::::::::::::
interface AlphaServices extends java.rmi.Remote {
  boolean odd(int n); 
} 
::::::::::::::
Beta.java
::::::::::::::
class Beta implements BetaServices {
  Alpha other; 
  public boolean even(int n) {
    if (n == 0) return true; 
    else return other.odd(n-1); 
  }  
  void startAsLocalServer() {

  }  
  void startAsClientOf(java.rmi.Remote peer) {
    ((Alpha)peer).register(this); 
  }
  void register(Alpha alpha) {
    this.other = alpha;
  }
}
::::::::::::::
BetaServices.java
::::::::::::::
interface BetaServices extends java.rmi.Remote {
  boolean even(int n); 
}
::::::::::::::
LocalSetup.java
::::::::::::::
class LocalSetup {
  public static void main(String[] args) {
    Beta beta = new Beta(); 
    beta.startAsLocalServer(); 

    Alpha alpha = new Alpha(); 
    alpha.startAsLocalServer(); 
        
    beta.startAsClientOf(alpha); 
    alpha.startAsClientOf(beta); // start is a bit too much to say 

    System.out.println(alpha.odd(5)); 
    System.out.println(beta.even(5)); 

  }
}
frilled.cs.indiana.edu%javac *.java
frilled.cs.indiana.edu%java LocalSetup
true
false
frilled.cs.indiana.edu%
Now we make this a distributed application:
frilled.cs.indiana.edu%pwd
/nfs/grouchy/home/user2/www/classes/a348-dger/sum2004/pasadena/lastPart/two
frilled.cs.indiana.edu%ls -ld *
-rw-------  1 dgerman faculty 1647 Jul  5 21:31 Alpha.java
-rw-------  1 dgerman faculty  178 Jul  5 18:06 AlphaServices.java
-rw-------  1 dgerman faculty 1702 Jul  5 21:34 Beta.java
-rw-------  1 dgerman faculty  178 Jul  5 18:06 BetaServices.java
-rw-------  1 dgerman faculty  465 Jul  5 21:18 LocalSetup.java
-rw-------  1 dgerman faculty 1575 Jul  5 17:14 NetworkPeer.java
frilled.cs.indiana.edu%javac *.java
frilled.cs.indiana.edu%rmic Beta
frilled.cs.indiana.edu%rmic Alpha
frilled.cs.indiana.edu%
We leave as an exercise for the reader to determine the differences with the local version.

Here's how the distributed version runs:

burrowww.cs.indiana.edu% java Alpha 36091 larry bobac.cs.indiana.edu 36092 michael
Server is ready ... 
Press a key if the other server is up and running...

Press a key when you want this to end ...
Beta_Stub[RemoteStub [ref: [endpoint:[129.79.245.103:54554](remote),objID:[7c6768:fd925a2d2c:-8000, 0]]]] is registering into Alpha@1430b5c
You are asking if 4 is odd...
You are asking if 2 is odd...
You are asking if 0 is odd...
You are asking if 5 is odd...
You are asking if 3 is odd...
You are asking if 1 is odd...
^Cburrowww.cs.indiana.edu% 

bobac.cs.indiana.edu% java Beta 36092 michael burrowww.cs.indiana.edu 36091 larry
Server is ready ... 
Press a key if the other server is up and running...

You are asking if 5 is even...
You are asking if 3 is even...
You are asking if 1 is even...
Is 5 even? The answer is: false
You are asking if 4 is even...
You are asking if 2 is even...
You are asking if 0 is even...
Is 5 odd? The answer is: true
Press a key when you want this to end ...
^Cbobac.cs.indiana.edu% 
bobac.cs.indiana.edu% 
One starts Alpha then Beta.

And then keys are being pressed into Alpha, and then in Beta.

Beta then prints the output, then one can close them (^-C) in any order.