je3.rmi.MudClient.java Source code

Java tutorial

Introduction

Here is the source code for je3.rmi.MudClient.java

Source

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