Java tutorial
/* * 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."; }