// Sample code file: Chat.java

// Warning: This code has been marked up for HTML

/*
   Copyright (c) 2000 Novell, Inc.  All Rights Reserved.

   THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. 
   USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT 
   ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK. 
   PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A 
   ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS 
   PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET, 
   DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S 
   PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S 
   CUSTOMERS WITH RESPECT TO THIS CODE. 
*/
 

package com.novell.Chat;

//Java imports
import java.util.Vector;
import java.util.Enumeration;
import java.util.Hashtable;
import java.beans.PropertyVetoException;
import javax.swing.JOptionPane;
import javax.swing.Icon;
import java.awt.Color;
import java.awt.Image;
import java.net.URL;
import java.awt.Toolkit;
import javax.swing.ImageIcon;
import java.util.Calendar;
import java.util.ResourceBundle;
import java.text.MessageFormat;

// ConsoleOne imports
import com.novell.application.console.snapin.*;
import com.novell.application.console.snapin.context.*;
import com.novell.application.console.snapin.scope.*;
import com.novell.application.console.util.objectentryselector.ObjectEntrySelector;
import com.novell.utility.nmsgbox.*;

// NDSNamespace and common library imports
import com.novell.admin.ns.*;
import com.novell.admin.ns.nds.*;
import com.novell.admin.common.exceptions.*;
import com.novell.admin.common.ui.*;
import com.novell.admin.common.snapins.*;

/**
 * This class maintains the needed global information that is used by Chat.
 * It creates and distroys the clients and servers used during the chatting
 * process and contains the methods used to interface with NDS.
 */
public class Chat implements ChatStatusListener, VetoableSnapinListener 
{
    /**
     * Only one instance of this class should exist.  This single
     * instance is kept here.  Having just one instance allows for
     * all snapins in this package to have access to the same set
     * of data.  This way an instance of the class doesn't have to
     * be passed from class to class.
     */
    private static Chat instance;
    
    /**
     * This is the resource that holds all the localized text for this snapin.
     */
    public static final ResourceBundle chatRes = ResourceBundle.getBundle("com.novell.Chat.resources.ChatResourceBundle");    
    
    /**
     * Snapin event raised when Chat is initialized.
     */
    public static final String EVENT_INIT_COMPLETE = "Chat Init Complete";
    
    /** 
     * The class name given to the Chat Room object.
     */
    public static final String CHATROOM_TYPE = "Chat Room";
    
    
    //  Theses are the names of the attribute which are created for the 
    //  Chat Room and user classes.
    
    /**
     * Name of the attribute used to hold the IP Address of a Chat Room.
     */
    public static final String ATTRIBUTE_IP = "ChatRoomIPAddress";
    
    /**
     * Name of the attribute used to hold the Port of a Chat Room.
     */
    public static final String ATTRIBUTE_PORT = "ChatPort";
    
    /**
     * Name of the attribute used to hold the name of the owner of a Chat Room.
     */
    public static final String ATTRIBUTE_OWNER = "ChatRoomOwner";
    
    /**
     * Name of the attribute used to hold the List of users in a Chat Room.
     */
    public static final String ATTRIBUTE_USERLIST = "ChatRoomUserList";
    
    /**
     * Name of the attribute used to hold the list of preferred users for a user object.
     */
    public static final String ATTRIBUTE_USER_PREFS = "ChatPreferredUsers";
    
    /**
     * Name of the attribute used to hold the port of a user.
     */
    public static final String ATTRIBUTE_USER_PORT = "ChatUserPort";
        
    /** 
     * These are the colors that are used when displaying the user name of a user
     * that is chatting.
     */         
    static final Color[] nameColors = new Color[]{Color.blue, Color.red, Color.green, 
                                                  Color.orange, Color.magenta, Color.cyan, 
                                                  Color.darkGray, Color.pink, Color.lightGray, 
                                                  Color.black};    
    /**
     * This is the default port to be used for chat room servers.
     */
    public static final int DEFAULT_PORT = 5000;
    
    /**
     * This is the default port to be used for user-to-user chatting.
     */
    public static final int USER_PORT = 4000;
    
    /**
     * The maximum number of times to increment the port value
     * while trying to create a socket.
     */
    public static final int PORT_MAX = 15;
        
    /**
     * Reference to the ConsoleOne Shell.
     */
    Shell shell;  
    
    /**
     * Reference to the NDS Namespace.
     */
    NDSNamespace nds;    
    
    /** 
     * The list of listeners who have been added to this class.
     */          
    Vector listeners = new Vector();
        
    /**
     * List of all of the valid schema trees.
     */
    Vector validSchemaList =  new Vector();
    
    /**
     * A list of all of the new attributes used by this snapin.
     * Used to make sure the schema is valid before attempting to access
     * on of these attributes.
     */
    Vector newAttributes = new Vector();
    
    /**
     * A cache of the user's identities for each tree it is authenticated to.
     * ChatUser objects are stored in this table.
     */
    Hashtable identityCache = new Hashtable();
    
    /**
     * A cache of the OnLineUsersDialogs that are associated with
     * each authenticated user.
     *
     * The online chat user's dialog allows the user to monitor what
     * other users are online.  It is global so that it can be redisplayed
     * without having to reload.
     */
    Hashtable onLineUserDlgCache = new Hashtable();
    
    /**
     * Holds a cache of all previously access icons to avoid reloading
     * the same icons.
     */
    Hashtable iconCache = new Hashtable();
    
    /**
     * A list of all of the connections currently active.
     * Both Server and Client.
     */
    Hashtable connectionList = new Hashtable();
            
    /**
     * True when the server is listening for connections.
     */
    boolean listening = false;            
        
    /**
     * True once chat is inititialized.
     */
    boolean initialized = false;
    
    //For giving rights to a trustee for an object, use these:
    private final static int RIGHTS_BROWSE =     0x00000001;
    private final static int RIGHTS_ADD =        0x00000002;
    private final static int RIGHTS_DELETE =     0x00000004;
    private final static int RIGHTS_RENAME =     0x00000008;
    private final static int RIGHTS_SUPERVISOR = 0x00000010; //16
    private final static int RIGHTS_INHERITABLE =0x00000040; //64
    private final static int ALL_OBJ_RIGHTS  =   0x0000001F; //31

    //For giving right too a trustee for an objects particular attribute, use these:
    private final static int ATT_RIGHTS_COMPARE =    0x00000001;
    private final static int ATT_RIGHTS_READ =       0x00000002;
    private final static int ATT_RIGHTS_WRITE =      0x00000004;
    private final static int ATT_RIGHTS_SELF =       0x00000008;
    private final static int ATT_RIGHTS_SUPERVISOR = 0x00000020; //32
    private final static int ATT_RIGHTS_INHERITABLE =0x00000040; //64
    private final static int ALL_PROP_RIGHTS =       0x0000002F; //47
    
    
    ///////////////////////////////////////////////////////////
    //                     M E T H O D S
    ///////////////////////////////////////////////////////////
    
    /**
     * Use this to method to get the single instance of this class.
     * Only one instance of this class should ever exist.  This single
     * instance is kept here.  Having just one instance allows for
     * all snapins in this package to have access to the same set
     * of data.  This way an instance of the class doesn't have to
     * be passed from class to class.
     *
     * @return The single instance of the Chat instance.
     */
    public static Chat getInstance()
    {
        //Creates it the first time it's called.
        if(instance == null)
        {
            instance = new Chat();
            
            //This is a list of all of the new attributes associated with this snapin.
            //When an attribute value is to be altered, this list is checked to see if
            //it is one of these attributes.  If it is, then it will check to make sure
            //that the schema has been extended to include that attribute.
            instance.newAttributes.addElement(ATTRIBUTE_PORT);   
            instance.newAttributes.addElement(ATTRIBUTE_IP);   
            instance.newAttributes.addElement(ATTRIBUTE_OWNER);   
            instance.newAttributes.addElement(ATTRIBUTE_USERLIST);   
            instance.newAttributes.addElement(ATTRIBUTE_USER_PREFS);     
            instance.newAttributes.addElement(ATTRIBUTE_USER_PORT);
        }
            
        return instance;
    }
    
    /**
     * Starts a thread that searches for the NDS Namespace.  Once NDS
     * is found, the identity of the user that is authenticated to each
     * tree is identified.  A ChatServer is then started for each of
     * these users in order to listen for Chat requests.
     */
    public void initialize()
    {                
        new InitializeChat();   
    } 
    
    
    /**
     * Called when ConsoleOne is being shutdown.  
     * This method will clean up any unfinished business. 
     */
    public void shutDown()
    {
        chatStatus(chatRes.getString("Status-ShutDown"));
        
        //First we clear all connections but the servers for Chat Rooms
        Enumeration connections = connectionList.elements();
        while(connections.hasMoreElements())
        {
            ChatConnection connection = (ChatConnection)connections.nextElement();
            
            if(!connection.getType().equals(CHATROOM_TYPE))
            {
                connection.close();
                connectionList.remove(connection);
            }                        
        }
        
        //Now we handle the Chat Rooms
        //When a user who is acting as the server for a chat room shuts down
        //ConsoleOne, someone else must take the role as server or the chat
        //room will stop functioning.  This will attempt to transfer the 
        //chat room's server to another user that is present in the chat room.
        connections = connectionList.elements();
        while(connections.hasMoreElements())
        {
            ChatConnection connection = (ChatConnection)connections.nextElement();
            
            ObjectEntry room = connection.getObjectEntry();
            if(room != null)
            {
                //Get the users that are in the room
                String[] users = getMultiValuedAttribute(room, ATTRIBUTE_USERLIST);   
                
                //Now try to transfer the server                   
                //If no transfer occurs, then the connection attributes will be deleted
                //leaving the chat room open to who ever enters it next.
                if(users == null || !serverTransfer((ChatServer)connection, room, users, users.length - 1))
                {
                    //delete IP, Port, and user list           
                    if(setAttributeValue(room, ATTRIBUTE_IP, null, false))
                        if(setAttributeValue(room, ATTRIBUTE_PORT, null, false))
                            if(setAttributeValue(room, ATTRIBUTE_USERLIST, null, false))
                            {
                                //Open the rights so the next user can gain control when
                                //he enters the room.
                                changeTrustee(room, null);
                            }
                }               
            }
            
            //now close the connection to the room
            connection.close();
        }
        
        //Deletes the ATTRIBUTE_PORT value from each user identity.
        Enumeration users = identityCache.elements();
        while(users.hasMoreElements())
        {
            ChatUser user = (ChatUser)users.nextElement();
            
            try
            {
                ObjectEntry usersOE = nds.getObjectEntry(user.getFullName());                
                setAttributeValue(usersOE, ATTRIBUTE_USER_PORT, null, false);
            }
            catch(SPIException e)
            { 
                NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Port Attribute Clear Error"));
                msg.show();  
            }                        
        }
                
        //Remove this class from the listener list.            
        shell.removeVetoableSnapinListener(this);                             
    }
    
