/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.util.*;
import je3.rmi.Mud.*;
/**
* This class is a client program for the MUD. The main() method sets up
* a connection to a RemoteMudServer, gets the initial RemoteMudPlace object,
* and creates a MudPerson object to represent the user in the MUD. Then it
* calls runMud() to put the person in the place, begins processing
* user commands. The getLine() and getMultiLine() methods are convenience
* methods used throughout to get input from the user.
**/
public class MudClient {
/**
* The main program. It expects two or three arguments:
* 0) the name of the host on which the mud server is running
* 1) the name of the MUD on that host
* 2) the name of a place within that MUD to start at (optional).
*
* It uses the Naming.lookup() method to obtain a RemoteMudServer object
* for the named MUD on the specified host. Then it uses the getEntrance()
* or getNamedPlace() method of RemoteMudServer to obtain the starting
* RemoteMudPlace object. It prompts the user for a their name and
* description, and creates a MudPerson object. Finally, it passes
* the person and the place to runMud() to begin interaction with the MUD.
**/
public static void main(String[] args) {
try {
String hostname = args[0]; // Each MUD is uniquely identified by a
String mudname = args[1]; // host and a MUD name.
String placename = null; // Each place in a MUD has a unique name
if (args.length > 2) placename = args[2];
// Look up the RemoteMudServer object for the named MUD using
// the default registry on the specified host. Note the use of
// the Mud.mudPrefix constant to help prevent naming conflicts
// in the registry.
RemoteMudServer server =
(RemoteMudServer)Naming.lookup("rmi://" + hostname + "/" +
Mud.mudPrefix + mudname);
// If the user did not specify a place in the mud, use
// getEntrance() to get the initial place. Otherwise, call
// getNamedPlace() to find the initial place.
RemoteMudPlace location = null;
if (placename == null) location = server.getEntrance();
else location = (RemoteMudPlace) server.getNamedPlace(placename);
// Greet the user and ask for their name and description.
// This relies on getLine() and getMultiLine() defined below.
System.out.println("Welcome to " + mudname);
String name = getLine("Enter your name: ");
String description = getMultiLine("Please describe what " +
"people see when they look at you:");
// Define an output stream that the MudPerson object will use to
// display messages sent to it to the user. We'll use the console.
PrintWriter myout = new PrintWriter(System.out);
// Create a MudPerson object to represent the user in the MUD.
// Use the specified name and description, and the output stream.
MudPerson me = new MudPerson(name, description, myout);
// Lower this thread's priority one notch so that broadcast
// messages can appear even when we're blocking for I/O. This is
// necessary on the Linux platform, but may not be necessary on all
// platforms.
int pri = Thread.currentThread().getPriority();
Thread.currentThread().setPriority(pri-1);
// Finally, put the MudPerson into the RemoteMudPlace, and start
// prompting the user for commands.
runMud(location, me);
}
// If anything goes wrong, print a message and exit.
catch (Exception e) {
System.out.println(e);
System.out.println("Usage: java MudClient <host> <mud> [<place>]");
System.exit(1);
}
}
/**
* This method is the main loop of the MudClient. It places the person
* into the place (using the enter() method of RemoteMudPlace). Then it
* calls the look() method to describe the place to the user, and enters a
* command loop to prompt the user for a command and process the command
**/
public static void runMud(RemoteMudPlace entrance, MudPerson me)
throws RemoteException
{
RemoteMudPlace location = entrance; // The current place
String myname = me.getName(); // The person's name
String placename = null; // The name of the current place
String mudname = null; // The name of the mud of that place
try {
// Enter the MUD
location.enter(me, myname, myname + " has entered the MUD.");
// Figure out where we are (for the prompt)
mudname = location.getServer().getMudName();
placename = location.getPlaceName();
// Describe the place to the user
look(location);
}
catch (Exception e) {
System.out.println(e);
System.exit(1);
}
// Now that we've entered the MUD, begin a command loop to process
// the user's commands. Note that there is a huge block of catch
// statements at the bottom of the loop to handle all the things that
// could go wrong each time through the loop.
for(;;) { // Loop until the user types "quit"
try { // Catch any exceptions that occur in the loop
// Pause just a bit before printing the prompt, to give output
// generated indirectly by the last command a chance to appear.
try { Thread.sleep(200); } catch (InterruptedException e) {}
// Display a prompt, and get the user's input
String line = getLine(mudname + '.' + placename + "> ");
// Break the input into a command and an argument that consists
// of the rest of the line. Convert the command to lowercase.
String cmd, arg;
int i = line.indexOf(' ');
if (i == -1) { cmd = line; arg = null; }
else {
cmd = line.substring(0, i).toLowerCase();
arg = line.substring(i+1);
}
if (arg == null) arg = "";
// Now go process the command. What follows is a huge repeated
// if/else statement covering each of the commands supported by
// this client. Many of these commands simply invoke one of
// the remote methods of the current RemoteMudPlace object.
// Some have to do a bit of additional processing.
// LOOK: Describe the place and its things, people, and exits
if (cmd.equals("look")) look(location);
// EXAMINE: Describe a named thing
else if (cmd.equals("examine"))
System.out.println(location.examineThing(arg));
// DESCRIBE: Describe a named person
else if (cmd.equals("describe")) {
try {
RemoteMudPerson p = location.getPerson(arg);
System.out.println(p.getDescription());
}
catch(RemoteException e) {
System.out.println(arg + " is having technical " +
"difficulties. No description " +
"is available.");
}
}
// GO: Go in a named direction
else if (cmd.equals("go")) {
location = location.go(me, arg);
mudname = location.getServer().getMudName();
placename = location.getPlaceName();
look(location);
}
// SAY: Say something to everyone
else if (cmd.equals("say")) location.speak(me, arg);
// DO: Do something that will be described to everyone
else if (cmd.equals("do")) location.act(me, arg);
// TALK: Say something to one named person
else if (cmd.equals("talk")) {
try {
RemoteMudPerson p = location.getPerson(arg);
String msg = getLine("What do you want to say?: ");
p.tell(myname + " says \"" + msg + "\"");
}
catch (RemoteException e) {
System.out.println(arg + " is having technical " +
"difficulties. Can't talk to them.");
}
}
// CHANGE: Change my own description
else if (cmd.equals("change"))
me.setDescription(
getMultiLine("Describe yourself for others: "));
// CREATE: Create a new thing in this place
else if (cmd.equals("create")) {
if (arg.length() == 0)
throw new IllegalArgumentException("name expected");
String desc = getMultiLine("Please describe the " +
arg + ": ");
location.createThing(me, arg, desc);
}
// DESTROY: Destroy a named thing
else if (cmd.equals("destroy")) location.destroyThing(me, arg);
// OPEN: Create a new place and connect this place to it
// through the exit specified in the argument.
else if (cmd.equals("open")) {
if (arg.length() == 0)
throw new IllegalArgumentException("direction expected");
String name = getLine("What is the name of place there?: ");
String back = getLine("What is the direction from " +
"there back to here?: ");
String desc = getMultiLine("Please describe " +
name + ":");
location.createPlace(me, arg, back, name, desc);
}
// CLOSE: Close a named exit. Note: only closes an exit
// uni-directionally, and does not destroy a place.
else if (cmd.equals("close")) {
if (arg.length() == 0)
throw new IllegalArgumentException("direction expected");
location.close(me, arg);
}
// LINK: Create a new exit that connects to an existing place
// that may be in another MUD running on another host
else if (cmd.equals("link")) {
if (arg.length() == 0)
throw new IllegalArgumentException("direction expected");
String host = getLine("What host are you linking to?: ");
String mud =
getLine("What is the name of the MUD on that host?: ");
String place =
getLine("What is the place name in that MUD?: ");
location.linkTo(me, arg, host, mud, place);
System.out.println("Don't forget to make a link from " +
"there back to here!");
}
// DUMP: Save the state of this MUD into the named file,
// if the password is correct
else if (cmd.equals("dump")) {
if (arg.length() == 0)
throw new IllegalArgumentException("filename expected");
String password = getLine("Password: ");
location.getServer().dump(password, arg);
}
// QUIT: Quit the game
else if (cmd.equals("quit")) {
try { location.exit(me, myname + " has quit."); }
catch (Exception e) {}
System.out.println("Bye.");
System.out.flush();
System.exit(0);
}
// HELP: Print out a big help message
else if (cmd.equals("help")) System.out.println(help);
// Otherwise, this is an unrecognized command.
else System.out.println("Unknown command. Try 'help'.");
}
// Handle the many possible types of MudException
catch (MudException e) {
if (e instanceof NoSuchThing)
System.out.println("There isn't any such thing here.");
else if (e instanceof NoSuchPerson)
System.out.println("There isn't anyone by that name here.");
else if (e instanceof NoSuchExit)
System.out.println("There isn't an exit in that direction.");
else if (e instanceof NoSuchPlace)
System.out.println("There isn't any such place.");
else if (e instanceof ExitAlreadyExists)
System.out.println("There is already an exit " +
"in that direction.");
else if (e instanceof PlaceAlreadyExists)
System.out.println("There is already a place " +
"with that name.");
else if (e instanceof LinkFailed)
System.out.println("That exit is not functioning.");
else if (e instanceof BadPassword)
System.out.println("Invalid password.");
else if (e instanceof NotThere) // Shouldn't happen
System.out.println("You can't do that when " +
"you're not there.");
else if (e instanceof AlreadyThere) // Shouldn't happen
System.out.println("You can't go there; " +
"you're already there.");
}
// Handle RMI exceptions
catch (RemoteException e) {
System.out.println("The MUD is having technical difficulties.");
System.out.println("Perhaps the server has crashed:");
System.out.println(e);
}
// Handle everything else that could go wrong.
catch (Exception e) {
System.out.println("Syntax or other error:");
System.out.println(e);
System.out.println("Try using the 'help' command.");
}
}
}
/**
* This convenience method is used in several places in the
* runMud() method above. It displays the name and description of
* the current place (including the name of the mud the place is in),
* and also displays the list of things, people, and exits in
* the current place.
**/
public static void look(RemoteMudPlace p)
throws RemoteException, MudException
{
String mudname = p.getServer().getMudName(); // Mud name
String placename = p.getPlaceName(); // Place name
String description = p.getDescription(); // Place description
Vector things = p.getThings(); // List of things here
Vector names = p.getNames(); // List of people here
Vector exits = p.getExits(); // List of exits from here
// Print it all out
System.out.println("You are in: " + placename +
" of the Mud: " + mudname);
System.out.println(description);
System.out.print("Things here: ");
for(int i = 0; i < things.size(); i++) { // Display list of things
if (i > 0) System.out.print(", ");
System.out.print(things.elementAt(i));
}
System.out.print("\nPeople here: ");
for(int i = 0; i < names.size(); i++) { // Display list of people
if (i > 0) System.out.print(", ");
System.out.print(names.elementAt(i));
}
System.out.print("\nExits are: ");
for(int i = 0; i < exits.size(); i++) { // Display list of exits
if (i > 0) System.out.print(", ");
System.out.print(exits.elementAt(i));
}
System.out.println(); // Blank line
System.out.flush(); // Make it appear now!
}
/** This static input stream reads lines from the console */
static BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
/**
* A convenience method for prompting the user and getting a line of
* input. It guarantees that the line is not empty and strips off
* whitespace at the beginning and end of the line.
**/
public static String getLine(String prompt) {
String line = null;
do { // Loop until a non-empty line is entered
try {
System.out.print(prompt); // Display prompt
System.out.flush(); // Display it right away
line = in.readLine(); // Get a line of input
if (line != null) line = line.trim(); // Strip off whitespace
} catch (Exception e) {} // Ignore any errors
} while((line == null) || (line.length() == 0));
return line;
}
/**
* A convenience method for getting multi-line input from the user.
* It prompts for the input, displays instructions, and guarantees that
* the input is not empty. It also allows the user to enter the name of
* a file from which text will be read.
**/
public static String getMultiLine(String prompt) {
String text = "";
for(;;) { // We'll break out of this loop when we get non-empty input
try {
BufferedReader br = in; // The stream to read from
System.out.println(prompt); // Display the prompt
// Display some instructions
System.out.println("You can enter multiple lines. " +
"End with a '.' on a line by itself.\n" +
"Or enter a '<<' followed by a filename");
// Make the prompt and instructions appear now.
System.out.flush();
// Read lines
String line;
while((line = br.readLine()) != null) { // Until EOF
if (line.equals(".")) break; // Or until a dot by itself
// Or, if a file is specified, start reading from it
// instead of from the console.
if (line.trim().startsWith("<<")) {
String filename = line.trim().substring(2).trim();
br = new BufferedReader(new FileReader(filename));
continue; // Don't count the << as part of the input
}
// Add the line to the collected input
else text += line + "\n";
}
// If we got at least one line, return it. Otherwise, chastise
// the user and go back to the prompt and the instructions.
if (text.length() > 0) return text;
else System.out.println("Please enter at least one line.");
}
// If there were errors, for example an IO error reading a file,
// display the error and loop again, displaying prompt and
// instructions
catch(Exception e) { System.out.println(e); }
}
}
/** This is the usage string that explains the available commands */
static final String help =
"Commands are:\n" +
"look: Look around\n" +
"examine <thing>: examine the named thing in more detail\n" +
"describe <person>: describe the named person\n" +
"go <direction>: go in the named direction (i.e. a named exit)\n" +
"say <message>: say something to everyone\n" +
"do <message>: tell everyone that you are doing something\n" +
"talk <person>: talk to one person. Will prompt for message\n" +
"change: change how you are described. Will prompt for input\n" +
"create <thing>: create a new thing. Prompts for description \n" +
"destroy <thing>: destroy a thing.\n" +
"open <direction>: create an adjoining place. Prompts for input\n"+
"close <direction>: close an exit from this place.\n" +
"link <direction>: create an exit to an existing place,\n" +
" perhaps on another server. Will prompt for input.\n" +
"dump <filename>: save server state. Prompts for password\n" +
"quit: leave the Mud\n" +
"help: display this message";
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.io.*;
import je3.rmi.Mud.*;
/**
* This is the simplest of the remote objects that we implement for the MUD.
* It maintains only a little bit of state, and has only two exported
* methods
**/
public class MudPerson extends UnicastRemoteObject implements RemoteMudPerson {
String name; // The name of the person
String description; // The person's description
PrintWriter tellStream; // Where to send messages we receive to
public MudPerson(String n, String d, PrintWriter out)
throws RemoteException
{
name = n;
description = d;
tellStream = out;
}
/** Return the person's name. Not a remote method */
public String getName() { return name; }
/** Set the person's name. Not a remote method */
public void setName(String n) { name = n; }
/** Set the person's description. Not a remote method */
public void setDescription(String d) { description = d; }
/** Set the stream that messages to us should be written to. Not remote. */
public void setTellStream(PrintWriter out) { tellStream = out; }
/** A remote method that returns this person's description */
public String getDescription() throws RemoteException {
return description;
}
/**
* A remote method that delivers a message to the person.
* I.e. it delivers a message to the user controlling the "person"
**/
public void tell(String message) throws RemoteException {
tellStream.println(message);
tellStream.flush();
}
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.util.*;
import je3.rmi.Mud.*;
/**
* This class implements the RemoteMudPlace interface and exports a
* bunch of remote methods that are at the heart of the MUD. The
* MudClient interacts primarily with these methods. See the comment
* for RemoteMudPlace for an overview.
* The MudPlace class is Serializable so that places can be saved to disk
* along with the MudServer that contains them. Note, however that the
* names and people fields are marked transient, so they are not serialized
* along with the place (because it wouldn't make sense to try to save
* RemoteMudPerson objects, even if they could be serialized).
**/
public class MudPlace extends UnicastRemoteObject
implements RemoteMudPlace, Serializable
{
String placename, description; // information about the place
Vector exits = new Vector(); // names of exits from this place
Vector destinations = new Vector(); // where the exits go to
Vector things = new Vector(); // names of things in this place
Vector descriptions = new Vector(); // descriptions of those things
transient Vector names = new Vector(); // names of people in this place
transient Vector people = new Vector(); // the RemoteMudPerson objects
MudServer server; // the server for this place
/** A no-arg constructor for de-serialization only. Do not call it */
public MudPlace() throws RemoteException { super(); }
/**
* This constructor creates a place, and calls a server method
* to register the object so that it will be accessible by name
**/
public MudPlace(MudServer server, String placename, String description)
throws RemoteException, PlaceAlreadyExists
{
this.server = server;
this.placename = placename;
this.description = description;
server.setPlaceName(this, placename); // Register the place
}
/** This remote method returns the name of this place */
public String getPlaceName() throws RemoteException { return placename; }
/** This remote method returns the description of this place */
public String getDescription() throws RemoteException {
return description;
}
/** This remote method returns a Vector of names of people in this place */
public Vector getNames() throws RemoteException { return names; }
/** This remote method returns a Vector of names of things in this place */
public Vector getThings() throws RemoteException { return things; }
/** This remote method returns a Vector of names of exits from this place*/
public Vector getExits() throws RemoteException { return exits; }
/**
* This remote method returns a RemoteMudPerson object corresponding to
* the specified name, or throws an exception if no such person is here
**/
public RemoteMudPerson getPerson(String name)
throws RemoteException, NoSuchPerson
{
synchronized(names) {
// What about when there are 2 of the same name?
int i = names.indexOf(name);
if (i == -1) throw new NoSuchPerson();
return (RemoteMudPerson) people.elementAt(i);
}
}
/**
* This remote method returns a description of the named thing, or
* throws an exception if no such thing is in this place.
**/
public String examineThing(String name) throws RemoteException, NoSuchThing
{
synchronized(things) {
int i = things.indexOf(name);
if (i == -1) throw new NoSuchThing();
return (String) descriptions.elementAt(i);
}
}
/**
* This remote method moves the specified RemoteMudPerson from this place
* in the named direction (i.e. through the named exit) to whatever place
* is there. It throws exceptions if the specified person isn't in this
* place to begin with, or if they are already in the place through the
* exit or if the exit doesn't exist, or if the exit links to another MUD
* server and the server is not functioning.
**/
public RemoteMudPlace go(RemoteMudPerson who, String direction)
throws RemoteException, NotThere, AlreadyThere, NoSuchExit, LinkFailed
{
// Make sure the direction is valid, and get destination if it is
Object destination;
synchronized(exits) {
int i = exits.indexOf(direction);
if (i == -1) throw new NoSuchExit();
destination = destinations.elementAt(i);
}
// If destination is a string, it is a place on another server, so
// connect to that server. Otherwise, it is a place already on this
// server. Throw an exception if we can't connect to the server.
RemoteMudPlace newplace;
if (destination instanceof String) {
try {
String t = (String) destination;
int pos = t.indexOf('@');
String url = t.substring(0, pos);
String placename = t.substring(pos+1);
RemoteMudServer s = (RemoteMudServer) Naming.lookup(url);
newplace = s.getNamedPlace(placename);
}
catch (Exception e) { throw new LinkFailed(); }
}
// If the destination is not a string, then it is a Place
else newplace = (RemoteMudPlace) destination;
// Make sure the person is here and get their name.
// Throw an exception if they are not here
String name = verifyPresence(who);
// Move the person out of here, and tell everyone who remains about it.
this.exit(who, name + " has gone " + direction);
// Put the person into the new place.
// Send a message to everyone already in that new place
String fromwhere;
if (newplace instanceof MudPlace) // going to a local place
fromwhere = placename;
else
fromwhere = server.getMudName() + "." + placename;
newplace.enter(who, name, name + " has arrived from: " + fromwhere);
// Return the new RemoteMudPlace object to the client so they
// know where they are now at.
return newplace;
}
/**
* This remote method sends a message to everyone in the room. Used to
* say things to everyone. Requires that the speaker be in this place.
**/
public void speak(RemoteMudPerson speaker, String msg)
throws RemoteException, NotThere
{
String name = verifyPresence(speaker);
tellEveryone(name + ":" + msg);
}
/**
* This remote method sends a message to everyone in the room. Used to
* do things that people can see. Requires that the actor be in this place.
**/
public void act(RemoteMudPerson actor, String msg)
throws RemoteException, NotThere
{
String name = verifyPresence(actor);
tellEveryone(name + " " + msg);
}
/**
* This remote method creates a new thing in this room.
* It requires that the creator be in this room.
**/
public void createThing(RemoteMudPerson creator,
String name, String description)
throws RemoteException, NotThere, AlreadyThere
{
// Make sure the creator is here
String creatorname = verifyPresence(creator);
synchronized(things) {
// Make sure there isn't already something with this name.
if (things.indexOf(name) != -1) throw new AlreadyThere();
// Add the thing name and descriptions to the appropriate lists
things.addElement(name);
descriptions.addElement(description);
}
// Tell everyone about the new thing and its creator
tellEveryone(creatorname + " has created a " + name);
}
/**
* Remove a thing from this room. Throws exceptions if the person
* who removes it isn't themselves in the room, or if there is no
* such thing here.
**/
public void destroyThing(RemoteMudPerson destroyer, String thing)
throws RemoteException, NotThere, NoSuchThing
{
// Verify that the destroyer is here
String name = verifyPresence(destroyer);
synchronized(things) {
// Verify that there is a thing by that name in this room
int i = things.indexOf(thing);
if (i == -1) throw new NoSuchThing();
// And remove its name and description from the lists
things.removeElementAt(i);
descriptions.removeElementAt(i);
}
// Let everyone know of the demise of this thing.
tellEveryone(name + " had destroyed the " + thing);
}
/**
* Create a new place in this MUD, with the specified name an description.
* The new place is accessible from this place through
* the specified exit, and this place is accessible from the new place
* through the specified entrance. The creator must be in this place
* in order to create a exit from this place.
**/
public void createPlace(RemoteMudPerson creator,
String exit, String entrance, String name,
String description)
throws RemoteException,NotThere,ExitAlreadyExists,PlaceAlreadyExists
{
// Verify that the creator is actually here in this place
String creatorname = verifyPresence(creator);
synchronized(exits) { // Only one client may change exits at a time
// Check that the exit doesn't already exist.
if (exits.indexOf(exit) != -1) throw new ExitAlreadyExists();
// Create the new place, registering its name with the server
MudPlace destination = new MudPlace(server, name, description);
// Link from there back to here
destination.exits.addElement(entrance);
destination.destinations.addElement(this);
// And link from here to there
exits.addElement(exit);
destinations.addElement(destination);
}
// Let everyone know about the new exit, and the new place beyond
tellEveryone(creatorname + " has created a new place: " + exit);
}
/**
* Create a new exit from this mud, linked to a named place in a named
* MUD on a named host (this can also be used to link to a named place in
* the current MUD, of course). Because of the possibilities of deadlock,
* this method only links from here to there; it does not create a return
* exit from there to here. That must be done with a separate call.
**/
public void linkTo(RemoteMudPerson linker, String exit,
String hostname, String mudname, String placename)
throws RemoteException, NotThere, ExitAlreadyExists, NoSuchPlace
{
// Verify that the linker is actually here
String name = verifyPresence(linker);
// Check that the link target actually exists. Throw NoSuchPlace if
// not. Note that NoSuchPlace may also mean "NoSuchMud" or
// "MudNotResponding".
String url = "rmi://" + hostname + '/' + Mud.mudPrefix + mudname;
try {
RemoteMudServer s = (RemoteMudServer) Naming.lookup(url);
RemoteMudPlace destination = s.getNamedPlace(placename);
}
catch (Exception e) { throw new NoSuchPlace(); }
synchronized(exits) {
// Check that the exit doesn't already exist.
if (exits.indexOf(exit) != -1) throw new ExitAlreadyExists();
// Add the exit, to the list of exit names
exits.addElement(exit);
// And add the destination to the list of destinations. Note that
// the destination is stored as a string rather than as a
// RemoteMudPlace. This is because if the remote server goes down
// then comes back up again, a RemoteMudPlace is not valid, but the
// string still is.
destinations.addElement(url + '@' + placename);
}
// Let everyone know about the new exit and where it leads
tellEveryone(name + " has linked " + exit + " to " +
"'" + placename + "' in MUD '" + mudname +
"' on host " + hostname);
}
/**
* Close an exit that leads out of this place.
* It does not close the return exit from there back to here.
* Note that this method does not destroy the place that the exit leads to.
* In the current implementation, there is no way to destroy a place.
**/
public void close(RemoteMudPerson who, String exit)
throws RemoteException, NotThere, NoSuchExit
{
// check that the person closing the exit is actually here
String name = verifyPresence(who);
synchronized(exits) {
// Check that the exit exists
int i = exits.indexOf(exit);
if (i == -1) throw new NoSuchExit();
// Remove it and its destination from the lists
exits.removeElementAt(i);
destinations.removeElementAt(i);
}
// Let everyone know that the exit doesn't exist anymore
tellEveryone(name + " has closed exit " + exit);
}
/**
* Remove a person from this place. If there is a message, send it to
* everyone who is left in this place. If the specified person is not here
* this method does nothing and does not throw an exception. This method
* is called by go(), and the client should call it when the user quits.
* The client should not allow the user to invoke it directly, however.
**/
public void exit(RemoteMudPerson who, String message)
throws RemoteException
{
String name;
synchronized(names) {
int i = people.indexOf(who);
if (i == -1) return;
names.removeElementAt(i);
people.removeElementAt(i);
}
if (message != null) tellEveryone(message);
}
/**
* This method puts a person into this place, assigning them the
* specified name, and displaying a message to anyone else who is in
* that place. This method is called by go(), and the client should
* call it to initially place a person into the MUD. Once the person
* is in the MUD, however, the client should restrict them to using go()
* and should not allow them to call this method directly.
* If there have been networking problems, a client might call this method
* to restore a person to this place, in case they've been bumped out.
* (A person will be bumped out of a place if the server tries to send
* a message to them and gets a RemoteException.)
**/
public void enter(RemoteMudPerson who, String name, String message)
throws RemoteException, AlreadyThere
{
// Send the message to everyone who is already here.
if (message != null) tellEveryone(message);
// Add the person to this place.
synchronized (names) {
if (people.indexOf(who) != -1) throw new AlreadyThere();
names.addElement(name);
people.addElement(who);
}
}
/**
* This final remote method returns the server object for the MUD in which
* this place exists. The client should not allow the user to invoke this
* method.
**/
public RemoteMudServer getServer() throws RemoteException {
return server;
}
/**
* Create and start a thread that sends out a message everyone in this
* place. If it gets a RemoteException talking to a person, it silently
* removes that person from this place. This is not a remote method, but
* is used internally by a number of remote methods.
**/
protected void tellEveryone(final String message) {
// If there is no-one here, don't bother sending the message!
if (people.size() == 0) return;
// Make a copy of the people here now. The message is sent
// asynchronously and the list of people in the room may change before
// the message is sent to everyone.
final Vector recipients = (Vector) people.clone();
// Create and start a thread to send the message, using an anonymous
// class. We do this because sending the message to everyone in this
// place might take some time, (particularly on a slow or flaky
// network) and we don't want to wait.
new Thread() {
public void run() {
// Loop through the recipients
for(int i = 0; i < recipients.size(); i++) {
RemoteMudPerson person =
(RemoteMudPerson)recipients.elementAt(i);
// Try to send the message to each one.
try { person.tell(message); }
// If it fails, assume that that person's client or
// network has failed, and silently remove them from
// this place.
catch (RemoteException e) {
try { MudPlace.this.exit(person, null); }
catch (Exception ex) {}
}
}
}
}.start();
}
/**
* This convenience method checks whether the specified person is here.
* If so, it returns their name. If not it throws a NotThere exception
**/
protected String verifyPresence(RemoteMudPerson who) throws NotThere {
int i = people.indexOf(who);
if (i == -1) throw new NotThere();
else return (String) names.elementAt(i);
}
/**
* This method is used for custom de-serialization. Since the vectors of
* people and of their names are transient, they are not serialized with
* the rest of this place. Therefore, when the place is de-serialized,
* those vectors have to be recreated (empty).
**/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Read most of the object as normal
names = new Vector(); // Then recreate the names vector
people = new Vector(); // and recreate the people vector
}
/** This constant is a version number for serialization */
static final long serialVersionUID = 5090967989223703026L;
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.io.*;
import java.util.Hashtable;
import java.util.zip.*;
import je3.rmi.Mud.*;
/**
* This class implements the RemoteMudServer interface. It also defines a
* main() method so you can run it as a standalone program that will
* set up and initialize a MUD server. Note that a MudServer maintains an
* entrance point to a MUD, but it is not the MUD itself. Most of the
* interesting MUD functionality is defined by the RemoteMudPlace interface
* and implemented by the RemotePlace class. In addition to being a remote
* object, this class is also Serializable, so that the state of the MUD
* can be saved to a file and later restored. Note that the main() method
* defines two ways of starting a MUD: one is to start it from scratch with
* a single initial place, and another is to restore an existing MUD from a
* file.
**/
public class MudServer extends UnicastRemoteObject
implements RemoteMudServer, Serializable
{
MudPlace entrance; // The standard entrance to this MUD
String password; // The password required to dump() the state of the MUD
String mudname; // The name that this MUD is registered under
Hashtable places; // A mapping of place names to places in this MUD
/**
* Start a MUD from scratch, with the given name and password. Create
* an initial MudPlace object as the entrance, giving it the specified
* name and description.
**/
public MudServer(String mudname, String password,
String placename, String description)
throws RemoteException
{
this.mudname = mudname;
this.password = password;
this.places = new Hashtable();
// Create the entrance place
try { this.entrance = new MudPlace(this, placename, description); }
catch (PlaceAlreadyExists e) {} // Should never happen
}
/** For serialization only. Never call this constructor. */
public MudServer() throws RemoteException {}
/** This remote method returns the name of the MUD */
public String getMudName() throws RemoteException { return mudname; }
/** This remote method returns the entrance place of the MUD */
public RemoteMudPlace getEntrance() throws RemoteException {
return entrance;
}
/**
* This remote method returns a RemoteMudPlace object for the named place.
* In this sense, a MudServer acts as like an RMI Registry object,
* returning remote objects looked up by name. It is simpler to do it this
* way than to use an actual Registry object. If the named place does not
* exist, it throws a NoSuchPlace exception
**/
public RemoteMudPlace getNamedPlace(String name)
throws RemoteException, NoSuchPlace
{
RemoteMudPlace p = (RemoteMudPlace) places.get(name);
if (p == null) throw new NoSuchPlace();
return p;
}
/**
* Define a new placename to place mapping in our hashtable.
* This is not a remote method. The MudPlace() constructor calls it
* to register the new place it is creating.
**/
public void setPlaceName(RemoteMudPlace place, String name)
throws PlaceAlreadyExists
{
if (places.containsKey(name)) throw new PlaceAlreadyExists();
places.put(name, place);
}
/**
* This remote method serializes and compresses the state of the MUD
* to a named file, if the specified password matches the one specified
* when the MUD was initially created. Note that the state of a MUD
* consists of all places in the MUD, with all things and exits in those
* places. The people in the MUD are not part of the state that is saved.
**/
public void dump(String password, String f)
throws RemoteException, BadPassword, IOException
{
if ((this.password != null)&& !this.password.equals(password))
throw new BadPassword();
ObjectOutputStream out = new ObjectOutputStream(
new GZIPOutputStream(new FileOutputStream(f)));
out.writeObject(this);
out.close();
}
/**
* This main() method defines the standalone program that starts up a MUD
* server. If invoked with a single argument, it treats that argument as
* the name of a file containing the serialized and compressed state of an
* existing MUD, and recreates it. Otherwise, it expects four command-line
* arguments: the name of the MUD, the password, the name of the entrance
* place for the MUD, and a description of that entrance place.
* Besides creating the MudServer object, this program sets an appropriate
* security manager, and uses the default rmiregistry to register the
* the MudServer under its given name.
**/
public static void main(String[] args) {
try {
MudServer server;
if (args.length == 1) {
// Read the MUD state in from a file
FileInputStream f = new FileInputStream(args[0]);
ObjectInputStream in =
new ObjectInputStream(new GZIPInputStream(f));
server = (MudServer) in.readObject();
}
// Otherwise, create an initial MUD from scratch
else server = new MudServer(args[0], args[1], args[2], args[3]);
Naming.rebind(Mud.mudPrefix + server.mudname, server);
}
// Display an error message if anything goes wrong.
catch (Exception e) {
System.out.println(e);
System.out.println("Usage: java MudServer <savefile>\n" +
" or: java MudServer <mudname> <password> " +
"<placename> <description>");
System.exit(1);
}
}
/** This constant is a version number for serialization */
static final long serialVersionUID = 7453281245880199453L;
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.sql.*;
import java.io.*;
import java.util.*;
import java.util.Date; // import explicitly to disambiguate from java.sql.Date
import je3.rmi.Bank.*; // Import inner classes of Bank
/**
* This class is another implementation of the RemoteBank interface.
* It uses a database connection as its back end, so that client data isn't
* lost if the server goes down. Note that it takes the database connection
* out of "auto commit" mode and explicitly calls commit() and rollback() to
* ensure that updates happen atomically.
**/
public class PersistentBankServer extends UnicastRemoteObject
implements RemoteBank
{
Connection db; // The connection to the database that stores account info
/** The constructor. Just save the database connection object away */
public PersistentBankServer(Connection db) throws RemoteException {
this.db = db;
}
/** Open an account */
public synchronized void openAccount(String name, String password)
throws RemoteException, BankingException
{
// First, check if there is already an account with that name
Statement s = null;
try {
s = db.createStatement();
s.executeQuery("SELECT * FROM accounts WHERE name='" + name + "'");
ResultSet r = s.getResultSet();
if (r.next()) throw new BankingException("Account name in use.");
// If it doesn't exist, go ahead and create it Also, create a
// table for the transaction history of this account and insert an
// initial transaction into it.
s = db.createStatement();
s.executeUpdate("INSERT INTO accounts VALUES ('" + name + "', '" +
password + "', 0)");
s.executeUpdate("CREATE TABLE " + name +
"_history (msg VARCHAR(80))");
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Account opened at " + new Date() + "')");
// And if we've been successful so far, commit these updates,
// ending the atomic transaction. All the methods below also use
// this atomic transaction commit/rollback scheme
db.commit();
}
catch(SQLException e) {
// If an exception was thrown, "rollback" the prior updates,
// removing them from the database. This also ends the atomic
// transaction.
try { db.rollback(); } catch (Exception e2) {}
// Pass the SQLException on in the body of a BankingException
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
// No matter what happens, don't forget to close the DB Statement
finally { try { s.close(); } catch (Exception e) {} }
}
/**
* This convenience method checks whether the name and password match
* an existing account. If so, it returns the balance in that account.
* If not, it throws an exception. Note that this method does not call
* commit() or rollback(), so its query is part of a larger transaction.
**/
public int verify(String name, String password)
throws BankingException, SQLException
{
Statement s = null;
try {
s = db.createStatement();
s.executeQuery("SELECT balance FROM accounts " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
ResultSet r = s.getResultSet();
if (!r.next())
throw new BankingException("Bad account name or password");
return r.getInt(1);
}
finally { try { s.close(); } catch (Exception e) {} }
}
/** Close a named account */
public synchronized FunnyMoney closeAccount(String name, String password)
throws RemoteException, BankingException
{
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
s = db.createStatement();
// Delete the account from the accounts table
s.executeUpdate("DELETE FROM accounts " +
"WHERE name = '" + name + "' " +
" AND password = '" + password + "'");
// And drop the transaction history table for this account
s.executeUpdate("DROP TABLE " + name + "_history");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
// Finally, return whatever balance remained in the account
return new FunnyMoney(balance);
}
/** Deposit the specified money into the named account */
public synchronized void deposit(String name, String password,
FunnyMoney money)
throws RemoteException, BankingException
{
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
s = db.createStatement();
// Update the balance
s.executeUpdate("UPDATE accounts " +
"SET balance = " + balance + money.amount + " " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
// Add a row to the transaction history
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Deposited " + money.amount +
" at " + new Date() + "')");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
}
/** Withdraw the specified amount from the named account */
public synchronized FunnyMoney withdraw(String name, String password,
int amount)
throws RemoteException, BankingException
{
int balance = 0;
Statement s = null;
try {
balance = verify(name, password);
if (balance < amount)
throw new BankingException("Insufficient Funds");
s = db.createStatement();
// Update the account balance
s.executeUpdate("UPDATE accounts " +
"SET balance = " + (balance - amount) + " " +
"WHERE name='" + name + "' " +
" AND password = '" + password + "'");
// Add a row to the transaction history
s.executeUpdate("INSERT INTO " + name + "_history " +
"VALUES ('Withdrew " + amount +
" at " + new Date() + "')");
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
return new FunnyMoney(amount);
}
/** Return the balance of the specified account */
public synchronized int getBalance(String name, String password)
throws RemoteException, BankingException
{
int balance;
try {
// Get the balance
balance = verify(name, password);
// Commit the transaction
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
// Return the balance
return balance;
}
/** Get the transaction history of the named account */
public synchronized List getTransactionHistory(String name,
String password)
throws RemoteException, BankingException
{
Statement s = null;
List list = new ArrayList();
try {
// Call verify to check the password, even though we don't
// care what the current balance is.
verify(name, password);
s = db.createStatement();
// Request everything out of the history table
s.executeQuery("SELECT * from " + name + "_history");
// Get the results of the query and put them in a Vector
ResultSet r = s.getResultSet();
while(r.next()) list.add(r.getString(1));
// Commit the transaction
db.commit();
}
catch (SQLException e) {
try { db.rollback(); } catch (Exception e2) {}
throw new BankingException("SQLException: " + e.getMessage() +
": " + e.getSQLState());
}
finally { try { s.close(); } catch (Exception e) {} }
// Return the Vector of transaction history.
return list;
}
/**
* This main() method is the standalone program that figures out what
* database to connect to with what driver, connects to the database,
* creates a PersistentBankServer object, and registers it with the registry,
* making it available for client use
**/
public static void main(String[] args) {
try {
// Create a new Properties object. Attempt to initialize it from
// the BankDB.props file or the file optionally specified on the
// command line, ignoring errors.
Properties p = new Properties();
try { p.load(new FileInputStream(args[0])); }
catch (Exception e) {
try { p.load(new FileInputStream("BankDB.props")); }
catch (Exception e2) {}
}
// The BankDB.props file (or file specified on the command line)
// must contain properties "driver" and "database", and may
// optionally contain properties "user" and "password".
String driver = p.getProperty("driver");
String database = p.getProperty("database");
String user = p.getProperty("user", "");
String password = p.getProperty("password", "");
// Load the database driver class
Class.forName(driver);
// Connect to the database that stores our accounts
Connection db = DriverManager.getConnection(database,
user, password);
// Configure the database to allow multiple queries and updates
// to be grouped into atomic transactions
db.setAutoCommit(false);
db.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// Create a server object that uses our database connection
PersistentBankServer bank = new PersistentBankServer(db);
// Read a system property to figure out how to name this server.
// Use "SecondRemote" as the default.
String name = System.getProperty("bankname", "SecondRemote");
// Register the server with the name
Naming.rebind(name, bank);
// And tell everyone that we're up and running.
System.out.println(name + " is open and ready for customers.");
}
catch (Exception e) {
System.err.println(e);
if (e instanceof SQLException)
System.err.println("SQL State: " +
((SQLException)e).getSQLState());
System.err.println("Usage: java [-Dbankname=<name>] " +
"je3.rmi.PersistentBankServer " +
"[<dbpropsfile>]");
System.exit(1);
}
}
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;
import je3.rmi.Bank.*;
/**
* This class implements the remote methods defined by the RemoteBank
* interface. It has a serious shortcoming, though: all account data is
* lost when the server goes down.
**/
public class RemoteBankServer extends UnicastRemoteObject implements RemoteBank
{
/**
* This nested class stores data for a single account with the bank
**/
class Account {
String password; // account password
int balance; // account balance
List transactions = new ArrayList(); // account transaction history
Account(String password) {
this.password = password;
transactions.add("Account opened at " + new Date());
}
}
/**
* This hashtable stores all open accounts and maps from account name
* to Account object. Methods that use this object will be synchronized
* to prevent concurrent access by more than one thread.
**/
Map accounts = new HashMap();
/**
* This constructor doesn't do anything, but because the superclass
* constructor throws an exception, the exception must be declared here
**/
public RemoteBankServer() throws RemoteException { super(); }
/**
* Open a bank account with the specified name and password
* This method is synchronized to make it thread safe, since it
* manipulates the accounts hashtable.
**/
public synchronized void openAccount(String name, String password)
throws RemoteException, BankingException
{
// Check if there is already an account under that name
if (accounts.get(name) != null)
throw new BankingException("Account already exists.");
// Otherwise, it doesn't exist, so create it.
Account acct = new Account(password);
// And register it
accounts.put(name, acct);
}
/**
* This internal method is not a remote method. Given a name and password
* it checks to see if an account with that name and password exists. If
* so, it returns the Account object. Otherwise, it throws an exception.
* This method is synchronized because it uses the accounts hashtable.
**/
synchronized Account verify(String name, String password)
throws BankingException
{
Account acct = (Account)accounts.get(name);
if (acct == null) throw new BankingException("No such account");
if (!password.equals(acct.password))
throw new BankingException("Invalid password");
return acct;
}
/**
* Close the named account. This method is synchronized to make it
* thread safe, since it manipulates the accounts hashtable.
**/
public synchronized FunnyMoney closeAccount(String name, String password)
throws RemoteException, BankingException
{
Account acct;
acct = verify(name, password);
accounts.remove(name);
// Before changing the balance or transactions of any account, we first
// have to obtain a lock on that account to be thread safe.
synchronized (acct) {
int balance = acct.balance;
acct.balance = 0;
return new FunnyMoney(balance);
}
}
/** Deposit the specified FunnyMoney to the named account */
public void deposit(String name, String password, FunnyMoney money)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) {
acct.balance += money.amount;
acct.transactions.add("Deposited " + money.amount +
" on " + new Date());
}
}
/** Withdraw the specified amount from the named account */
public FunnyMoney withdraw(String name, String password, int amount)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) {
if (acct.balance < amount)
throw new BankingException("Insufficient Funds");
acct.balance -= amount;
acct.transactions.add("Withdrew " + amount + " on "+new Date());
return new FunnyMoney(amount);
}
}
/** Return the current balance in the named account */
public int getBalance(String name, String password)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) { return acct.balance; }
}
/**
* Return a Vector of strings containing the transaction history
* for the named account
**/
public List getTransactionHistory(String name, String password)
throws RemoteException, BankingException
{
Account acct = verify(name, password);
synchronized(acct) { return acct.transactions; }
}
/**
* The main program that runs this RemoteBankServer.
* Create a RemoteBankServer object and give it a name in the registry.
* Read a system property to determine the name, but use "FirstRemote"
* as the default name. This is all that is necessary to set up the
* service. RMI takes care of the rest.
**/
public static void main(String[] args) {
try {
// Create a bank server object
RemoteBankServer bank = new RemoteBankServer();
// Figure out what to name it
String name = System.getProperty("bankname", "FirstRemote");
// Name it that
Naming.rebind(name, bank);
// Tell the world we're up and running
System.out.println(name + " is open and ready for customers.");
}
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java [-Dbankname=<name>] " +
"je3.rmi.RemoteBankServer");
System.exit(1); // Force exit because there may be RMI threads
}
}
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.util.List;
/**
* This class is a placeholder that simply contains other classes and
* for interfaces remote banking.
**/
public class Bank {
/**
* This is the interface that defines the exported methods of the
* bank server.
**/
public interface RemoteBank extends Remote {
/** Open a new account, with the specified name and password */
public void openAccount(String name, String password)
throws RemoteException, BankingException;
/** Close the named account */
public FunnyMoney closeAccount(String name, String password)
throws RemoteException, BankingException;
/** Deposit money into the named account */
public void deposit(String name, String password, FunnyMoney money)
throws RemoteException, BankingException;
/** Withdraw the specified amount of money from the named account */
public FunnyMoney withdraw(String name, String password, int amount)
throws RemoteException, BankingException;
/** Return the amount of money in the named account */
public int getBalance(String name, String password)
throws RemoteException, BankingException;
/**
* Return a List of Strings that list the transaction history
* of the named account
**/
public List getTransactionHistory(String name, String password)
throws RemoteException, BankingException;
}
/**
* This simple class represents a monetary amount. This implementation
* is really nothing more than a wrapper around an integer. It is a useful
* to demonstrate that RMI can accept arbitrary non-String objects as
* arguments and return them as values, as long as they are Serializable.
* A more complete implementation of this FunnyMoney class might bear
* a serial number, a digital signature, and other security features to
* ensure that it is unique and non-forgeable.
**/
public static class FunnyMoney implements java.io.Serializable {
public int amount;
public FunnyMoney(int amount) { this.amount = amount; }
}
/**
* This is a type of exception used to represent exceptional conditions
* related to banking, such as "Insufficient Funds" and "Invalid Password"
**/
public static class BankingException extends Exception {
public BankingException(String msg) { super(msg); }
}
/**
* This class is a simple stand-alone client program that interacts
* with a RemoteBank server. It invokes different RemoteBank methods
* depending on its command-line arguments, and demonstrates just how
* simple it is to interact with a server using RMI.
**/
public static class Client {
public static void main(String[] args) {
try {
// Figure out what RemoteBank to connect to by reading a system
// property (specified on the command line with a -D option to
// java) or, if it is not defined, use a default URL. Note
// that by default this client tries to connect to a server on
// the local machine
String url = System.getProperty("bank", "rmi:///FirstRemote");
// Now look up that RemoteBank server using the Naming object,
// which contacts the rmiregistry server. Given the url, this
// call returns a RemoteBank object whose methods may be
// invoked remotely
RemoteBank bank = (RemoteBank) Naming.lookup(url);
// Convert the user's command to lower case
String cmd = args[0].toLowerCase();
// Now, go test the command against a bunch of possible options
if (cmd.equals("open")) { // Open an account
bank.openAccount(args[1], args[2]);
System.out.println("Account opened.");
}
else if (cmd.equals("close")) { // Close an account
FunnyMoney money = bank.closeAccount(args[1], args[2]);
// Note: our currency is denominated in wooden nickels
System.out.println(money.amount +
" wooden nickels returned to you.");
System.out.println("Thanks for banking with us.");
}
else if (cmd.equals("deposit")) { // Deposit money
FunnyMoney money=new FunnyMoney(Integer.parseInt(args[3]));
bank.deposit(args[1], args[2], money);
System.out.println("Deposited " + money.amount +
" wooden nickels.");
}
else if (cmd.equals("withdraw")) { // Withdraw money
FunnyMoney money = bank.withdraw(args[1], args[2],
Integer.parseInt(args[3]));
System.out.println("Withdrew " + money.amount +
" wooden nickels.");
}
else if (cmd.equals("balance")) { // Check account balance
int amt = bank.getBalance(args[1], args[2]);
System.out.println("You have " + amt +
" wooden nickels in the bank.");
}
else if (cmd.equals("history")) { // Get transaction history
List transactions =
bank.getTransactionHistory(args[1], args[2]);
for(int i = 0; i < transactions.size(); i++)
System.out.println(transactions.get(i));
}
else System.out.println("Unknown command");
}
// Catch and display RMI exceptions
catch (RemoteException e) { System.err.println(e); }
// Catch and display Banking related exceptions
catch (BankingException e) { System.err.println(e.getMessage()); }
// Other exceptions are probably user syntax errors, so show usage.
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java [-Dbank=<url>] Bank$Client " +
"<cmd> <name> <password> [<amount>]");
System.err.println("where cmd is: open, close, deposit, " +
"withdraw, balance, history");
}
}
}
}
/*
* Copyright (c) 2004 David Flanagan. All rights reserved.
* This code is from the book Java Examples in a Nutshell, 3nd Edition.
* It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
* You may study, use, and modify it for any non-commercial purpose,
* including teaching and use in open-source projects.
* You may distribute it non-commercially as long as you retain this notice.
* For a commercial use license, or to purchase the book,
* please visit http://www.davidflanagan.com/javaexamples3.
*/
package je3.rmi;
import java.rmi.*;
import java.util.Vector;
import java.io.IOException;
/**
* This class defines three nested Remote interfaces for use by our MUD game.
* It also defines a bunch of exception subclasses, and a constant string
* prefix used to create unique names when registering MUD servers
**/
public class Mud {
/**
* This interface defines the exported methods of the MUD server object
**/
public interface RemoteMudServer extends Remote {
/** Return the name of this MUD */
public String getMudName() throws RemoteException;
/** Return the main entrance place for this MUD */
public RemoteMudPlace getEntrance() throws RemoteException;
/** Look up and return some other named place in this MUD */
public RemoteMudPlace getNamedPlace(String name)
throws RemoteException, NoSuchPlace;
/**
* Dump the state of the server to a file so that it can be restored
* later All places, and their exits and things are dumped, but the
* "people" in them are not.
**/
public void dump(String password, String filename)
throws RemoteException, BadPassword, IOException;
}
/**
* This interface defines the methods exported by a "person" object that
* is in the MUD.
**/
public interface RemoteMudPerson extends Remote {
/** Return a full description of the person */
public String getDescription() throws RemoteException;
/** Deliver a message to the person */
public void tell(String message) throws RemoteException;
}
/**
* This is the most important remote interface for the MUD. It defines the
* methods exported by the "places" or "rooms" within a MUD. Each place
* has a name and a description, and also maintains a list of "people" in
* the place, things in the place, and exits from the place. There are
* methods to get a list of names for these people, things, and exits.
* There are methods to get the RemoteMudPerson object for a named person,
* to get a description of a named thing, and to go through a named exit.
* There are methods for interacting with other people in the MUD. There
* are methods for building the MUD by creating and destroying things,
* adding new places (and new exits to those places), for linking a place
* through a new exit to some other place (possibly on another MUD server),
* and for closing down an existing exit.
**/
public interface RemoteMudPlace extends Remote {
/** Look up the name of this place */
public String getPlaceName() throws RemoteException;
/** Get a description of this place */
public String getDescription() throws RemoteException;
/** Find out the names of all people here */
public Vector getNames() throws RemoteException;
/** Get the names of all things here */
public Vector getThings() throws RemoteException;
/** Get the names of all ways out of here */
public Vector getExits() throws RemoteException;
/** Get the RemoteMudPerson object for the named person. */
public RemoteMudPerson getPerson(String name)
throws RemoteException, NoSuchPerson;
/** Get more details about a named thing */
public String examineThing(String name)
throws RemoteException,NoSuchThing;
/** Use the named exit */
public RemoteMudPlace go(RemoteMudPerson who, String direction)
throws RemoteException,NotThere,AlreadyThere,NoSuchExit,LinkFailed;
/** Send a message of the form "David: hi everyone" */
public void speak(RemoteMudPerson speaker, String msg)
throws RemoteException, NotThere;
/** Send a message of the form "David laughs loudly" */
public void act(RemoteMudPerson speaker, String msg)
throws RemoteException, NotThere;
/** Add a new thing in this place */
public void createThing(RemoteMudPerson who, String name,
String description)
throws RemoteException, NotThere, AlreadyThere;
/** Remove a thing from this place */
public void destroyThing(RemoteMudPerson who, String thing)
throws RemoteException, NotThere, NoSuchThing;
/**
* Create a new place, bi-directionally linked to this one by an exit
**/
public void createPlace(RemoteMudPerson creator,
String exit, String entrance,
String name, String description)
throws RemoteException,NotThere,
ExitAlreadyExists,PlaceAlreadyExists;
/**
* Link this place (unidirectionally) to some existing place. The
* destination place may even be on another server.
**/
public void linkTo(RemoteMudPerson who, String exit,
String hostname, String mudname, String placename)
throws RemoteException, NotThere, ExitAlreadyExists, NoSuchPlace;
/** Remove an existing exit */
public void close(RemoteMudPerson who, String exit)
throws RemoteException, NotThere, NoSuchExit;
/**
* Remove this person from this place, leaving them nowhere.
* Send the specified message to everyone left in the place.
**/
public void exit(RemoteMudPerson who, String message)
throws RemoteException, NotThere;
/**
* Put a person in a place, assigning their name, and sending the
* specified message to everyone else in the place. The client should
* not make this method available to the user. They should use go()
* instead.
**/
public void enter(RemoteMudPerson who, String name, String message)
throws RemoteException, AlreadyThere;
/**
* Return the server object of the MUD that "contains" this place
* This method should not be directly visible to the player
**/
public RemoteMudServer getServer() throws RemoteException;
}
/**
* This is a generic exception class that serves as the superclass
* for a bunch of more specific exception types
**/
public static class MudException extends Exception {}
/**
* These specific exception classes are thrown in various contexts.
* The exception class name contains all the information about the
* exception; no detail messages are provided by these classes.
**/
public static class NotThere extends MudException {}
public static class AlreadyThere extends MudException {}
public static class NoSuchThing extends MudException {}
public static class NoSuchPerson extends MudException {}
public static class NoSuchExit extends MudException {}
public static class NoSuchPlace extends MudException {}
public static class ExitAlreadyExists extends MudException {}
public static class PlaceAlreadyExists extends MudException {}
public static class LinkFailed extends MudException {}
public static class BadPassword extends MudException {}
/**
* This constant is used as a prefix to the MUD name when the server
* registers the mud with the RMI Registry, and when the client looks
* up the MUD in the registry. Using this prefix helps prevent the
* possibility of name collisions.
**/
static final String mudPrefix = "je3.rmi.Mud.";
}
|