    /**
     * Transfers a Chat Room server from this user to a user in the Chat Room User's List.
     *
     * @param server The ChatServer to transfer.
     * @param roomOE The ObjectEntry of the Chat Room being transfered.
     * @param users The list of users that are present in the chat room.
     * @param index The index to the user in the list to transfer the server to.
     * @return True if the transfer was successfull.
     */
    boolean serverTransfer(ChatServer server, ObjectEntry roomOE, String[] users, int index)
    {
        if(users != null && index >= 0)
        {        
            //Get the Fullname and the address key for the user that will become the server.
            String fullName = null;
            
            int index1 = users[index].indexOf('|');
            int index2 = 0;
            
            String key = users[index].substring(0, index1);
                    
            do{
                index2 = users[index].indexOf('|', index1);
                if(index2 != -1)
                    index1 = index2 + 1;
            }while(index2 != -1);
                    
            fullName = users[index].substring(index1);
                   
            //Change the trustee asignments so the new user will have write priviledges
            //to the room.       
            changeTrustee(roomOE, null);  
            
            //Requests the new user to take the Chat Room
            if(server.sendTransferMessage(key))
            {
                //Now change the trustee assignments so that the container does not have write 
                //priviledges to the room, only the owner and the new server.
                changeTrustee(roomOE, fullName);  
                
                //Tells all of the users in the room to reconnect to the new
                //Chat Room Server.
                server.sendTansferCommand();
                return true;
            }
            
            index--;        
            
            if(index >=0)        
                return serverTransfer(server, roomOE, users, index);   
        }        
        
        return false;
    }
  
    
    /**
     * Returns a reference to the NDSNamespace.
     *
     * @return The NDSNamespace.
     */
    public NDSNamespace getNDSNamespace()
    {        
        //Tries to get a reference to the NDS Namespace
        if(nds == null)
             nds = (NDSNamespace)shell.getNamespaceSnapin(NDSNamespace.name);
        
        if(nds == null)
            System.err.println("Looking for NDS.");
        
        return nds;   
    }
    
    /**
     * Returns the reference to the shell.
     *
     * @return The ConsoleOne Shell.
     */
    public Shell getShell()
    {
        return shell;
    }
    
    /**
     * Sets the shell reference.
     *
     * @param shell The consoleone shell.
     */
    public void setShell(Shell shell)
    {
        this.shell = shell;      
        
        //We will be informed when objects are deleted so that we can
        //watch for the deletion of Chat Rooms.
        this.shell.addVetoableSnapinListener(this, new String[]{NSObjectEvent.VETO_DEL_EVENT});
    }
    
    /**
     * Displays a dialog with the given error message.
     *
     * @param message The error message to display.
     */
    public void errorDialog(String message)
    {
        JOptionPane.showMessageDialog(shell.getShellFrame(), message, chatRes.getString("Chat"), JOptionPane.ERROR_MESSAGE);        
    }
    
    /**
     * Get's the color associated with the given color code index.
     * Used for the color of the user names.
     *
     * @param colorCode The index to the color to use.
     * @return The color.
     */
    public Color getNameColor(int colorCode)
    {
        //Accounts for role overs.
        while(colorCode >= nameColors.length)
            colorCode -= nameColors.length;
            
        return nameColors[colorCode];        
    }
    
    /**
     * Displays the Online Users Dialog.  If more than one user has been used to
     * authenticate to NDS, then the user will be asked which User object to use
     * for the list of Online users to monitor.
     */
    public void showOnlineUsersDialog()
    {
        try
        {
            OnlineUsersDlg dlg = null;
            ChatUser chatUserToUse = null;
            ObjectEntry userToUse = null;
            String userToUseName = null;
                        
            //If there are more than one user, then the user must select which one to use.
            if(identityCache.size() > 1)
            {
                userToUseName = ChooseUserDlg.get(getShell().getShellFrame(), identityCache);
                if(userToUseName != null)
                {
                    //First try to get it from the cache
                    dlg = (OnlineUsersDlg)onLineUserDlgCache.get(userToUseName);   
                        
                    //If it fails, then it will have to be loaded
                    if(dlg == null)
                    {
                        userToUse = nds.getObjectEntry(userToUseName);
                        dlg = new OnlineUsersDlg(userToUse);   
                        onLineUserDlgCache.put(userToUseName, dlg);  //now store it in the cache
                    }           
                    dlg.setVisible(true);
                }
            }
            //If there is only one user, then that one will be used.
            else if(identityCache.size() == 1)
            {
                Enumeration users = identityCache.elements();
                chatUserToUse = (ChatUser)users.nextElement();
                
                //First try to get it from the cache
                dlg = (OnlineUsersDlg)onLineUserDlgCache.get(chatUserToUse.getFullName());   
                    
                //If it fails, then it will have to be loaded    
                if(dlg == null)
                {
                    dlg = new OnlineUsersDlg(chatUserToUse.getObjectEntry());   
                    onLineUserDlgCache.put(chatUserToUse.getFullName(), dlg);
                }
                dlg.setVisible(true);
            }            
        }
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("User List Attribute Error"));
            msg.show();  
        }
    }
    
    /**
     * Uses the key to find a reference to the ChatServer.
     *
     * @param key The IP:Port combination that references the ChatServer desired.
     * @return The ChatServer, or null if not found.
     */
    public ChatServer getServer(String key)
    {
        Object obj = connectionList.get(key);
        if(obj instanceof ChatServer)
            return (ChatServer)obj;
                
        return null;        
    }
    
    /**
     * Checks to see if Chat has been initialized.
     *
     * @return True when Chat has been initialized.
     */
    public boolean isInitialized()
    {
        return initialized;   
    }
    
    /**
     * Finds the identity of the user that is authenticated 
     * to the tree the given ObjectEntry is from.
     *
     * @param sameTreeOE An object entry in the tree where the user
     *                   is authenticated.
     * @return The ChatUser Object for the found user.
     */
    public ChatUser getUserIdentity(ObjectEntry sameTreeOE)
    {             
        ChatUser chatUser = null;        
        
        try
        {
            if(sameTreeOE != null)
            {
                //Get the tree name from the ObjectEntry
                String fullName = sameTreeOE.getFullName();  
                
                String tree = sameTreeOE.getRoot().getName();                                
                    
                System.out.println("Getting Identity for tree " + tree);
            
                // Once the user's identity is found, it is stored in a table under 
                // the tree name it belongs to.  To avoid repetition, we will first
                // check the table to see if it has already been found.
                chatUser = (ChatUser)identityCache.get(tree);
                
                if(chatUser == null)
                {                        
                    //Get the user's OE
                    ObjectEntry userOE = ((AuthenticationNamespace)getNDSNamespace()).getAuthenticatedIdentity(sameTreeOE);
                    if(userOE != null)
                    {
                        //Get the IP Address this user is using
                        String userIPAddress = getIPAddress(userOE, chatRes.getString("Select IP Message"));
                        if(userIPAddress != null)
                        {
                            //Get information about the user
                            String userName = userOE.getName();
                            String userFullName = userOE.getFullName();                                                            
                            
                            //Create the ChatUser object to hold all this information.
                            chatUser = new ChatUser(userOE, userName, userFullName, userIPAddress);
                        
                            //Put the new identity in the cache
                            identityCache.put(tree, chatUser);
                        }
                        else
                            userOE = null;
                    }
                }
            } 
         }
         catch(SPIException e)
         {
             NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("User Identity Error"));
             msg.show();  
         }
         
         return chatUser;
    }
    
    /** 
     * Checks to see if the given chat room owner is this user.
     *
     * @param owner The chat room's owner to check for in the list.
     * @return True if it is in the identity cache.
     */
    public boolean isRoomOwner(String owner)
    {
        Enumeration identities = identityCache.elements();
        while(identities.hasMoreElements())
        {
            ChatUser user = (ChatUser)identities.nextElement();
            String name = nds.getUnrootedName(user.getObjectEntry());
            
            if(name.equals(owner))
                return true;
        }
        
        return false;
    }

    
    /**
     * Finds the identity of the user that is authenticated 
     * to each tree under the root.  A ChatServer is then started for each 
     * of these users in order to listen for Chat requests.
     *
     * @param listen True if the new servers should begin listening immediatly.
     */
    public void findAuthenticatedIdentities(boolean listen)
    {
        listening = listen;
        
        try
        {
            //Gets the root object entries in the NDS Tree. 
            //An ObjectEntry from the tree is needed to get the user's
            //identity.
            ObjectEntry[] rootEntries = ((NamespaceSnapin)getNDSNamespace()).getInitialObjectEntries();   
                
            System.out.println("OEs at root:  " + rootEntries.length);    
                
            //Cycles through these entries looking for the 'Top' of the trees.
            for(int i = 0; i < rootEntries.length; i++)
            {
                System.out.println("Examining OE " + rootEntries[i].getName());
                
                //The root of the tree won't have a parent.
                if(rootEntries[i].getParent() == null)
                {
                    //Gets the user identity associated with this tree.
                    ChatUser user = getUserIdentity(rootEntries[i]);
                    
                    //Creates the chat server and then starts it.
                    if(user != null)
                    {
                        ChatServer chatServer = createChatServer(USER_PORT, user, 0);
                        if(chatServer != null)  //Will equal null if it fails.
                        {
                            if(chatServer.getPort() != -1)
                            {
                                //start the server listening for connections
                                chatServer.listen(listen);  
                                
                                //Record in NDS the Port this user is listening at.
                                setAttributeValue(user.getObjectEntry(), ATTRIBUTE_USER_PORT, new Integer(chatServer.getPort()), false);
                                
                                //Store this server in our list of connections.
                                connectionList.put(chatServer.getKey(), chatServer);  
                            }
                        }
                    }
                }
            }                            
        }
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("User Identity Error"));
            msg.show();  
        }  
        catch (SnapinException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinException"), chatRes.getString("User Identity Error") + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
    }
   
    
    /**
     * Gets the given user's IP address.
     * If two IP addresses are found, the user will be asked 
     * to choose which one to use.  
     *
     * @param oe   The ObjectEntry of the user to find the IP address for.
     * @param when Explains what the IP address will be used for.
     * @return The given user's IP address.
     */
    public String getIPAddress(ObjectEntry oe, String when)
    {
        String ipAddress = null;
        
        //Get all of the IP address for this user.
        Vector ipList = getIPAddresses(oe);
        
        //If there's more than one, allow the user to select which one to use.
        if(ipList.size() > 1)
        {
            removeServerIP(oe, ipList);
            
            if(ipList.size() > 1)
                ipAddress = ChooseIPDialog.getIP(getShell().getShellFrame(), oe.getName(), when, ipList);
            else
                ipAddress = (String)ipList.firstElement();
        }
        else if(ipList.size() == 1)
            ipAddress = (String)ipList.firstElement();
                
        return ipAddress;
    }
    
    /**
     * Finds all of the IP addresses for the given user.
     *
     * @param oe The ObjectEntry of the user to find the IP address for.
     * @return A list of the user's IP addresses.
     */
    public Vector getIPAddresses(ObjectEntry oe)
    {
        Vector ipAddresses = new Vector();
        
        if(oe != null)
        {
            //Get the IP addresses
            String[] hexIPs = getMultiValuedAttribute(oe, "Network Address");
            
            if(hexIPs == null)
            {
                JOptionPane.showMessageDialog(shell.getShellFrame(), 
                    chatRes.getString("No IP Address 1") + oe.getFullName() + ".\n" + 
                    chatRes.getString("No IP Address 2"), chatRes.getString("Chat"), JOptionPane.ERROR_MESSAGE);
            }
            else
            {
                for(int i = 0; i < hexIPs.length; i++)
                {
                    //IP's with length > 15 are IPX
                    if(hexIPs[i].length() <= 15)           
                        //Convert the Hex IP returned from NDS to Decimal and then store it.
                        ipAddresses.addElement(convertHexIP(hexIPs[i])); 
                }
                
                if(ipAddresses.size() == 0)
                {
                    JOptionPane.showMessageDialog(shell.getShellFrame(), 
                        chatRes.getString("IPX Message 1") + oe.getFullName() + ".\n" + 
                        chatRes.getString("IPX Message 2"), 
                        chatRes.getString("Chat"), JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        
        return ipAddresses;
    }
    
    /**
     * When authenticating to NDS, the server's IP address is sometimes included
     * in the user's Network Address attribute along with the user's real IP address.
     * This method will remove the server's IP address from the IP address list.
     *
     * @param oe The entry the IP addresses are for.
     * @param ipList The list of IP addresses.
     */
    void removeServerIP(ObjectEntry oe, Vector ipList)
    {
        ObjectEntry serverOE = null;
        
        //The entries Message Server attribute contains the name of the server
        //this user is authenticated to.
        String serverName = getAttributeValueAsString(oe, "Message Server");
        
        if(serverName != null)
        {
            String tree = oe.getRoot().getName();            
            serverName = tree + "/" + serverName;
            
            //We'll now get the ObjectEntry for that server
            try{
                serverOE = nds.getObjectEntry(serverName);
            }catch(SPIException e)
            {
                System.err.println("SPIException in Chat.removeServerIP():  " + e.getMessage());
            }
            
            if(serverOE != null)
            {
                //Now we'll compare the IP address of the server to the list from the user.
                String[] ips = getMultiValuedAttribute(serverOE, "Network Address");
                if(ips != null)
                {
                    for(int i = 0; i < ips.length; i++)
                    {
                        if(ips[i].length() < 15)
                        {
                            ips[i] = ips[i].substring(4);
                            String decIP = convertHexIP(ips[i]);
                            Enumeration enum = ipList.elements();
                            while(enum.hasMoreElements())
                            {
                                String ip = (String)enum.nextElement();
                                
                                if(ip.equals(decIP))
                                {
                                    //Once a match is found, it will be removed and we can exit.
                                    ipList.removeElement(ip);
                                    return;
                                }
                            }     
                        }
                    }
                }
            }            
        }
    }
    
    /** 
    * The Network Address obtained from NDS uses a Hex format.  We must
    * convert that format to decimal so that it can be recognized by
    * the sockets.
    *
    * @param netAddress The Hex Net Address.
    * @return The IP Address in decimal.
    */
    private String convertHexIP(String netAddress)
    {
        String newAddress = new String();
            
        for(int i = 1; i < netAddress.length(); i+=2)
        {
            String hexChar = netAddress.substring(i,i+2);
            Integer dec = (new Integer(0)).valueOf(hexChar, 16);
            newAddress += dec.toString() + ".";
        }
        
        return newAddress.substring(0, newAddress.length() - 1);
    }
    
    
     
   /**
    * Finds and loads the given icon.
    * 
    * @param type The type, or name, of the icon to get.
    * @return The icon.
    */
   public Icon getIcon(String type)
   {
        //Checks the hashtable to see if this icon has already been loaded.
        Icon icon = (Icon)iconCache.get(type);
          
        if(icon == null)
        {            
            Image i = null;
            
            //All icons are stored here for this snapin.
            String path = "/com/novell/Chat/images/";
            
            path += type + ".gif";
            URL url = getClass().getResource(path);
            
            if(url != null)
            {
                i = Toolkit.getDefaultToolkit().getImage(url);
                icon = new ImageIcon(i);
                iconCache.put(type, icon);        // save it in our icon cache
            }
            else
            {
                System.err.println("Unable to load image " + path + " in Chat.");               
            }
        }
        return icon;
   }
    
    //////////////////////////////////////////////////////////////
    //                 NDS Interface Methods                    //
    //////////////////////////////////////////////////////////////
    
    /**
     * Verifies that a user is who he says he is by checking the
     * known IP address to the NetAddress stored in his user object.
     *
     * @param fullName The fullname of the user.
     * @param ipAddress The known IP Address.
     * @return True if it verifies, false if it fails.     
     */
    public boolean verifyUser(String fullName, String ipAddress)
    {
        try
        {
            //Get the ObjectEntry for this user
            ObjectEntry oe = getNDSNamespace().getObjectEntry(fullName);
            if(oe != null)
            {
                //If it's a Chat Room, then it will automatically verify
                //since a chat room's address is stored differently and
                //can't be spoofed.
                if(oe.getObjectType().getName().equals(CHATROOM_TYPE))
                    return true;
                    
                //Get the IP Address this user is logged in under.
                String[] oeIPAddress = getMultiValuedAttribute(oe, "Network Address");
                    
                if(oeIPAddress != null)
                {
                    for(int i = 0; i < oeIPAddress.length; i++)
                    {
                        //Convert it to Decimal
                        oeIPAddress[i] = convertHexIP(oeIPAddress[i]);
                                                    
                        if(ipAddress.indexOf('/') != -1)
                            ipAddress = ipAddress.substring(ipAddress.indexOf('/') + 1);                                            
                            
                        //Once a match is found, the user is verified.
                        if(ipAddress.equals(oeIPAddress[i]))
                            return true;
                    }
                }
            }
        }
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Verification Error"));
            msg.show();  
        }
        
        System.err.println("User " + fullName + " failed chat verification.");    
        return false; 
    }
    
    /**
     * Gives all users the rights to their ChatPort and ChatPrefferredUsers 
     * attributes.
     *
     * @param oe An objectEntry under the root to give the rights to.
     * @return True if successfull.
     */
    boolean giveRootRights(ObjectEntry oe)
    {          
        //True if this attribute wasn't present before and need to be 
        //added to the object.
        boolean newAttribute = false;  
        
        try
        {
            if(oe != null)
            {             
                //Gets the root object of this ObjectEntry
                ObjectEntry root = oe.getRoot();
                if(root != null)
                {                    
                        //Get the attribute's definition
                        NDSAttributeDefinition attDef = (NDSAttributeDefinition)nds.getAttributeDefinition(root, "ACL");                
                        if(attDef != null)  
                        {
                            //Get the attribute's syntax
                            NDSSyntax syntax = (NDSSyntax)attDef.getSyntax();     
                            if(syntax != null)
                            {
                                NSObject nsObject = nds.getDetails(root);            
                            if(nsObject != null)
                            {                                              
                                //Get the ObjectAttribute
                                    NDSObjectAttribute objAttrib = (NDSObjectAttribute)nsObject.getAttribute("ACL");
                                    
                                    //If the object doesn't already have this attribute, it create a new one.
                                    if(objAttrib == null)  
                                    {
                                        objAttrib = new NDSObjectAttribute(attDef);
                                        if(objAttrib == null)
                                            return false;
                                            
                                        newAttribute = true;     //can't add the attribute to the object until after it has a value        
                                    }
                                    
                                    boolean hasAllAttr = false;      //True if this ACL already has rights for [All Attriubute Rights].
                                    boolean hasEntryRights = false;  //True if this ACL already has rights for [Entry Rights].
                                    
                                    Enumeration enum = objAttrib.getValueComponents();
                                    while(enum.hasMoreElements())
                                    {
                                        ObjectACLFacade facade = new ObjectACLFacade((ValueList)enum.nextElement());
                                        if(facade != null)
                                        {
                                            if(facade.getSubjectName().equals("[Root]") && facade.getSubjectName().equals("[All Attributes Rights]"))
                                                hasAllAttr = true;
                                            else if(facade.getSubjectName().equals("[Root]") && facade.getSubjectName().equals("[Entry Rights]"))
                                                hasEntryRights = true;
                                        }
                                    }
                                                                                
                                ValueList rights = ObjectACLFacade.createValueList(ATTRIBUTE_USER_PORT, "[Root]", ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE | ATT_RIGHTS_INHERITABLE);                             
                                objAttrib.addComponent(rights);   
                                
                                rights = ObjectACLFacade.createValueList(ATTRIBUTE_USER_PREFS , "[Root]", ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE | ATT_RIGHTS_INHERITABLE);                             
                                objAttrib.addComponent(rights);   
                                
                                //If it already has rights for this, then we don't want to mess them up,
                                //otherwise we need to set the default.
                                if(!hasAllAttr)
                                {
                                    ValueList allAttr = ObjectACLFacade.createValueList("[All Attributes Rights]", "[Root]", ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ); 
                                    objAttrib.addComponent(allAttr);
                                }
                                
                                //If it already has rights for this, then we don't want to mess them up,
                                //otherwise we need to set the default.
                                if(!hasEntryRights)
                                {
                                    ValueList entry = ObjectACLFacade.createValueList("[Entry Rights]", "[Root]", RIGHTS_BROWSE);
                                    objAttrib.addComponent(entry); 
                                }
                                
                                //If it is a new attribute, then it can't be added to the object until its value
                                    //is assigned first.
                                    if(newAttribute)
                                        nsObject.addAttribute(objAttrib);
                                    
                                    nds.update(nsObject); //Update the object in NDS 
            
                                return true;
                            }
                        }
                    }
                }
            }
        }
        catch (SPIException e)
        {
            // NDS Problem encountered
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("[Root] Trustee Error"));
            msg.show();
        }
        catch (NamespaceException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("[Root] Trustee Error") + "\n" + e.toString(), NMsgBox.ERROR);
            msg.show();
        }
        catch (SnapinVetoException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinVetoException"), chatRes.getString("[Root] Trustee Error") + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        catch (IncompatibleComponentException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("IncompatibleComponentException"), chatRes.getString("[Root] Trustee Error") + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        
        return false;
    }
    
    /**
     * Changes the trustee assignments to the given user.
     *
     * @param oe The ObjectEntry whose trustee assignments will be changed.
     * @param userFullName The name of the user to change the assignments to.
     *                     If null, it assigns the rights to the container object.
     * @return True if successfull.
     */
    boolean changeTrustee(ObjectEntry oe, String userFullName)
    {
        boolean newAttribute = false;  //set to true when a new attribute is created.
        String unrootedName = null;    //The users unrooted name.
        
        if(userFullName != null)
            unrootedName = nds.getUnrootedName(oe);                                 
        
        try
        {
            if(oe != null)
            {                          
                //Checks to make sure the schema is properly extended, if not, it will extend it.
                if(checkSchema(oe, null))
                {                    
                        //Get the attribute's definition
                        NDSAttributeDefinition attDef = (NDSAttributeDefinition)nds.getAttributeDefinition(oe, "ACL");          
                        if(attDef != null)  
                        {
                            //Get the attribute's syntax
                            NDSSyntax syntax = (NDSSyntax)attDef.getSyntax();     
                            if(syntax != null)
                            {
                                NSObject nsObject = nds.getDetails(oe);            
                            if(nsObject != null)
                            {                                              
                                //Get the ObjectAttribute
                                    NDSObjectAttribute objAttrib = (NDSObjectAttribute)nsObject.getAttribute("ACL");
                                    
                                    //If the object doesn't already have this attribute, it create a new one.
                                    if(objAttrib == null)  
                                    {
                                        objAttrib = new NDSObjectAttribute(attDef);
                                        if(objAttrib == null)
                                            return false;
                                            
                                        newAttribute = true;     //can't add the attribute to the object until after it has a value        
                                    }
                                    else        
                                        objAttrib.removeAllComponents();   //Remove any previous values
                                        
                                String parentName = nds.getUnrootedName(oe.getParent());         
                                String ownerName = getAttributeValueAsString(oe, ATTRIBUTE_OWNER);
                                
                                //The Chat Room's owner always gets the same rights.
                                if(ownerName != null)
                                {
                                    ValueList allAttr = ObjectACLFacade.createValueList("[All Attributes Rights]", ownerName, ATT_RIGHTS_SUPERVISOR | ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ); 
                                    objAttrib.addComponent(allAttr);                                    
                                    ValueList entry = ObjectACLFacade.createValueList("[Entry Rights]", ownerName, RIGHTS_SUPERVISOR | RIGHTS_BROWSE); 
                                    objAttrib.addComponent(entry); 
                                }
                                
                                //Assign the rights to the given user if its a different user than
                                //the owner of the Chat Room.
                                if(unrootedName != null && !ownerName.equals(unrootedName))
                                {
                                    ValueList ip = ObjectACLFacade.createValueList(ATTRIBUTE_IP, unrootedName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                    objAttrib.addComponent(ip); 
                                    ValueList port = ObjectACLFacade.createValueList(ATTRIBUTE_PORT, unrootedName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                    objAttrib.addComponent(port); 
                                    ValueList list = ObjectACLFacade.createValueList(ATTRIBUTE_USERLIST, unrootedName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                    objAttrib.addComponent(list); 
                                    ValueList acl = ObjectACLFacade.createValueList("ACL", unrootedName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                    objAttrib.addComponent(acl); 
                                    ValueList allAttr = ObjectACLFacade.createValueList("[All Attributes Rights]", unrootedName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ);
                                    objAttrib.addComponent(allAttr); 
                                    ValueList entry2 = ObjectACLFacade.createValueList("[Entry Rights]", unrootedName, RIGHTS_BROWSE);
                                    objAttrib.addComponent(entry2); 
                                }
                                
                                //Assigns rights to the Chat Room's container object.
                                if(parentName != null)
                                {
                                    ValueList allAttr = ObjectACLFacade.createValueList("[All Attributes Rights]", parentName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ);
                                    objAttrib.addComponent(allAttr); 
                                    ValueList entry2 = ObjectACLFacade.createValueList("[Entry Rights]", parentName, RIGHTS_BROWSE);
                                    objAttrib.addComponent(entry2);
                                    
                                    //More rights are assigned if no new owner is specified.
                                    if(unrootedName == null)
                                    {
                                        ValueList ip = ObjectACLFacade.createValueList(ATTRIBUTE_IP, parentName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                        objAttrib.addComponent(ip); 
                                        ValueList port = ObjectACLFacade.createValueList(ATTRIBUTE_PORT, parentName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                        objAttrib.addComponent(port); 
                                        ValueList list = ObjectACLFacade.createValueList(ATTRIBUTE_USERLIST, parentName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                        objAttrib.addComponent(list); 
                                        ValueList acl = ObjectACLFacade.createValueList("ACL", parentName, ATT_RIGHTS_COMPARE | ATT_RIGHTS_READ | ATT_RIGHTS_WRITE);
                                        objAttrib.addComponent(acl); 
                                    }
                                }                                                          
                                        
                                    //If it is a new attribute, then it can't be added to the object until its value
                                    //is assigned first.
                                    if(newAttribute)
                                        nsObject.addAttribute(objAttrib);
                                    
                                    nds.update(nsObject); //Update the object in NDS 
            
                                return true;
                            }
                        }
                    }
                }
            }
        }
        catch (SPIException e)
        {
            // NDS Problem encountered
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Chat Room Trustee Error") + oe.getName());
            msg.show();
        }
        catch (NamespaceException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("Chat Room Trustee Error") + oe.getName() + "\n" + e.toString(), NMsgBox.ERROR);
            msg.show();
        }
        catch (SnapinVetoException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinVetoException"), chatRes.getString("Chat Room Trustee Error") + oe.getName() + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        
        return false;
    }
    
    
    /**
     * A simple method designed to change the value of an attribute.     
     *
     * @param oe The ObjectEntry that contains the attribute
     * @param attributeName The name of the attribute
     * @param newValue The new value to add.  If null, the attribute will be 
     *                 deleted from the object.
     * @param append True if the value should be appended to the list of ValueComponents.
     * @return True if successfull
    **/
    public boolean setAttributeValue(ObjectEntry oe, String attributeName, Object newValue, boolean append)
    {
        boolean newAttribute = false;  //set to true when a new attribute is created.
        
        try
        {
            if(oe != null)
            {                  
                //Checks to make sure the schema is properly extended, if not, it will extend it.
                if(checkSchema(oe, attributeName))
                {                    
                        //Get the attributes definition
                        NDSAttributeDefinition attDef = (NDSAttributeDefinition)nds.getAttributeDefinition(oe, attributeName);          
                        if(attDef != null)  
                        {
                            //Get the attributes syntax
                            NDSSyntax syntax = (NDSSyntax)attDef.getSyntax();     
                            if(syntax != null)
                            {
                                NSObject nsObject = nds.getDetails(oe);            
                            if(nsObject != null)
                            {              
                                //Get the object's attribute
                                    NDSObjectAttribute objAttrib = (NDSObjectAttribute)nsObject.getAttribute(attributeName);
                                    if(objAttrib == null)  //If the object doesn't already have this attribute, it adds it
                                    {
                                        objAttrib = new NDSObjectAttribute(attDef);
                                        if(objAttrib == null)
                                            return false;
                                        
                                        // We can't add the attribute to the object until after it has a value
                                        // so this flags tells that we will have to add the attribute later.                                    
                                        newAttribute = true;            
                                    }
                                    else if(!append)
                                    {
                                        objAttrib.removeAllComponents();   //Remove any previous values
                                        if(newValue == null)
                                            nsObject.deleteAttribute(attributeName);
                                    }
                
                                    if(newValue != null)
                                    {                               
                                        // If the value is already an NDSObjectAttribute, this we'll just
                                        // copy it right in.
                                        if(newValue instanceof NDSObjectAttribute)
                                        {                    
                                            Enumeration components = ((NDSObjectAttribute)newValue).getValueComponents();
                                            while(components.hasMoreElements())                                                                            
                                                objAttrib.addComponent((ValueComponent)components.nextElement());                   
                                        }
                                        else
                                        {
                                            //If the value is already ValueComponet, then we can just add it.
                                            if(newValue instanceof ValueComponent)
                                                objAttrib.addComponent((ValueComponent)newValue);
                                            else
                                            {
                                                //All other value types will need to be converted to a ValueComponent
                                                //before it can be added to the attribute.
                                                if(syntax == NDSSyntax.SYN_CI_STRING || syntax == NDSSyntax.SYN_INTEGER ||
                                                   syntax == NDSSyntax.SYN_DIST_NAME)
                                                {
                                                    ValueComponent VC = syntax.createValueComponent(newValue.toString());               
                                                    objAttrib.addComponent(VC); 
                                                }
                                            }
                                        }     
                                    }
                                    
                                    //If it is a new attribute, then it can't be added to the object until its value
                                    //is assigned first.
                                    if(newAttribute && (newValue != null))
                                        nsObject.addAttribute(objAttrib);
                                    
                                    nds.update(nsObject); //Update the object in NDS 
            
                                return true;
                            }
                        }
                    }
                }
            }
        }
        catch (SPIException e)
        {
            // NDS Problem encountered
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Set Attribute Error") + attributeName);
            msg.show();
        }
        catch (NamespaceException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.toString(), NMsgBox.ERROR);
            msg.show();
        }
        catch (ComponentCreationException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("ComponentCreationException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        catch (SnapinVetoException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinVetoException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        
        return false;
    }
    
    
    /**
     * A simple method designed to replace the given value from a 
     * multivalued attribute with a new value.
     *
     * @param oe The ObjectEntry that contains the attribute
     * @param attributeName The name of the attribute
     * @param oldValue The old value to remove.
     * @param newValue The new value to add.
     * @return True if successfull
    **/
    public boolean replaceAttributeValue(ObjectEntry oe, String attributeName, String oldValue, String newValue)
    {
        try
        {
            if(oe != null)
            {              
                //Checks to make sure the schema is properly extended, if not, it will extend it.
                if(checkSchema(oe, attributeName))
                {
                        //Get the attribute's definition
                        NDSAttributeDefinition attDef = (NDSAttributeDefinition)nds.getAttributeDefinition(oe, attributeName);          
                        if(attDef != null)  
                        {
                            //Get the attribute's syntax
                            NDSSyntax syntax = (NDSSyntax)attDef.getSyntax();     
                            if(syntax != null)
                            {
                                NSObject nsObject = nds.getDetails(oe);            
                            if(nsObject != null)
                            {              
                                //Get the ObjectAttribute
                                    NDSObjectAttribute objAttrib = (NDSObjectAttribute)nsObject.getAttribute(attributeName);
                                    if(objAttrib != null)  //If the object doesn't already have this attribute, it adds it
                                    {                     
                                        //Get the value components from the attribute.
                                    Enumeration enum = objAttrib.getValueComponents();            
                                    while(enum.hasMoreElements())
                                    {   
                                        ValueComponent vc = (ValueComponent)enum.nextElement();
                                        
                                        //Use the syntax to properly convert the attribute to a string
                                        String strValue = syntax.getStrategy().toString(vc);
                                        
                                        //If there's a match, then this value will be replaced with the new value.
                                        if(strValue.startsWith(oldValue))
                                        {
                                            //If newValue is null, it will simply be removed.
                                            if(newValue == null)
                                                objAttrib.removeComponent(vc);                                  
                                            else
                                            {
                                                strValue = newValue + strValue.substring(oldValue.length());
                                                ValueComponent newVC = NDSSyntax.getSyntax(syntax.getId()).createValueComponent(strValue);              
                                                objAttrib.replaceComponent(vc, newVC);
                                            }
                                                
                                                nds.update(nsObject); //Update the object in NDS 
                                                return true;
                                            }
                                    }
                                }                                                               
                            }
                        }
                    }
                }
            }
        }
        catch (SPIException e)
        {
            // NDS Problem encountered
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Set Attribute Error") + attributeName);
            msg.show();
        }
        catch (NamespaceException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.toString(), NMsgBox.ERROR);
            msg.show();
        }
        catch (ComponentCreationException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("ComponentCreationException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        catch (SnapinVetoException e)
        {
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinVetoException"), chatRes.getString("Set Attribute Error") + attributeName + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
        }
        
        return false;
    }
    
   
    
   /**
    * A Simple method designed to get the value of an attribute 
    * from a given object and return the value as a string regardless 
    * of its syntax.
    *
    * @param oe The ObjectEntry of the object in NDS.
    * @param attributeName The name of the attribute.
    * @return The value of the attribute, null if it fails.
    */
   public String getAttributeValueAsString(ObjectEntry oe, String attributeName)
   {
        String strValue = null;    
        
        try
        {
            if(oe != null)
            {
                //Checks to make sure the schema is properly extended, if not, it will extend it.
                if(checkSchema(oe, attributeName))
                {
                    NSObject nsObj = nds.getDetails(oe);            
                    if(nsObj != null)
                    {
                        //Get's the attribute from the object
                        NDSObjectAttribute attribute = (NDSObjectAttribute)nsObj.getAttribute(attributeName);
                        if(attribute != null)
                        {
                            //Get's the attribute's definition
                            NDSAttributeDefinition attributeDef = attribute.getNDSAttributeDefinition();
                            if(attributeDef != null)
                            {
                                //Get's the attribute's syntax
                                NDSSyntax syntax = (NDSSyntax)attributeDef.getSyntax();
                                if(syntax != null)
                                {
                                    //Get the value components from the attribute.
                                    Enumeration enum = attribute.getValueComponents();            
                                    if(enum.hasMoreElements())
                                    {   
                                        //We'll use only the first component
                                        ValueComponent value = (ValueComponent)enum.nextElement();
                                        
                                        //Use the syntax to properly convert the attribute to a string
                                        strValue = syntax.getStrategy().toString(value);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Get Attribute Error") + attributeName);
            msg.show();  
        }
        
        return strValue;            
   }    
   
   /**
    * For multi-valued attributes.
    * A Simple method designed to get the values of a provided attribute 
    * from a given object and return the value as a string array.
    *
    * @param oe The ObjectEntry of the object in NDS.
    * @param attributeName The name of the attribute.
    * @return The value of the attribute, null if it fails.
    */
   public String[] getMultiValuedAttribute(ObjectEntry oe, String attributeName)
   {
        String strValues[] = null;    
        
        try
        {
            if(oe != null)
            {
                //Checks to make sure the schema is properly extended, if not, it will extend it.
                if(checkSchema(oe, attributeName))
                {
                    NSObject nsObj = nds.getDetails(oe);            
                    if(nsObj != null)
                    {
                        //Get's the attribute from the object
                        NDSObjectAttribute attribute = (NDSObjectAttribute)nsObj.getAttribute(attributeName);
                        if(attribute != null)
                        {
                            //Get's the attribute's definition
                            NDSAttributeDefinition attributeDef = attribute.getNDSAttributeDefinition();
                            if(attributeDef != null)
                            {
                                //Get's the attribute's syntax
                                NDSSyntax syntax = (NDSSyntax)attributeDef.getSyntax();
                                if(syntax != null)
                                {
                                    int count = attribute.getComponentCount();
                                    if(count > 0)
                                    {
                                        strValues = new String[count];
                                    
                                        count = 0;
                                        //Get the value components from the attribute.
                                        Enumeration enum = attribute.getValueComponents();            
                                        while(enum.hasMoreElements())
                                        {   
                                            //We'll use only the first component
                                            ValueComponent value = (ValueComponent)enum.nextElement();
                                            
                                            //Use the syntax to properly convert the attribute to a string
                                            strValues[count] = syntax.getStrategy().toString(value);
                                            count++;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Get Attribute Error") + attributeName);
            msg.show();  
        }
                    
        return strValues;            
   }    
      
    /** 
     * Extends the NDS Schema if it hasn't already been extended.
     *
     * Classes to add:         Chat Room            (Used by the Chat Room Class)
     * Attributes to add:      ChatRoomIPAddress    (Used by the Chat Room Class)
     *                         ChatRoomOwner        (Used by the Chat Room Class)
     *                         ChatUserList         (Used by the Chat Room Class)
     *                         ChatPrefferredUsers  (Added to User Class)
     *                         ChatPort             (Added to User Class)   
     *                  
     * We first check to see if the Chat Room class has been added.  If so, then
     * we will assume that all of the attributes that are used by this class have
     * also be added to the schema.  Next we'll check if the attribute ChatPreferredUsers
     * has been added.  If this is present, then we will assume that the User object
     * has been properly extended. If either haven't, then the schema will be extended 
     * if this user has sufficent rights.
     *
     * @param oe An ObjectEntry in the tree where the schema needs to be extended.
     * @param attributeName The name of the attribute to check, null if it doesn't matter.
     * @return True if the schema is properly set up.
     */
    public boolean checkSchema(ObjectEntry oe, String attributeName)
    {
        boolean definedChatManager = false;
        boolean definedChatUser = false;
        String when = chatRes.getString("Schema Error 1");
        
        if(attributeName != null && !newAttributes.contains(attributeName))
            return true;
        
        try
        {    
            //First check to see if this tree has already been checked.            
            String treeName = oe.getRoot().getName();
            
            if(!validSchemaList.contains(treeName))
            {            
                // Get the Schema's Definition for the tree the ObjectEntry belongs to.
                NDSSchemaDefinition def = (NDSSchemaDefinition)nds.getSchemaDefinition(oe);
                
                if(!def.isClassDefined(CHATROOM_TYPE) || !def.isAttributeDefined(ATTRIBUTE_USER_PORT))
                {                    
                    //Ask for permission to extend the schema
                    if(JOptionPane.showConfirmDialog(shell.getShellFrame(), 
                            MessageFormat.format(chatRes.getString("Schema Confirmation"), new Object[]{treeName}), 
                            chatRes.getString("Chat"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
                    {                                         
                        if(!def.isClassDefined(CHATROOM_TYPE))
                        {
                            when = chatRes.getString("Schema Error 2");
                            createChatRoomClass(def);     //Create the ChatRoom Class
                        }
                        if(!def.isAttributeDefined(ATTRIBUTE_USER_PORT))
                        {
                            when = chatRes.getString("Schema Error 3");
                            createNewUserAttributes(def); //Add the attributes to the User Class    
                        }
                            
                        //The rights of the root object need to be adjusted so that all users
                        //will have access to their ChatPort and ChatPrefferredUsers attributes.
                        when = chatRes.getString("Schema Error 4");
                        giveRootRights(oe);                
                        
                        JOptionPane.showMessageDialog(getShell().getShellFrame(), chatRes.getString("Schema Success"), chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);
                    }
                    else
                        return false;
                }
                validSchemaList.addElement(treeName);
            }
        }
        catch(SPIException e)
        {
            // NDS Problem encountered
            e.printStackTrace();
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Extention Failure 1") + "\n" + when + "\n" + chatRes.getString("Extention Failure 2"));
            msg.show();
            
            return false;
        }
        catch(NamespaceException e)
        {            
            e.printStackTrace();
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("Extention Failure 1") + "\n" + when + "\n" + chatRes.getString("Extention Failure 2") + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
                        
                        return false;
        }
        catch(PropertyVetoException e)
        {
            // Advise that a PropertyChangeListener for the Schema has vetoed the new
            // changes by showing the message from the exception.
            e.printStackTrace();
            NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("PropertyVetoException"), chatRes.getString("Extention Failure 1") + "\n" + when + "\n" + chatRes.getString("Extention Failure 2") + "\n" + e.getMessage(), NMsgBox.INFO);
            msg.show();
            
            return false;
        }
                        
        return true;
    }
   
   /** 
     * Creates a ChatRoom object under the given object.
     *
     * @param parentOE The object to place it under.
     * @param cn The Common Name of this ChatRoom Object.
     * @param ipAddress The IPAddress it is listening at.
     * @param port The port it is listening at.
     * @param ownerOE The ObjectEntry of the owner.
     * @return The ChatUser ObjectEntry if successfull, null if it failed.
     */ 
    public ObjectEntry newChatRoom(ObjectEntry parentOE, String cn, String ipAddress, int port, ObjectEntry ownerOE)
    {
        ObjectEntry newRoom = null;
        String owner = nds.getUnrootedName(ownerOE);
        
        try
        {                            
            if(checkSchema(parentOE, null))
            {          
                // Make sure that an object with the same name doesn't already exist
                if(nds.doesExist(parentOE, cn))
                {
                    NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("Chat"), chatRes.getString("Duplicate Name Error"), NMsgBox.ERROR);
                    msg.show();
                }
                else
                {
                    // Get the attribute definitions
                    NDSAttributeDefinition cnDef = (NDSAttributeDefinition)nds.getAttributeDefinition(parentOE, "CN");
                    NDSAttributeDefinition ipDef = (NDSAttributeDefinition)nds.getAttributeDefinition(parentOE, ATTRIBUTE_IP);
                    NDSAttributeDefinition portDef = (NDSAttributeDefinition)nds.getAttributeDefinition(parentOE, ATTRIBUTE_PORT);
                    NDSAttributeDefinition nameDef = (NDSAttributeDefinition)nds.getAttributeDefinition(parentOE, ATTRIBUTE_OWNER);
                     
                    // Create the ObjectAttributes with the given values
                    //CN Attribute
                    NDSObjectAttribute cnAttrib = new NDSObjectAttribute(cnDef);
                    ValueComponent cnVC = NDSSyntax.SYN_CI_STRING.createValueComponent(cn);
                    cnAttrib.addComponent(cnVC);
                    //ChatRoomAddress Attribute
                    NDSObjectAttribute ipAttrib = new NDSObjectAttribute(ipDef);
                    ValueComponent ipVC = NDSSyntax.SYN_CI_STRING.createValueComponent(ipAddress);
                    ipAttrib.addComponent(ipVC);
                    //ChatRoomPort Attribute
                    NDSObjectAttribute portAttrib   = new NDSObjectAttribute(portDef);
                    ValueComponent portVC = NDSSyntax.SYN_INTEGER.createValueComponent(new Integer(port));
                    portAttrib.addComponent(portVC);
                    //ChatRoomOwner Attribute
                    NDSObjectAttribute nameAttrib = new NDSObjectAttribute(nameDef);
                    ValueComponent nameVC = NDSSyntax.SYN_DIST_NAME.createValueComponent(owner);
                    nameAttrib.addComponent(nameVC);
                     
                    // Copy into a vector
                    Vector attribs = new Vector(4);
                    attribs.addElement(cnAttrib);
                    attribs.addElement(ipAttrib);
                    attribs.addElement(portAttrib);
                    attribs.addElement(nameAttrib);
                    
                    // Create the ObjectEntry
                    newRoom = nds.createObjectEntry(parentOE, cn, CHATROOM_TYPE);
                    
                    // Create NSObject
                    NSObject newNSObj = nds.createNSObject(newRoom, attribs, 0);
                     
                    // Write to NDS
                    nds.create(newNSObj);                                         
                    
                    //Now create the ChatServer that will handle the new room's
                    //communications.
                    ChatUser chatRoom = new ChatUser(newRoom, cn, newRoom.getFullName(), ipAddress);                    
                    ChatServer chatServer = createChatServer(port, chatRoom, -1);
                    
                    if(chatServer != null)
                    {
                        chatServer.listen(true);  //Start the server listening
                        
                        connectionList.put(chatServer.getKey(), chatServer);
                        
                        //Set the port it is listening at.
                        if(chatServer.getPort() != port)
                            setAttributeValue(newRoom, ATTRIBUTE_PORT, new Integer(chatServer.getPort()), false);                                                
                            
                        //Set the trustee assignments for the room.
                        changeTrustee(newRoom, ownerOE.getFullName());                            
                    }
                    else 
                    {
                        //If it fails, then delete the room.
                        nds.delete(newRoom);
                        
                        NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("Chat"), chatRes.getString("Chat Room Socket Error"), NMsgBox.ERROR);
                        msg.show();
                        newRoom = null;
                    }       
                    
                    // Refresh ConsoleOne's selection
                    shell.refreshCurrentTreeSelection();
                }
            }
        }
        
        catch(SPIException e)
        {
            NSPIExceptionMsgBox msg = new NSPIExceptionMsgBox(shell, e, chatRes.getString("Create Room Error"));
            msg.show();
            return null;
        }
        catch(NamespaceException e)
        {
            NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("NamespaceException"), chatRes.getString("Create Room Error") + "\n" + e.toString(), NMsgBox.ERROR);
            msg.show();
            return null;
        }
        catch(ComponentCreationException e)
        {
            NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("ComponentCreationException"), chatRes.getString("Create Room Error") + "\n" + e.getMessage(), NMsgBox.ERROR);
            msg.show();
            return null;
        }
        catch(SnapinVetoException e)
        {
            // Advise that another snapin has vetoed the creation and get the message from
            // the Exception
            NMsgBox msg = new NMsgBox(shell.getShellFrame(), chatRes.getString("SnapinVetoException"), chatRes.getString("Create Room Error") + "\n" + e.getMessage(), NMsgBox.INFO);
            msg.show();
            return null;
        }
        
        return newRoom;   
    }
    
    /**
     * Transfers the given chat room to this user at the given
     * IP Address.
     *
     * @param roomOE The ObjectEntry of the Chat Room.
     * @param ipAddress The IP Address to change the Chat Room to.
     * @param startColor The user's name color to start with when assigning new
     *                   name colors.
     * @return True if the transfer was successfull.
     */    
    public boolean chatRoomTransfer(ObjectEntry roomOE, String ipAddress, int startColor)
    {                        
        String roomName = roomOE.getName();
        String roomFullName = roomOE.getFullName();
        
        ChatUser chatRoom = new ChatUser(roomOE, roomName, roomFullName, ipAddress);  
        
        //Create the ChatServer to handle the rooms communications
        ChatServer chatServer = createChatServer(DEFAULT_PORT, chatRoom, startColor);
                    
        if(chatServer != null)
        {
            chatServer.listen(true);  //Start the server listening for connections
            connectionList.put(chatServer.getKey(), chatServer);
            
            //Set the communication's attributes so others can connect.
            setAttributeValue(roomOE, ATTRIBUTE_PORT, new Integer(chatServer.getPort()), false);   
            setAttributeValue(roomOE, ATTRIBUTE_IP, ipAddress, false);  
            
            return true;
        }
        
        return false;    
    }
   
   /**
     * Creates the Chat Room class and the associated attributes.
     *
     * @param def The Schema's definition.
     * @exception SPIException
     * @exception NamespaceException
     * @exception PropertyVetoException
     */
    private void createChatRoomClass(NDSSchemaDefinition def) throws SPIException, NamespaceException, PropertyVetoException
    {
        int flags;
        NDSAttributeDefinition port;
        NDSAttributeDefinition owner;
        NDSAttributeDefinition ipAddress;
        NDSAttributeDefinition list;
        
        // We will first create all of the attributes that will be added to
        // this class.        
         
        // Create ChatServerPort Attribute
        // Syntax:  Integer
        if(def.isAttributeDefined(ATTRIBUTE_PORT))
            port = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_PORT);                
        else
        {
            flags = NDSAttributeFlags.SINGLE_VALUED | NDSAttributeFlags.SIZED;
            port = new NDSAttributeDefinition(ATTRIBUTE_PORT, 
                                                NDSSyntax.SYN_INTEGER,
                                                new NDSAttributeFlags(flags),
                                                0,
                                                10000,
                                                new String("").getBytes());
            def.putAttributeDefinition(port);   
        }
        
        // Create ChatUserFullName Attribute
        // Syntax:  Distingished Name
        if(def.isAttributeDefined(ATTRIBUTE_OWNER))
            owner = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_OWNER);                
        else
        {
            flags = NDSAttributeFlags.SINGLE_VALUED;
            owner = new NDSAttributeDefinition(ATTRIBUTE_OWNER, 
                                                NDSSyntax.SYN_DIST_NAME,
                                                new NDSAttributeFlags(flags),
                                                0,
                                                0,
                                                new String("").getBytes());
            def.putAttributeDefinition(owner);
        }
        
        // Create ChatServerIPAddress Attribute
        // Syntax:  Case Ignore String
        if(def.isAttributeDefined(ATTRIBUTE_IP))
            ipAddress = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_IP);                
        else
        {
            flags = NDSAttributeFlags.STRING | NDSAttributeFlags.SIZED | NDSAttributeFlags.SINGLE_VALUED;
            ipAddress = new NDSAttributeDefinition(ATTRIBUTE_IP, 
                                                NDSSyntax.SYN_CI_STRING,
                                                new NDSAttributeFlags(flags),
                                                0,
                                                20,
                                                new String("").getBytes());
            def.putAttributeDefinition(ipAddress);
        }
        
        // Create ChatStatus Attribute
        // Syntax:  Case Ignore String
        if(def.isAttributeDefined(ATTRIBUTE_USERLIST))
            list = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_USERLIST);                
        else
        {
            flags = NDSAttributeFlags.STRING | NDSAttributeFlags.SIZED;
            list = new NDSAttributeDefinition(ATTRIBUTE_USERLIST, 
                                                NDSSyntax.SYN_CI_STRING,
                                                new NDSAttributeFlags(flags),
                                                0,
                                                500,
                                                new String("").getBytes());
            def.putAttributeDefinition(list);
        }
        
        // Now we create the class.  We set up the containment and superclasses for the
        // new class and then the mandatory, naming and optional attributes.  Then
        // we define the class and add it to the schema.
        flags = NDSClassFlags.EFFECTIVE_CLASS;
          
        //The class can only be contained within these objects
        String[] containment = new String[]{"Organization", "Organizational Unit", "Country", "Locality"};  
        
        //Its only superclass is the Top class
        String[] superClasses   = new String[]{"Top"};
        
        //All of the attributes we created will be mandatory since they are all 
        //required for communicating between Users.
        AttributeDefinition[] mandatoryAttribs = new AttributeDefinition[]{def.getAttributeDefinition("CN"), owner};
        AttributeDefinition[] namingAttribs    = new AttributeDefinition[]{def.getAttributeDefinition("CN")};
        AttributeDefinition[] optionalAttribs  = new AttributeDefinition[]{list, ipAddress, port};
          
        //Now create the class
        NDSClassDefinition classDef = new NDSClassDefinition(CHATROOM_TYPE,
                                            new NDSClassFlags(flags),
                                            containment,
                                            superClasses,
                                            mandatoryAttribs,
                                            namingAttribs,
                                            optionalAttribs,
                                            new String("").getBytes());
        //Finally we add the class to the Schema                                    
        def.putClassDefinition(classDef); 
    }
    
    /**
     * Creates the new attributes to add to the user's class.
     *
     * @param def The Schema definition.
     * @exception SPIException
     * @exception NamespaceException
     * @exception PropertyVetoException
     */
    public void createNewUserAttributes(NDSSchemaDefinition def) throws SPIException, NamespaceException, PropertyVetoException
    {
        int flags;
        int attributesToAdd = 0;      
        boolean addPort = false;
        boolean addList = false;
        NDSAttributeDefinition port = null;
        NDSAttributeDefinition list = null;
        NDSClassDefinition userClass = def.getUnexpandedClassDefinition("User");
        
        //ChatPort Attribute
        if(!userClass.isClassUsingAttribute(ATTRIBUTE_USER_PORT))
        {
            addPort = true;
            attributesToAdd++;
        
            if(def.isAttributeDefined(ATTRIBUTE_USER_PORT))
                port = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_USER_PORT);                
            else
            {        
                // Create ChatUserPort Attribute
                // Syntax:  Integer
                flags = NDSAttributeFlags.SINGLE_VALUED;
                port = new NDSAttributeDefinition(ATTRIBUTE_USER_PORT, 
                                                        NDSSyntax.SYN_INTEGER,
                                                        new NDSAttributeFlags(flags),
                                                        0,
                                                        0,
                                                        new String("").getBytes());
                def.putAttributeDefinition(port);   
            }
        }
        
        //ChatPreferredUsers Attribute
        if(!userClass.isClassUsingAttribute(ATTRIBUTE_USER_PREFS))
        {
            addList = true;
            attributesToAdd++;
            
            if(def.isAttributeDefined(ATTRIBUTE_USER_PREFS))
                list = (NDSAttributeDefinition)def.getAttributeDefinition(ATTRIBUTE_USER_PREFS);
            else
            {
                // Create ChatPreferredUsers Attribute
                // Syntax:  Case Ignore String
                flags = NDSAttributeFlags.STRING;
                list = new NDSAttributeDefinition(ATTRIBUTE_USER_PREFS, 
                                                        NDSSyntax.SYN_CI_STRING,
                                                        new NDSAttributeFlags(flags),
                                                        0,
                                                        255,
                                                        new String("").getBytes());
                def.putAttributeDefinition(list);
            }
        }
    
        if(attributesToAdd > 0)
        {                 
            //Create a new array a little bigger so that it can hold the new attributes
            AttributeDefinition[] optionalAttribs = new AttributeDefinition[attributesToAdd];
            
            //Add the new ones to the array
            if(addPort && addList)
            {
                optionalAttribs[0] = port;
                optionalAttribs[1] = list;      
            }
            else if(addPort)
                optionalAttribs[0] = port;
            else if(addList)
                optionalAttribs[0] = list;
        
            //Now recreate the class with the new attributes.
            NDSClassDefinition classDef = new NDSClassDefinition("User",null,null,null,
                                                null,null,optionalAttribs,null);                                            
                                            
            //Finally we add the class to the Schema                                    
            def.putClassDefinition(classDef); 
        }
    }
   
   
    
    //////////////////////////////////////////////////////////
    //      Methods used to administer to the chat server.  //
    //////////////////////////////////////////////////////////
    
    
    /**
     * Creates a new Chat server for the given ChatUser.
     *
         * @param port The port on which this server will listen for connections.
         * @param user The ChatUser associated with this server.
         * @param startColor The color to start this user at in the ChatDialog.
     */
    public ChatServer createChatServer(int port, ChatUser user, int startColor)
    {
        ChatServer chatServer = new ChatServer(this, port, user, startColor); 
        
        //If it is unable to create a chat server, the port will be set to -1
        if(chatServer.getPort() == -1)
            chatServer = null;
        
        return chatServer;
    }
    
    /**
     * Starts the default chat servers listening for connections.
     *
     * @param listen True if the server should be listening.
     */
     public void listen(boolean listen)
     {
        Enumeration enum = connectionList.keys();
        while(enum.hasMoreElements())
        {
            //First we'll look for the star which identifies this 
            //connection as a user server.
            String key = (String)enum.nextElement();
            if(key.indexOf('*') != -1)
            {
                ChatServer chatServer = (ChatServer)connectionList.get(key);
                chatServer.listen(listen);
                
                //This will make it so other users can tell if this user has chat enabled or not.
                if(listen)
                    setAttributeValue(chatServer.getObjectEntry(), ATTRIBUTE_USER_PORT,  new Integer(chatServer.getPort()), false);
                else
                    setAttributeValue(chatServer.getObjectEntry(), ATTRIBUTE_USER_PORT,  null, false);
            }
        }   
          
        listening = listen;
     }
     
     /**
      * Checks to see if the chat servers are listening.
      *
      * @return True if listening.
      */
      public boolean isListening()
      {
         return listening;
      }         
                
                        
    ///////////////////////////////////////////////////////////////
    // These methods are used only to interface with the client. //
    ///////////////////////////////////////////////////////////////
   
   /** 
    * Called when a user requests to chat with either another user or
    * a Chat Room.  This will make a connection with the user and send
    * a request to chat message.
    *
    * @param oe The ObjectEntry to connect to.
    */
    public void requestChat(ObjectEntry oe)
    {
        if(oe != null)
        {                        
            String type = oe.getObjectType().getName();
            
            //Make sure the user's current identity is being used.  
            //Can change if shifting between trees.
            ChatUser chatUser = getUserIdentity(oe);     
            
            if(chatUser == null)
            {
                JOptionPane.showMessageDialog(shell.getShellFrame(), 
                        chatRes.getString("Init Chat Error 1"), 
                        chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);
            }
            else
            {                
                //For user to user connections
                if(type.equals("User"))
                {                
                    String oeName = oe.getName();
                    String userPort = getAttributeValueAsString(oe, ATTRIBUTE_USER_PORT);
                    
                    //You can tell if a user is online because they will have a value stored
                    //in the ChatPort attribute.
                    if(userPort == null)
                    {
                        JOptionPane.showMessageDialog(shell.getShellFrame(), 
                            MessageFormat.format(chatRes.getString("Not Enabled"), new Object[]{oeName}), 
                            chatRes.getString("Chat"), JOptionPane.INFORMATION_MESSAGE);
                    }
                    //Confirm the request
                    else if(JOptionPane.showConfirmDialog(shell.getShellFrame(), 
                            MessageFormat.format(chatRes.getString("Request User"), new Object[]{oeName}),
                            chatRes.getString("Chat"), JOptionPane.YES_NO_OPTION, 
                            JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
                    {                                   
                        chatStatus(chatRes.getString("Status-Connect"));
                        
                        //Get the correct IP Address.
                        String userIP = getIPAddress(oe, chatRes.getString("Select IP Request"));
                                            
                        if(userIP != null)
                        {
                            //Creates the client and then attempts to connect to the user.
                            ChatClient chatClient = new ChatClient(this, new ChatUser(oe, oe.getName(), oe.getFullName(), userIP), chatUser, (new Integer(userPort)).intValue());
                            chatClient.connect(ChatClient.NORMAL);   
                            
                            if(chatClient.isConnected())
                                connectionList.put(chatClient.getKey(), chatClient);     
                            else
                                System.err.println("Unable to Connect to " + oeName);
                        }
                    }
                }
                //For Chat Room Connections
                else if(type.equals(CHATROOM_TYPE))
                {
                    //Count the number of users in the room
                    String message;
                    String[] roomUsers = getMultiValuedAttribute(oe, ATTRIBUTE_USERLIST);
                    int num = 0;                                                
                    if(roomUsers != null)
                        num = roomUsers.length;
                        
                    
                    if(num == 0)
                        message = chatRes.getString("Chat Room Empty");
                    else if(num == 1)
                        message = chatRes.getString("One in Chat Room");
                    else
                        message = MessageFormat.format(chatRes.getString("Number in Room"), new Object[]{new Integer(num)});
                                                                
                    //Confirm the decision to connect.
                    if(JOptionPane.showConfirmDialog(shell.getShellFrame(), message + 
                            "\n" + MessageFormat.format(chatRes.getString("Enter Room?"), new Object[]{oe.getName()}), chatRes.getString("Chat"), JOptionPane.YES_NO_OPTION, 
                            JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
                    {
                        chatStatus(chatRes.getString("Status-Connect"));
                                                     
                        //Get the rooms IP Address                                    
                        String roomIPAddress = getAttributeValueAsString(oe, ATTRIBUTE_IP);
                        
                        //If the IPAddress is null, then no one is currently acting as the server
                        //of this room, so that privledge will be given to this user.
                        if(roomIPAddress == null)
                        {
                            chatRoomTransfer(oe, chatUser.getIPAddress(), -1);
                            changeTrustee(oe, chatUser.getFullName());
                            
                            roomIPAddress = getAttributeValueAsString(oe, ATTRIBUTE_IP);
                        }
                        
                        //Now get the Port the room is at.
                        String roomPort = getAttributeValueAsString(oe, ATTRIBUTE_PORT);
                        
                        if(roomIPAddress != null && roomPort != null)
                        {
                            //Creates the client and then attempts to connect.
                            ChatClient chatClient = new ChatClient(this, new ChatUser(oe, oe.getName(), oe.getFullName(), roomIPAddress), chatUser, (new Integer(roomPort)).intValue());
                            chatClient.connect(ChatClient.NORMAL); 
                            
                            if(chatClient.isConnected())
                                connectionList.put(chatClient.getKey(), chatClient);        
                            else
                                System.err.println("Unable to Connect to " + oe.getName());
                        }
                    }
                }
            }
        }
    }
    
    /**
     * Replaces the keys used in the connectionList table.  This is 
     * done when the Chat Room Server is transferred to another user.
     *
     * @param oldKey The key to replace.
     * @param newKey The key to use.
     * @param client The ChatClient associated with the key.
     */
    public void swapConnectionKeys(String oldKey, String newKey, ChatClient client)
    {
        Object oldClient = connectionList.remove(oldKey);   
                    
        connectionList.put(newKey, client);
    }
        
            
    ///////////////////////////////////////////////////////////////////////
    //  The following methods deal with the ChatStausListener interface. //
    ///////////////////////////////////////////////////////////////////////
        
    /**
     * Adds a ChatStatusListener to the Chat manager.
     *
     * @param listener The ChatStatusListener to add.
     */
    public void addChatListener(ChatStatusListener listener)
    {
        listeners.addElement(listener);
    }
    
    /**
     * Removes a ChatStatusListener from the Chat manager.
     *
     * @param listener The ChatStatusListener to remove.
     */
    public void removeChatListener(ChatStatusListener listener)
    {
        listeners.removeElement(listener);
    }
    

    /**
     * Implementation of the ChatStatusListener Interface.
     *
     * Called when the connection is lost by the client or when the server's
     * last connection is lost.        
     *
     * @param key The (IP:Port) key of the connection.
     */
    public void disconnected(String key)
    {    
        Object obj = connectionList.remove(key);
                        
        if(listening)
            chatStatus(chatRes.getString("Status-Ready"));
        else
            chatStatus(chatRes.getString("Status-Disabled"));
        
        //Relay it to all of the listeners
        Enumeration list = listeners.elements();
        while(list.hasMoreElements())
        {
            ChatStatusListener listener = (ChatStatusListener)list.nextElement();
            listener.disconnected(key);
        }
    }
        
    /**
     * Implementation of the ChatStatusListener Interface.  
     *
     * Called when the client or the server has a change of status.
     *
     * @param status The status message sent.
     */
    public void chatStatus(String status)
    {
        //Relay it to all of the listeners
        Enumeration list = listeners.elements();
        while(list.hasMoreElements())
        {
            ChatStatusListener listener = (ChatStatusListener)list.nextElement();
            listener.chatStatus(status);
        }
    }
    
    /**
     * Implementation of the VetoableSnapinListener Interface.
     *
     * Called when the user attempts to delete an object.
     * Only the owner of the Chat Room should have delete rights to
     * Chat Rooms.
     *
     * @param event The SnapinEvent.
     * @exception SnapinVetoException Thrown to veto the action.
     */
    public void vetoableSnapinListener(SnapinEvent event) throws SnapinVetoException
    {                
        ObjectEntryCollection entries = shell.getCurrentSelections();
        ObjectEntry oe = entries.getFirstElement();  
        
        //Make sure it's a Chat Room
        if(oe.getObjectType().getName().equals(CHATROOM_TYPE))
        {
            //Checks to see if there is anybody in the room
            String users[] = getMultiValuedAttribute(oe, ATTRIBUTE_USERLIST);            
            if(users != null && users.length > 0)
            {            
                //Inform the user of what is happening and ask him if he wants to proceed.
                if(JOptionPane.showConfirmDialog(shell.getShellFrame(), chatRes.getString("Delete Room?"), 
                        chatRes.getString("Chat"), JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)                                     
                {                                           
                    //Creates the client and then attempts to connect.
                    ChatUser user = getUserIdentity(oe);
                    String port = getAttributeValueAsString(oe, ATTRIBUTE_PORT); 
                    String ipAddress = getAttributeValueAsString(oe, ATTRIBUTE_IP);
                    
                    ChatClient chatClient = new ChatClient(this, new ChatUser(oe, oe.getName(), oe.getFullName(), ipAddress), user, (new Integer(port)).intValue());
                    
                    //Sends the message to force the shutdown of the Chat Room
                    chatClient.connect(ChatClient.FORCE_DOWN);   
                }
                else
                    throw new SnapinVetoException(chatRes.getString("Room Deleted"), event);            
            }
        }
    }
    
    
    /**
     * Implementation of Interface Runnable.
     *
     * Starts a thread that will first search for the NDS Namespace.
     * It must be searched for in this way, because it is sometimes not
     * available when initSnapin() is called in the ChatServiceSnapin.
     * Once NDS is found, the identity of the user that is authenticated 
     * to each tree is identified.  A ChatServer is then started for each 
     * of these users in order to listen for Chat requests.
     */     
    class InitializeChat implements Runnable
        {
            final int TIMES_TO_TRY = 10;  //The number of times to try to find NDS.
            Thread thread = new Thread(this);   //The thread that will handle the search.
            
            /**
             * Constructor
             *
             * Starts the thread.
             */
            public InitializeChat()
            {
                thread.start();
            }
            
            /**
             * Implementation of Runnable Interface.
             */
            public void run()
            {
                int count = 0;
                
                //Keep looking until it's either found or met the max number or tries.
                while(count < TIMES_TO_TRY)
                {
                    if(getNDSNamespace() != null)
                    {
                        try{
                            //Just because you have NDS doesn't mean that you have access to the root OEs.
                            ObjectEntry[] oes = ((NamespaceSnapin)getNDSNamespace()).getInitialObjectEntries();
                            if(oes != null && oes.length > 0)
                                break;
                        }catch(SnapinException ex){}
                    }
                    
                    try{
                        thread.sleep(2000);       //Wait 3 seconds, and then look again
                    }catch(InterruptedException e){}
                    
                    count++;
                }                               
                
                if(count >= TIMES_TO_TRY)
                {
                    chatStatus(chatRes.getString("Status-Disabled"));
                    thread.interrupt();
                }
                else                
                {
                    findAuthenticatedIdentities(true);  //Check each tree for user identities.   
                }
                
                //Notify any listeners that Chat has been initialized.
                if(identityCache.size() > 0 && connectionList.size() > 0)               
                {
                    initialized = true;    
                    shell.postSnapinEvent(new SnapinEvent(getInstance(), EVENT_INIT_COMPLETE));
                }
                else
                {
                    chatStatus(chatRes.getString("Status-Disabled"));
                }
            }
        }
}