diff --git a/CopyOftrunk/apps/webchat/build/ant b/CopyOftrunk/apps/webchat/build/ant
new file mode 100644
index 000000000..b4319cf79
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/build/ant
@@ -0,0 +1,42 @@
+#! /bin/sh
+
+# //--------------------------------------------------------------------------//
+# // $RCSfile$
+# // $Revision$
+# // $Date$
+# //
+# // Standard Jive Software ant file. Do not change this file. If you do,
+# // you will have seven years of bad luck and bad builds.
+# //--------------------------------------------------------------------------//
+
+# //--------------------------------------------------------------------------//
+# // Uncomment the following lines if you wish to set JAVA_HOME in this script
+# //--------------------------------------------------------------------------//
+# JAVA_HOME=
+# EXPORT JAVA_HOME
+
+# //--------------------------------------------------------------------------//
+# // Check for the JAVA_HOME environment variable                             //
+# //--------------------------------------------------------------------------//
+if [ "$JAVA_HOME" != "" ] ; then
+    # //----------------------------------------------------------------------//
+    # // Create Ant's classpath                                               //
+    # //----------------------------------------------------------------------//
+    CP=$JAVA_HOME/lib/tools.jar:../../../build/ant.jar
+    
+    # //----------------------------------------------------------------------//
+    # // Run ant                                                              //
+    # //----------------------------------------------------------------------//
+    $JAVA_HOME/bin/java -classpath $CP -Dant.home=. org.apache.tools.ant.Main $@
+else
+    # //----------------------------------------------------------------------//
+    # // No JAVA_HOME error message                                           //
+    # //----------------------------------------------------------------------//
+    echo "Jive Forums Build Error:"
+    echo ""
+    echo "The JAVA_HOME environment variable is not set. JAVA_HOME should point"
+    echo "to your java directory, ie: /usr/local/bin/jdk1.3. You can set"
+    echo "this via the command line like so:"
+    echo "  export JAVA_HOME=/usr/local/bin/jdk1.3"
+fi
+
diff --git a/CopyOftrunk/apps/webchat/build/ant.bat b/CopyOftrunk/apps/webchat/build/ant.bat
new file mode 100644
index 000000000..fcbd96e70
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/build/ant.bat
@@ -0,0 +1,52 @@
+@echo off
+
+rem //------------------------------------------------------------------------//
+rem // $RCSfile$
+rem // $Revision$
+rem // $Date$
+rem //
+rem // Standard Jive Software ant.bat file. Do not change this file. If you do,
+rem // you will have seven years of bad luck and bad builds.
+rem //------------------------------------------------------------------------//
+
+rem //------------------------------------------------------------------------//
+rem // Uncomment the following if you wish to set JAVA_HOME in this bat file:
+rem //------------------------------------------------------------------------//
+rem SET JAVA_HOME=
+
+rem //------------------------------------------------------------------------//
+rem // Check for the JAVA_HOME environment variable
+rem //------------------------------------------------------------------------//
+if "%JAVA_HOME%" == "" goto noJavaHome
+
+rem //------------------------------------------------------------------------//
+rem // Make the correct classpath (should include the java jars and the
+rem // Ant jars)
+rem //------------------------------------------------------------------------//
+SET CP=%JAVA_HOME%\lib\tools.jar;..\..\..\build\ant.jar
+
+rem //------------------------------------------------------------------------//
+rem // Run Ant
+rem // Note for Win 98/95 users: You need to change "%*" in the following
+rem // line to be "%1 %2 %3 %4 %5 %6 %7 %8 %9"
+rem //------------------------------------------------------------------------//
+
+%JAVA_HOME%\bin\java -Xms32m -Xmx128m -classpath %CP% -Dant.home=. org.apache.tools.ant.Main %*
+goto end
+
+rem //------------------------------------------------------------------------//
+rem // Error message for missing JAVA_HOME
+rem //------------------------------------------------------------------------//
+:noJavaHome
+echo.
+echo Jive Forums Build Error:
+echo.
+echo The JAVA_HOME environment variable is not set. JAVA_HOME should point to
+echo your java directory, ie: c:\jdk1.3.1. You can set this via the command
+echo line like so:
+echo   SET JAVA_HOME=c:\jdk1.3
+echo.
+goto end
+
+:end
+
diff --git a/CopyOftrunk/apps/webchat/build/build.xml b/CopyOftrunk/apps/webchat/build/build.xml
new file mode 100644
index 000000000..eb9095f21
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/build/build.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+
+<!--
+    $RCSfile$
+    $Revision$
+    $Date$
+-->
+
+<project name="WebChat" default="all" basedir="..">
+
+    <property file="${basedir}/build/webchat-build.properties" />
+    <property name="smack.home" value="${basedir}/../../../smack" />
+    <property name="compile.dir" value="${basedir}/build/temp" />
+    <property name="jar.dest.dir" value="${basedir}/build/WEB-INF/lib" />
+    <property name="jar.name" value="webchat" />
+    <property name="war.dest.dir" value="${basedir}" />
+    <property name="war.name" value="webchat" />
+
+
+    <path id="dependencies">
+        <!-- build jars -->
+        <fileset dir="${smack.home}" includes="smack.jar" />
+        <fileset dir="${basedir}/build/lib" includes="*.jar" />
+    </path>
+
+
+    <patternset id="web.filetypes">
+        <include name="**/*.jsp" />
+        <include name="**/*.js" />
+        <include name="**/*.html" />
+        <include name="**/*.gif" />
+        <include name="**/*.css" />
+    </patternset>
+
+
+    <target name="init">
+        <mkdir dir="${compile.dir}" />
+        <mkdir dir="${jar.dest.dir}" />
+        <!-- call smack jar process -->
+        <ant antfile="build/build.xml" dir="${smack.home}" target="jar" inheritAll="false" />
+    </target>
+
+
+    <target name="clean">
+        <delete dir="${compile.dir}" />
+        <delete dir="${jar.dest.dir}" />
+        <!-- call smack jar process -->
+        <ant antfile="build/build.xml" dir="${smack.home}" target="clean" inheritAll="false" />
+    </target>
+
+
+    <target name="compile" depends="init">
+        <javac
+            destdir="${compile.dir}"
+            includeAntRuntime="no"
+            debug="on"
+            classpathref="dependencies"
+        >
+            <src path="${basedir}/source/java" />
+        </javac>
+    </target>
+
+
+    <target name="jar" depends="compile">
+        <jar destfile="${jar.dest.dir}/${jar.name}.jar"
+            basedir="${compile.dir}"
+            includes="**/*.class"
+         />
+    </target>
+
+
+    <target name="war" depends="jar">
+        <war warfile="${war.dest.dir}/${war.name}.war"
+            webxml="${basedir}/source/config/WEB-INF/web.xml"
+        >
+            <lib dir="${jar.dest.dir}" includes="*.jar" />
+            <lib dir="${smack.home}" includes="smack.jar" />
+            <zipfileset dir="${basedir}/source/web">
+                <patternset refid="web.filetypes" />
+            </zipfileset>
+        </war>
+    </target>
+
+
+    <target name="deploywar" depends="war">
+        <copy todir="${deploy.war.dir}" overwrite="${overwrite}">
+            <fileset dir="${war.dest.dir}" includes="${war.name}.war" />
+        </copy>
+    </target>
+
+</project>
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/build/lib/servlet.jar b/CopyOftrunk/apps/webchat/build/lib/servlet.jar
new file mode 100644
index 000000000..cda22d2a4
Binary files /dev/null and b/CopyOftrunk/apps/webchat/build/lib/servlet.jar differ
diff --git a/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml b/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml
new file mode 100644
index 000000000..98cbd9e49
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/config/WEB-INF/web.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+    
+<web-app>
+
+    <display-name>WebChat</display-name>
+    <description>Smack-powered WebChat Application</description>
+      
+    <context-param>
+        <param-name>host</param-name>
+        <param-value>jivesoftware.com</param-value>
+    </context-param>
+
+    <!--
+      *******************
+        ALL OF THE FOLLOWING context-params are optional, and are listed with their
+            default values. Since they are optional, they needn't be included in this
+            file if you're satisfied with the default values; they're listed here
+            for informational purposes.
+            *******************
+    -->
+
+    <!-- allow users to logon anonymously? -->
+    <context-param>
+        <param-name>allowAnonymous</param-name>
+        <param-value>true</param-value>
+    </context-param>
+
+    <!-- allow users to create new accounts? -->
+    <context-param>
+        <param-name>allowAccountCreation</param-name>
+        <param-value>true</param-value>
+    </context-param>
+
+    <!-- allow users to login using their username/password? -->
+    <context-param>
+        <param-name>allowLogin</param-name>
+        <param-value>true</param-value>
+    </context-param>
+
+    <!-- the url for the logo image in the chat window (path relative to the .jsp files) -->
+    <context-param>
+        <param-name>logoFilename</param-name>
+        <param-value>images/logo.gif</param-value>
+    </context-param>
+
+    <!-- the color of the text for chat window room announcements -->
+    <context-param>
+        <param-name>chat.announcement-color</param-name>
+        <param-value>#009d00</param-value>
+    </context-param>
+
+    <!-- the color of the text for the dialog label associated with the user of the web client -->
+    <context-param>
+        <param-name>chat.owner-label-color</param-name>
+        <param-value>#aa0000</param-value>
+    </context-param>
+
+    <!-- the color of the text for the dialog label associated with other chat participants -->
+    <context-param>
+        <param-name>chat.participant-label-color</param-name>
+        <param-value>#0000aa</param-value>
+    </context-param>
+
+    <!-- the color of the text for the dialog in the chat window -->
+    <context-param>
+        <param-name>chat.text-color</param-name>
+        <param-value>#434343</param-value>
+    </context-param>
+
+    <!-- the color of error message text -->
+    <context-param>
+        <param-name>error.text-color</param-name>
+        <param-value>#ff0000</param-value>
+    </context-param>
+
+    <!-- the color of the text for unvisited links -->
+    <context-param>
+        <param-name>link.color</param-name>
+        <param-value>#045d30</param-value>
+    </context-param>
+
+    <!-- the color of the text for links with the pointer hovering over them -->
+    <context-param>
+        <param-name>link.hover-color</param-name>
+        <param-value>#350000</param-value>
+    </context-param>
+
+    <!-- the color of the text for already visited links -->
+    <context-param>
+        <param-name>link.visited-color</param-name>
+        <param-value>#3b3757</param-value>
+    </context-param>
+
+    <!-- the color of the background of all pages -->
+    <context-param>
+        <param-name>body.background-color</param-name>
+        <param-value>#ffffff</param-value>
+    </context-param>
+
+    <!-- the default color of the text on all pages -->
+    <context-param>
+        <param-name>body.text-color</param-name>
+        <param-value>#362f2d</param-value>
+    </context-param>
+
+    <!-- the color of the chat window divider between the participant listing and the chat -->
+    <context-param>
+        <param-name>frame.divider-color</param-name>
+        <param-value>#83272b</param-value>
+    </context-param>
+
+    <!-- the color of the form element buttons -->
+    <context-param>
+        <param-name>button.color</param-name>
+        <param-value>#d6dfdf</param-value>
+    </context-param>
+
+    <!-- the color of the text in the form element buttons -->
+    <context-param>
+        <param-name>button.text-color</param-name>
+        <param-value>#333333</param-value>
+    </context-param>
+
+    <!-- the color of the background in the form element text fields -->
+    <context-param>
+        <param-name>textfield.color</param-name>
+        <param-value>#f7f7fb</param-value>
+    </context-param>
+
+    <!-- the color of the text in the form element text fields and textareas -->
+    <context-param>
+        <param-name>textfield.text-color</param-name>
+        <param-value>#333333</param-value>
+    </context-param>
+
+
+    <!-- Session listener -->
+    <listener>
+        <listener-class>org.jivesoftware.webchat.JiveChatServlet</listener-class>
+    </listener>
+
+    <!-- Servlets -->
+    <servlet>
+        <servlet-name>ChatServlet</servlet-name>
+        <servlet-class>org.jivesoftware.webchat.JiveChatServlet</servlet-class>
+        <load-on-startup>1</load-on-startup>        
+    </servlet>
+
+    <!-- Servlet mappings -->
+    <servlet-mapping>
+        <servlet-name>ChatServlet</servlet-name>
+        <url-pattern>/ChatServlet/*</url-pattern>
+    </servlet-mapping>
+    
+    <session-config>
+      <session-timeout>3</session-timeout>
+    </session-config>
+
+    <!-- Welcome file list -->
+    <welcome-file-list>
+        <welcome-file>index.jsp</welcome-file>
+    </welcome-file-list>
+
+</web-app>
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java
new file mode 100644
index 000000000..293edf0ff
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/EmoticonFilter.java
@@ -0,0 +1,292 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2003 Jive Software. All rights reserved.
+ *
+ * This software is the proprietary information of Jive Software. Use is subject to license terms.
+ */
+
+package org.jivesoftware.webchat;
+
+/**
+ * A Filter that converts ASCII emoticons into image equivalents.
+ * This filter should only be run after any HTML stripping filters.<p>
+ *
+ * The filter must be configured with information about where the image files
+ * are located. A table containing all the supported emoticons with their
+ * ASCII representations and image file names is as follows:<p>
+ *
+ * <table border=1>
+ *     <tr><td><b>Emotion</b></td><td><b>ASCII</b></td><td><b>Image</b></td></tr>
+ *
+ *     <tr><td>Happy</td><td>:) or :-)</td><td>happy.gif</td></tr>
+ *     <tr><td>Sad</td><td>:( or :-(</td><td>sad.gif</td></tr>
+ *     <tr><td>Grin</td><td>:D</td><td>grin.gif</td></tr>
+ *     <tr><td>Love</td><td>:x</td><td>love.gif</td></tr>
+ *     <tr><td>Mischief</td><td>;\</td><td>mischief.gif</td></tr>
+ *     <tr><td>Cool</td><td>B-)</td><td>cool.gif</td></tr>
+ *     <tr><td>Devil</td><td>]:)</td><td>devil.gif</td></tr>
+ *     <tr><td>Silly</td><td>:p</td><td>silly.gif</td></tr>
+ *     <tr><td>Angry</td><td>X-(</td><td>angry.gif</td></tr>
+ *     <tr><td>Laugh</td><td>:^O</td><td>laugh.gif</td></tr>
+ *     <tr><td>Wink</td><td>;) or ;-)</td><td>wink.gif</td></tr>
+ *     <tr><td>Blush</td><td>:8}</td><td>blush.gif</td></tr>
+ *     <tr><td>Cry</td><td>:_|</td><td>cry.gif</td></tr>
+ *     <tr><td>Confused</td><td>?:|</td><td>confused.gif</td></tr>
+ *     <tr><td>Shocked</td><td>:O</td><td>shocked.gif</td></tr>
+ *     <tr><td>Plain</td><td>:|</td><td>plain.gif</td></tr>
+ *  </table>
+ *
+ * Note: special thanks to August Harrison for implementing an earlier version of this filter.
+ */
+public class EmoticonFilter {
+
+    private static String imageHappy    = "happy.gif";
+    private static String imageSad      = "sad.gif";
+    private static String imageGrin     = "grin.gif";
+    private static String imageLove     = "love.gif";
+    private static String imageMischief = "mischief.gif";
+    private static String imageCool     = "cool.gif";
+    private static String imageDevil    = "devil.gif";
+    private static String imageSilly    = "silly.gif";
+    private static String imageAngry    = "angry.gif";
+    private static String imageLaugh    = "laugh.gif";
+    private static String imageWink     = "wink.gif";
+    private static String imageBlush    = "blush.gif";
+    private static String imageCry      = "cry.gif";
+    private static String imageConfused = "confused.gif";
+    private static String imageShocked  = "shocked.gif";
+    private static String imagePlain    = "plain.gif";
+    private static String imageURL      = "images/emoticons";
+
+    // Placeholders for the built image tags
+    private static String imgHappy;
+    private static String imgSad;
+    private static String imgGrin;
+    private static String imgLove;
+    private static String imgMischief;
+    private static String imgCool;
+    private static String imgDevil;
+    private static String imgSilly;
+    private static String imgAngry;
+    private static String imgLaugh;
+    private static String imgWink;
+    private static String imgBlush;
+    private static String imgCry;
+    private static String imgConfused;
+    private static String imgShocked;
+    private static String imgPlain;
+
+    public EmoticonFilter() {
+        buildImageTags();
+    }
+
+    public String applyFilter(String string) {
+        if (string == null || string.length() < 1) {
+            return string;
+        }
+
+        int length = string.length();
+        StringBuffer filtered = new StringBuffer(string.length() + 100);
+        char[] chars = string.toCharArray();
+
+        int	length1 = length - 1;
+        int	length2 = length - 2;
+
+        int index = -1, i = 0, oldend = 0;
+		String imgTag;
+
+        // Replace each of the emoticons, expanded search for performance
+        while (++index < length1) {
+			// no tag found yet...
+			imgTag = null;
+
+			switch (chars[i = index]) {
+                case ']':
+                    // "]:)"
+                    if (i < length2 && chars[++i] == ':' && chars[++i] == ')') {
+                        imgTag = imgDevil;
+                    }
+                    break;
+                case ':':
+                    switch (chars[++i]) {
+                        case ')':
+                            // ":)"
+                            imgTag = imgHappy;
+                            break;
+                        case '-':
+                            // ":-)"
+                            if (i < length1 && chars[++i] == ')') {
+                                imgTag = imgHappy;
+                            }
+                            // ":-("
+                            else if (chars[i] == '(') {
+                                imgTag = imgSad;
+                            }
+                            break;
+                        case '(':
+                            // ":("
+                            imgTag = imgSad;
+                            break;
+                        case 'D':
+                            // ":D"
+                            imgTag = imgGrin;
+                            break;
+                        case 'x':
+                            // ":x"
+                            imgTag = imgLove;
+                            break;
+                        case 'p':
+                            // ":p"
+                            imgTag = imgSilly;
+                            break;
+                        case '^':
+                            // ":^O"
+                            if (i < length1 && chars[++i] == 'O') {
+                                imgTag = imgLaugh;
+                            }
+                            break;
+                        case '8':
+                            // ":8}"
+                            if (i < length1 && chars[++i] == '}') {
+                                imgTag = imgBlush;
+                            }
+                            break;
+                        case '_':
+                            // ":_|"
+                            if (i < length1 && chars[++i] == '|') {
+                                imgTag = imgCry;
+                            }
+                            break;
+                        case 'O':
+                            // ":O"
+                            imgTag = imgShocked;
+                            break;
+                        case '|':
+                            // ":|"
+                            imgTag = imgPlain;
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case ';':
+                    switch (chars[++i]) {
+                        case ')':
+                            // ";)"
+                            imgTag = imgWink;
+                            break;
+                        case '-':
+                            // ";-)"
+                            if (i < length1 && chars[++i] == ')') {
+                                imgTag = imgWink;
+                            }
+                            break;
+                        case '\\':
+                            // ";\\"
+                            imgTag = imgMischief;
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case 'B':
+                        // "B-)"
+                        if (i < length2 && chars[++i] == '-' && chars[++i] == ')') {
+                            imgTag = imgCool;
+                        }
+                    break;
+                case 'X':
+                        // "X-("
+                        if (i < length2 && chars[++i] == '-' && chars[++i] == '(') {
+                            imgTag = imgAngry;
+                        }
+                    break;
+                case '?':
+                        // "?:|"
+                        if (i < length2 && chars[++i] == ':' && chars[++i] == '|') {
+                            imgTag = imgConfused;
+                        }
+                    break;
+                default:
+                    break;
+            }
+
+			// if we found one, replace
+			if (imgTag != null) {
+		        filtered.append(chars, oldend, index-oldend);
+		        filtered.append(imgTag);
+
+				oldend = i + 1;
+		        index = i;
+        	}
+        }
+
+        if (oldend < length) {
+	        filtered.append(chars, oldend, length-oldend);
+        }
+
+        return filtered.toString();
+    }
+
+    /**
+     * Returns the base URL for emoticon images. This can be specified as
+     * an absolute or relative path.
+     *
+     * @return the base URL for smiley images.
+     */
+    public String getImageURL() {
+        return imageURL;
+    }
+
+    /**
+     * Sets the base URL for emoticon images. This can be specified as
+     * an absolute or relative path.
+     *
+     * @param imageURL the base URL for emoticon images.
+     */
+    public void setImageURL(String imageURL) {
+		if (imageURL != null && imageURL.length() > 0) {
+			if (imageURL.charAt(imageURL.length()-1) == '/') {
+                imageURL = imageURL.substring(0, imageURL.length()-1);
+			}
+		}
+        this.imageURL = imageURL;
+
+        // rebuild the image tags
+        buildImageTags();
+    }
+
+    /**
+     * Build image tags
+     */
+    private void buildImageTags() {
+        imgHappy    = buildURL(imageHappy);
+        imgSad      = buildURL(imageSad);
+        imgGrin     = buildURL(imageGrin);
+        imgLove     = buildURL(imageLove);
+        imgMischief = buildURL(imageMischief);
+        imgCool     = buildURL(imageCool);
+        imgDevil    = buildURL(imageDevil);
+        imgSilly    = buildURL(imageSilly);
+        imgAngry    = buildURL(imageAngry);
+        imgLaugh    = buildURL(imageLaugh);
+        imgWink     = buildURL(imageWink);
+        imgBlush    = buildURL(imageBlush);
+        imgCry      = buildURL(imageCry);
+        imgConfused = buildURL(imageConfused);
+        imgShocked  = buildURL(imageShocked);
+        imgPlain    = buildURL(imagePlain);
+    }
+
+	/**
+     * Returns an HTML image tag using the base image URL and image name.
+     */
+    private String buildURL(String imageName) {
+	    String tag = "<img border='0' src='" + imageURL + "/" + imageName + "'>";
+
+		return tag;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java
new file mode 100644
index 000000000..b509da321
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/JiveChatServlet.java
@@ -0,0 +1,729 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2003 Jive Software. All rights reserved.
+ *
+ * This software is the proprietary information of Jive Software. Use is subject to license terms.
+ */
+
+package org.jivesoftware.webchat;
+
+import java.io.*;
+import java.util.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.StringUtils;
+
+
+/**
+ * An extension of HttpServlet customized to handle transactions between N webclients
+ *  and M chats located on a given XMPP server. While N >= M in the case of group chats,
+ *  the code will currently, never the less, hold onto N connections to the XMPP server.<br>
+ *
+ * It is assumed that all JSP pages are in the context root. The init params should be:
+ * <ul>
+ *      <li> host</li>
+ *      <li> port (optional)</li>
+ *      <li> SSLEnabled (optional)</li>
+ * </ul>
+ *
+ * @author Bill Lynch
+ * @author loki der quaeler
+ */
+public class JiveChatServlet
+    extends HttpServlet
+    implements HttpSessionListener, PacketListener {
+
+    static public final String JIVE_WEB_CHAT_RESOURCE_NAME = "WebChat";
+
+    static protected long PACKET_RESPONSE_TIMEOUT_MS = 5000;
+
+    static protected String CHAT_LAUNCHER_URI_SUFFIX = "/chat-launcher.jsp";
+    static protected String CREATE_ACCOUNT_URI = "/account_creation.jsp";
+    static protected String LOGIN_URI = "/index.jsp";
+
+    static protected String ERRORS_ATTRIBUTE_STRING = "messenger.servlet.errors";
+    static protected String NICKNAME_ATTRIBUTE_STRING = "messenger.servlet.nickname";
+    static protected String ROOM_ATTRIBUTE_STRING = "messenger.servlet.room";
+
+    static protected String HOST_PARAM_STRING = "host";
+    static protected String PORT_PARAM_STRING = "port";
+    static protected String SSL_PARAM_STRING = "SSLEnabled";
+
+    static protected String COMMAND_PARAM_STRING = "command";
+    static protected String NICKNAME_PARAM_STRING = "nickname";
+    static protected String PASSWORD_PARAM_STRING = "password";
+    static protected String RETYPED_PASSWORD_PARAM_STRING = "password_zwei";
+    static protected String ROOM_PARAM_STRING = "room";
+    static protected String USERNAME_PARAM_STRING = "username";
+
+    static protected String ANON_LOGIN_COMMAND_STRING = "anon_login";
+    static protected String CREATE_ACCOUNT_COMMAND_STRING = "create_account";
+    static protected String LOGIN_COMMAND_STRING = "login";
+    static protected String LOGOUT_COMMAND_STRING = "logout";
+    static protected String READ_COMMAND_STRING = "read";
+    static protected String SILENCE_COMMAND_STRING = "silence";
+    static protected String WRITE_COMMAND_STRING = "write";
+
+    static protected String MESSAGE_REQUEST_STRING = "message";
+
+    // is this value used elsewhere? (if not, why a string?) PENDING
+    static protected String ERROR_RETURN_CODE_STRING = "error";
+    static protected String SUCCESS_RETURN_CODE_STRING = "success";
+
+    // k/v :: S(session id) / ChatData
+    static protected Map SESSION_CHATDATA_MAP = new HashMap();
+    // k/v :: S(unique root of packet ids) / ChatData
+    static protected Map PACKET_ROOT_CHATDATA_MAP = new HashMap();
+
+    static protected EmoticonFilter EMOTICONFILTER = new EmoticonFilter();
+    static protected URLTranscoder URLTRANSCODER = new URLTranscoder();
+
+
+    protected String host;
+    protected int port;
+    protected boolean sslEnabled;
+
+    public void init (ServletConfig config)
+        throws ServletException {
+        ServletContext context = null;
+        String portParameter = null;
+
+        super.init(config);
+
+// XMPPConnection.DEBUG_ENABLED = true;        
+
+        context = config.getServletContext();
+
+        this.host = context.getInitParameter(HOST_PARAM_STRING);
+        if (this.host == null) {
+            throw new ServletException("Init parameter \"" + HOST_PARAM_STRING + "\" must be set.");
+        }
+
+        this.port = -1;
+
+        portParameter = context.getInitParameter(PORT_PARAM_STRING);
+        if (portParameter != null) {
+            try {
+                this.port = Integer.parseInt(portParameter);
+            } catch (NumberFormatException nfe) {
+                throw new ServletException("Init parameter \"" + PORT_PARAM_STRING
+                                                + "\" must be a valid number.", nfe);
+            }
+        }
+
+        this.sslEnabled
+                    = Boolean.valueOf(context.getInitParameter(SSL_PARAM_STRING)).booleanValue();
+    }
+
+    /**
+     * Take care of closing down everything we're holding on to, then bubble up the destroy
+     *  to our superclass.
+     */
+    public void destroy () {
+        synchronized (SESSION_CHATDATA_MAP) {
+            for (Iterator i = SESSION_CHATDATA_MAP.values().iterator(); i.hasNext(); ) {
+                ChatData chatData = (ChatData)i.next();
+
+                if (chatData.groupChat != null) {
+                    chatData.groupChat.leave();
+                }
+
+                chatData.connection.close();
+            }
+        }
+
+        super.destroy();
+    }
+
+    protected void service (HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException {
+        HttpSession session = request.getSession();
+        String sessionID = session.getId();
+        String path = request.getContextPath();
+        String command = request.getParameter(COMMAND_PARAM_STRING);
+
+        if (READ_COMMAND_STRING.equals(command))  {
+            ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID);
+            StringBuffer reply = null;
+            boolean foundData = false;
+            Message message = null;
+            int i = 0;
+
+            if (chatData == null) {
+                this.writeData("Must login first.", response);
+
+                return;
+            }
+
+            reply = new StringBuffer();
+            reply.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n");
+            reply.append("<html><head><title>Chat Read Page</title>\n");
+            reply.append("<meta http-equiv=\"refresh\" content=\"2\">\n");
+            reply.append("<script language=\"Javascript\" src=\"common.js\"></script>");
+            reply.append("<script language=\"JavaScript\" type=\"text/javascript\">\n");
+            reply.append("  var nickname = \"");
+            reply.append(request.getSession().getAttribute(NICKNAME_ATTRIBUTE_STRING));
+            reply.append("\";\n");
+
+            message = chatData.groupChat.pollMessage();
+            while (message != null) {
+                String from = message.getFrom();
+                String body = message.getBody();
+
+                // Get the the user's nickname
+                from = StringUtils.parseResource(from);
+
+                // PENDING - stop using the replace method
+
+                // encode the HTML special characters
+                body = this.replace(body, "&", "&amp;");
+                body = this.replace(body, "<", "&lt;");
+                body = this.replace(body, ">", "&gt;");
+
+                // replace newlines in the body:
+                body = this.replace(body, "\r", "");
+                body = this.replace(body, "\n", "<br>");
+
+                // encode the quotes
+                body = this.replace(body, "\"", "&quot;");
+
+                // encode the embedded urls
+                body = URLTRANSCODER.encodeURLsInText(body);
+
+                // Apply emoticons
+                body = EMOTICONFILTER.applyFilter(body);
+
+                if (from.length() == 0) {
+                    reply.append("  var body").append(i).append(" = \"").append(body);
+                    reply.append("\";\n  addChatText(body").append(i).append(", true);\n");
+                } else {
+                    reply.append("  var from").append(i).append(" = \"").append(from);
+                    reply.append("\";\n  var body").append(i).append(" = \"").append(body);
+                    reply.append("\";\n  addUserName(from").append(i);
+                    reply.append(");\n  addChatText(body").append(i).append(", false);\n");
+                }
+
+                message = chatData.groupChat.pollMessage();
+
+                foundData = true;
+
+                i++;
+            }
+
+            synchronized (chatData.newJoins) {
+                synchronized (chatData.newDepartures) {
+                    Iterator it = chatData.newJoins.iterator();
+
+                    i = 0;
+
+                    while (it.hasNext()) {
+                        reply.append("  var joined").append(i).append(" = \"").append(it.next());
+                        reply.append("\";\n  userJoined(joined").append(i).append(");\n");
+
+                        i++;
+                    }
+
+                    i = 0;
+                    it = chatData.newDepartures.iterator();
+                    while (it.hasNext()) {
+                        reply.append("  var departed").append(i).append(" = \"").append(it.next());
+                        reply.append("\";\n  userDeparted(departed").append(i).append(");\n");
+
+                        i++;
+                    }
+
+                    chatData.newJoins.clear();
+                    chatData.newDepartures.clear();
+                }
+            }
+
+            reply.append("</script>\n</head><body></body></html>");
+
+            this.writeData(reply.toString(), response);
+        } else if (WRITE_COMMAND_STRING.equals(command)) {
+            String message = request.getParameter(MESSAGE_REQUEST_STRING);
+            ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID);
+
+            if (message == null) {
+                this.writeData("Parameter \"" + MESSAGE_REQUEST_STRING + "\" is required.",
+                               response);
+
+                return;
+            } else if (chatData == null) {
+                this.writeData("Must login first.", response);
+
+                return;
+            }
+
+            try {
+                StringBuffer reply = new StringBuffer();
+
+                chatData.groupChat.sendMessage(message.trim());
+
+                reply.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n");
+                reply.append("<html><head>\n");
+                reply.append(
+                       "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
+                reply.append("<title>Chat Form</title>");
+                reply.append("</head><body>\n");
+                reply.append("<form name=\"chatform\" action=\"").append(path);
+                reply.append("/ChatServlet\" method=\"post\">\n");
+                reply.append("<input type=\"hidden\" name=\"command\" value=\"write\">\n");
+                reply.append("<input type=\"hidden\" name=\"message\" value=\"\">\n");
+                reply.append("</form></body></html>");
+
+                this.writeData(reply.toString(), response);
+            } catch (XMPPException e) {
+                // PENDING - better handling
+                e.printStackTrace();
+            }
+        } else if (LOGOUT_COMMAND_STRING.equals(command)) {
+            ChatData chatData = null;
+
+            synchronized (SESSION_CHATDATA_MAP) {
+                chatData = (ChatData)SESSION_CHATDATA_MAP.remove(sessionID);
+            }
+
+            if (chatData != null) {
+                if (chatData.groupChat != null) {
+                    chatData.groupChat.leave();
+                }
+
+                synchronized (PACKET_ROOT_CHATDATA_MAP) {
+                    Packet p = new IQ();
+                    String root = this.getPacketIDRoot(p);
+
+                    PACKET_ROOT_CHATDATA_MAP.remove(root);
+                }
+
+                chatData.connection.close();
+            }
+        } else if (ANON_LOGIN_COMMAND_STRING.equals(command)) {
+            String returnCode = this.handleLogin(request, true);
+
+            if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) {
+                response.sendRedirect(path + CHAT_LAUNCHER_URI_SUFFIX);
+            } else {
+                // error, return to the original page to display errors and allow re-attempts
+                RequestDispatcher rd = request.getRequestDispatcher(LOGIN_URI);
+
+                rd.forward(request, response);
+            }
+        } else if (LOGIN_COMMAND_STRING.equals(command)) {
+            String returnCode = this.handleLogin(request, false);
+
+            if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) {
+                response.sendRedirect(path + CHAT_LAUNCHER_URI_SUFFIX);
+            } else {
+                // error, return to the original page to display errors and allow re-attempts
+                RequestDispatcher rd = request.getRequestDispatcher(LOGIN_URI);
+
+                rd.forward(request, response);
+            }
+        } else if (CREATE_ACCOUNT_COMMAND_STRING.equals(command)) {
+            String returnCode = this.createAccount(request);
+
+            if (SUCCESS_RETURN_CODE_STRING.equals(returnCode)) {
+                response.sendRedirect(path + LOGIN_URI);
+            } else {
+                // error, return to the original page to display errors and allow re-attempts
+                RequestDispatcher rd = request.getRequestDispatcher(CREATE_ACCOUNT_URI);
+
+                rd.forward(request, response);
+            }
+        } else if (SILENCE_COMMAND_STRING.equals(command)) {
+            // do nothing
+        } else if (command != null) {
+            this.writeData(("Invalid command: " + command), response);
+        } else {
+            this.writeData("Jive Messenger Chat Servlet", response);
+        }
+    }
+
+    protected String getPacketIDRoot (Packet p) {
+        if (p == null) {
+            return null;
+        }
+
+        return p.getPacketID().substring(0, 5);
+    }
+
+    /**
+     * Creates an account for the user data specified.
+     */
+    private String createAccount (HttpServletRequest request) {
+        String sessionID = request.getSession().getId();
+        String username = request.getParameter(USERNAME_PARAM_STRING);
+        String password = request.getParameter(PASSWORD_PARAM_STRING);
+        String retypedPassword = request.getParameter(RETYPED_PASSWORD_PARAM_STRING);
+        Map errors = new HashMap();
+
+        // PENDING: validate already taken username
+
+        if ((username == null) || (username.trim().length() == 0)) {
+            errors.put("empty_username", "");
+        }
+
+        if ((password == null) || (password.trim().length() == 0)) {
+            errors.put("empty_password", "");
+        }
+
+        if ((retypedPassword == null) || (retypedPassword.trim().length() == 0)) {
+            errors.put("empty_password_two", "");
+        }
+
+        if ((retypedPassword != null) && (password != null)
+                && (! retypedPassword.equals(password))) {
+            errors.put("mismatch_password", "");
+        }
+
+        // If there were no errors, continue
+        if (errors.size() == 0) {
+            ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID);
+
+            // If a connection already exists for this session, close it before creating
+            //  another.
+            if (chatData != null) {
+                if (chatData.groupChat != null) {
+                    chatData.groupChat.leave();
+                }
+
+                chatData.connection.close();
+            }
+
+            chatData = new ChatData();
+
+            try {
+                AccountManager am = null;
+
+                // Create connection.
+                if (! this.sslEnabled) {
+                    if (port != -1) {
+                        chatData.connection = new XMPPConnection(this.host, this.port);
+                    } else {
+                        chatData.connection = new XMPPConnection(this.host);
+                    }
+                } else {
+                     if (port != -1) {
+                        chatData.connection = new SSLXMPPConnection(this.host, this.port);
+                    }
+                    else {
+                        chatData.connection = new SSLXMPPConnection(this.host);
+                    }
+                }
+
+                am = chatData.connection.getAccountManager();
+
+                // PENDING check whether the server even supports account creation
+                am.createAccount(username, password);
+            } catch (XMPPException e) {
+                errors.put("general", "The server reported an error in account creation: "
+                                           + e.getXMPPError().getMessage());
+            }
+        }
+
+        if (errors.size() > 0) {
+            request.setAttribute(ERRORS_ATTRIBUTE_STRING, errors);
+
+            return ERROR_RETURN_CODE_STRING;
+        }
+
+        return SUCCESS_RETURN_CODE_STRING;
+    }
+
+    /**
+     * Handles login logic.
+     */
+    private String handleLogin (HttpServletRequest request, boolean anonymous) {
+        String sessionID = request.getSession().getId();
+        String username = request.getParameter(USERNAME_PARAM_STRING);
+        String password = request.getParameter(PASSWORD_PARAM_STRING);
+        String room = request.getParameter(ROOM_PARAM_STRING);
+        String nickname = request.getParameter(NICKNAME_PARAM_STRING);
+        Map errors = new HashMap();
+
+        // Validate parameters
+        if ((! anonymous) && ((username == null) || (username.trim().length() == 0))) {
+            errors.put(USERNAME_PARAM_STRING, "");
+        }
+
+        if ((! anonymous) && ((password == null) || (password.trim().length() == 0))) {
+            errors.put(PASSWORD_PARAM_STRING, "");
+        }
+
+        if ((room == null) || (room.trim().length() == 0)) {
+            errors.put(ROOM_PARAM_STRING, "");
+        }
+
+        if ((nickname == null) || (nickname.trim().length() == 0)) {
+            errors.put(NICKNAME_PARAM_STRING, "");
+        }
+
+        // If there were no errors, continue
+        if (errors.size() == 0) {
+            ChatData chatData = (ChatData)SESSION_CHATDATA_MAP.get(sessionID);
+
+            // If a connection already exists for this session, close it before creating
+            //  another.
+            if (chatData != null) {
+                if (chatData.groupChat != null) {
+                    chatData.groupChat.leave();
+                }
+
+                chatData.connection.close();
+            }
+
+            chatData = new ChatData();
+
+            try {
+                // Create connection.
+                if (! this.sslEnabled) {
+                    if (port != -1) {
+                        chatData.connection = new XMPPConnection(this.host, this.port);
+                    } else {
+                        chatData.connection = new XMPPConnection(this.host);
+                    }
+                } else {
+                     if (port != -1) {
+                        chatData.connection = new SSLXMPPConnection(this.host, this.port);
+                    }
+                    else {
+                        chatData.connection = new SSLXMPPConnection(this.host);
+                    }
+                }
+
+                if (anonymous) {
+                    Authentication a = new Authentication();
+                    PacketCollector pc = chatData.connection.createPacketCollector(
+                                                               new PacketIDFilter(a.getPacketID()));
+                    Authentication responsePacket = null;
+
+                    a.setType(IQ.Type.SET);
+
+                    chatData.connection.sendPacket(a);
+
+                    responsePacket = (Authentication)pc.nextResult(PACKET_RESPONSE_TIMEOUT_MS);
+                    if (responsePacket == null) {
+//                        throw new XMPPException("No response from the server.");
+                    }
+                    // check for error response
+
+                    pc.cancel();
+
+                    // since GroupChat isn't setting the 'from' in it's message sends,
+                    //  i can't see a problem in not doing anything with the unique resource
+                    //  we've just been given by the server. if GroupChat starts setting the
+                    //  from, it would probably grab the information from the XMPPConnection
+                    //  instance it holds, and as such we would then need to introduce the
+                    //  concept of anonymous logins to XMPPConnection, or tell GroupChat what
+                    //  to do what username is null or blank but a resource exists... PENDING
+                } else {
+                    chatData.connection.login(username, password, JIVE_WEB_CHAT_RESOURCE_NAME);
+                }
+
+                chatData.connection.addPacketListener(this,
+                                                      new PacketTypeFilter(Presence.class));
+
+                synchronized (SESSION_CHATDATA_MAP) {
+                    SESSION_CHATDATA_MAP.put(sessionID, chatData);
+                }
+
+                synchronized (PACKET_ROOT_CHATDATA_MAP) {
+                    Packet p = new IQ();
+                    String root = this.getPacketIDRoot(p);
+
+                    // PENDING -- we won't do anything with this packet, so it will ultimately look
+                    //  to the server as though a packet has disappeared -- is this ok with the
+                    //  server?
+                    PACKET_ROOT_CHATDATA_MAP.put(root, chatData);
+                }
+
+                // Join groupChat room.
+                chatData.groupChat = chatData.connection.createGroupChat(room);
+                chatData.groupChat.join(nickname);
+
+                // Put the user's nickname in the session - this is used by the view to correctly
+                //  display the user's messages in a different color:
+                request.getSession().setAttribute(NICKNAME_ATTRIBUTE_STRING, nickname);
+                request.getSession().setAttribute(ROOM_ATTRIBUTE_STRING, room);
+            } catch (XMPPException e) {
+                XMPPError err = e.getXMPPError();
+
+                errors.put("general", ((err != null) ? err.getMessage() : e.getMessage()));
+
+                if (chatData.groupChat != null) {
+                    chatData.groupChat.leave();
+                }
+            }
+        }
+
+        if (errors.size() > 0) {
+            request.setAttribute(ERRORS_ATTRIBUTE_STRING, errors);
+
+            return ERROR_RETURN_CODE_STRING;
+        }
+
+        return SUCCESS_RETURN_CODE_STRING;
+    }
+
+    private void writeData (String data, HttpServletResponse response) {
+        try {
+            PrintWriter responseWriter = response.getWriter();
+
+            response.setContentType("text/html");
+
+            responseWriter.println(data);
+            responseWriter.close();
+        } catch (IOException ioe) {
+            // PENDING
+        }
+    }
+
+    // a hack class to hold a data glom (really hacky)
+    private class ChatData {
+
+        private XMPPConnection connection;
+        private GroupChat groupChat;
+        private Set newJoins = new HashSet();
+        private Set newDepartures = new HashSet();
+
+    }
+
+    /**
+     * Replaces all instances of oldString with newString in string.
+     *
+     * PENDING - why is this final?
+     * PENDING - take this out -- it fails under some cases...
+     *
+     * @param string the String to search to perform replacements on
+     * @param oldString the String that should be replaced by newString
+     * @param newString the String that will replace all instances of oldString
+     *
+     * @return a String will all instances of oldString replaced by newString
+     */
+    static public final String replace (String string, String oldString, String newString) {
+        int i = 0;
+
+        // MAY RETURN THIS BLOCK
+        if (string == null) {
+            return null;
+        }
+
+        if (newString == null) {
+            return string;
+        }
+
+        // Make sure that oldString appears at least once before doing any processing.
+        if (( i=string.indexOf(oldString, i)) >= 0) {
+            // Use char []'s, as they are more efficient to deal with.
+            char[] string2 = string.toCharArray();
+            char[] newString2 = newString.toCharArray();
+            int oLength = oldString.length();
+            StringBuffer buf = new StringBuffer(string2.length);
+            int j = 1;
+
+            buf.append(string2, 0, i).append(newString2);
+
+            i += oLength;
+
+            // Replace all remaining instances of oldString with newString.
+            while ((i=string.indexOf(oldString, i)) > 0) {
+                buf.append(string2, j, (i - j)).append(newString2);
+
+                i += oLength;
+                j = i;
+            }
+
+            buf.append(string2, j, (string2.length - j));
+
+            return buf.toString();
+        }
+
+        return string;
+    }
+
+    /**
+     *
+     * HttpSessionListener implementation
+     *
+     */
+    public void sessionCreated (HttpSessionEvent event) { }
+
+    public void sessionDestroyed (HttpSessionEvent event) {
+        String sessionID = event.getSession().getId();
+        ChatData chatData = null;
+
+        synchronized (SESSION_CHATDATA_MAP) {
+            chatData = (ChatData)SESSION_CHATDATA_MAP.remove(sessionID);
+        }
+
+        if (chatData != null) {
+            if (chatData.groupChat != null) {
+                chatData.groupChat.leave();
+            }
+
+            synchronized (PACKET_ROOT_CHATDATA_MAP) {
+                Packet p = new IQ();
+                String root = this.getPacketIDRoot(p);
+
+                PACKET_ROOT_CHATDATA_MAP.remove(root);
+            }
+
+            chatData.connection.close();
+        }
+    }
+
+    /**
+     *
+     * PacketListener implementation
+     *
+     */
+    public void processPacket (Packet packet) {
+        Presence presence = (Presence)packet;
+        String root = null;
+        ChatData chatData = null;
+        String userName = null;
+
+        // MAY RETURN THIS BLOCK
+        if (presence.getMode() == Presence.Mode.INVISIBLE) {
+            return;
+        }
+
+        root = this.getPacketIDRoot(presence);
+        chatData = (ChatData)PACKET_ROOT_CHATDATA_MAP.get(root);
+
+        // MAY RETURN THIS BLOCK
+        if (chatData == null) {
+            return;
+        }
+
+        userName = StringUtils.parseResource(packet.getFrom());
+
+        if (presence.getType() == Presence.Type.UNAVAILABLE) {
+            synchronized (chatData.newDepartures) {
+                synchronized (chatData.newJoins) {
+                    chatData.newJoins.remove(userName);
+
+                    chatData.newDepartures.add(userName);
+                }
+            }
+        } else if (presence.getType() == Presence.Type.AVAILABLE) {
+            synchronized (chatData.newJoins) {
+                synchronized (chatData.newDepartures) {
+                    chatData.newDepartures.remove(userName);
+
+                    chatData.newJoins.add(userName);
+                }
+            }
+        }
+    }
+
+}
diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java
new file mode 100644
index 000000000..3e6aa955e
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/TextStyle.java
@@ -0,0 +1,126 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 1999-2002 CoolServlets, Inc. All rights reserved.
+ *
+ * This software is the proprietary information of CoolServlets, Inc.
+ * Use is subject to license terms.
+ */
+
+package org.jivesoftware.webchat;
+
+/**
+ * A Filter that replaces [b][/b], [i][/i], [u][/u], [pre][/pre] tags with their HTML
+ * tag equivalents.
+ */
+public class TextStyle {
+
+    public String applyFilter(String string) {
+        if (string == null || string.length() == 0) {
+            return string;
+        }
+
+        // To figure out how many times we've made text replacements, we
+        // need to pass around integer count objects.
+        int[] boldStartCount      = new int[1];
+        int[] italicsStartCount   = new int[1];
+        int[] boldEndCount        = new int[1];
+        int[] italicsEndCount     = new int[1];
+        int[] underlineStartCount = new int[1];
+        int[] underlineEndCount   = new int[1];
+        int[] preformatStartCount = new int[1];
+        int[] preformatEndCount   = new int[1];
+
+        // Bold
+        string = replaceIgnoreCase(string, "[b]", "<b>", boldStartCount);
+        string = replaceIgnoreCase(string, "[/b]", "</b>", boldEndCount);
+        int bStartCount = boldStartCount[0];
+        int bEndCount = boldEndCount[0];
+
+        while (bStartCount > bEndCount) {
+            string = string.concat("</b>");
+            bEndCount++;
+        }
+
+        // Italics
+        string = replaceIgnoreCase(string, "[i]", "<i>", italicsStartCount);
+        string = replaceIgnoreCase(string, "[/i]", "</i>", italicsEndCount);
+        int iStartCount = italicsStartCount[0];
+        int iEndCount = italicsEndCount[0];
+
+        while (iStartCount > iEndCount) {
+            string = string.concat("</i>");
+            iEndCount++;
+        }
+
+        // Underline
+        string = replaceIgnoreCase(string, "[u]", "<u>", underlineStartCount);
+        string = replaceIgnoreCase(string, "[/u]", "</u>", underlineEndCount);
+        int uStartCount = underlineStartCount[0];
+        int uEndCount = underlineEndCount[0];
+
+        while (uStartCount > uEndCount) {
+            string = string.concat("</u>");
+            uEndCount++;
+        }
+
+        // Pre
+        string = replaceIgnoreCase(string, "[pre]", "<pre>", preformatStartCount);
+        string = replaceIgnoreCase(string, "[/pre]", "</pre>", preformatEndCount);
+        int preStartCount = preformatStartCount[0];
+        int preEndCount = preformatEndCount[0];
+
+        while (preStartCount > preEndCount) {
+            string = string.concat("</pre>");
+            preEndCount++;
+        }
+
+        return string;
+    }
+
+    /**
+     * Replaces all instances of oldString with newString in line with the
+     * added feature that matches of newString in oldString ignore case.
+     * The count paramater is set to the number of replaces performed.
+     *
+     * @param line the String to search to perform replacements on
+     * @param oldString the String that should be replaced by newString
+     * @param newString the String that will replace all instances of oldString
+     * @param count a value that will be updated with the number of replaces
+     *      performed.
+     *
+     * @return a String will all instances of oldString replaced by newString
+     */
+    private static final String replaceIgnoreCase(String line, String oldString,
+            String newString, int [] count)
+    {
+        if (line == null) {
+            return null;
+        }
+        String lcLine = line.toLowerCase();
+        String lcOldString = oldString.toLowerCase();
+        int i=0;
+        if ((i=lcLine.indexOf(lcOldString, i)) >= 0) {
+            int counter = 1;
+            char [] line2 = line.toCharArray();
+            char [] newString2 = newString.toCharArray();
+            int oLength = oldString.length();
+            StringBuffer buf = new StringBuffer(line2.length);
+            buf.append(line2, 0, i).append(newString2);
+            i += oLength;
+            int j = i;
+            while ((i=lcLine.indexOf(lcOldString, i)) > 0) {
+                counter++;
+                buf.append(line2, j, i-j).append(newString2);
+                i += oLength;
+                j = i;
+            }
+            buf.append(line2, j, line2.length - j);
+            count[0] = counter;
+            return buf.toString();
+        }
+        return line;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java
new file mode 100644
index 000000000..de2f972b5
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLFilter.java
@@ -0,0 +1,312 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 1999-2002 CoolServlets, Inc. All rights reserved.
+ *
+ * This software is the proprietary information of CoolServlets, Inc.
+ * Use is subject to license terms.
+ */
+
+package org.jivesoftware.webchat;
+
+import java.util.*;
+
+/**
+ * A Filter that converts URL's to working HTML web links.<p>
+ *
+ * The default set of patterns recognized are <code>ftp://path-of-url</code>,
+ * <code>http://path-of-url</code>, <code>https://path-of-url</code> but can be expanded upon.<p>
+ *
+ * In addition, the following patterns are also recognized.
+ *
+ * <code>[url path-of-url]descriptive text[/url]</code> and
+ * <code>[url=path-of-url]descriptive text[/url]</code>.<p>
+ *
+ * The <code>[url]</code> allows any path to be defined as link.
+ */
+public class URLFilter{
+
+    private ArrayList schemes = new ArrayList();
+
+    // define a preset default set of schemes
+    public URLFilter() {
+        schemes.add("http://");
+        schemes.add("https://");
+        schemes.add("ftp://");
+    }
+
+    public String applyFilter(String string) {
+        if (string == null || string.length() == 0) {
+            return string;
+        }
+
+        int length            = string.length();
+        StringBuffer filtered = new StringBuffer((int) (length * 1.5));
+        ArrayList urlBlocks   = new ArrayList(5);
+
+        // search for url's such as [url=..]text[/url] or [url ..]text[/url]
+        int start = string.indexOf("[url");
+        while (start != -1 && (start + 5 < length)) {
+            // check to verify we're not in another block
+            if (withinAnotherBlock(urlBlocks, start)) {
+                    start = string.indexOf("[url", start + 5);
+                    continue;
+            }
+
+            int end = string.indexOf("[/url]", start + 5);
+
+            if (end == -1 || end >= length) {
+                // went past end of string, skip
+                break;
+            }
+
+            String u = string.substring(start, end + 6);
+            int startTagClose = u.indexOf(']');
+            String url;
+            String description;
+            if (startTagClose > 5) {
+                url = u.substring(5, startTagClose);
+                description = u.substring(startTagClose + 1, u.length() - 6);
+
+                // Check the user entered URL for a "javascript:" or "file:" link. Only
+                // append the user entered link if it doesn't contain 'javascript:' and 'file:'
+                String lcURL = url.toLowerCase();
+                if (lcURL.indexOf("javascript:") == -1 && lcURL.indexOf("file:") == -1) {
+                    URLBlock block = new URLBlock(start, end + 5, url, description);
+                    urlBlocks.add(block);
+                }
+            }
+            else {
+                url = description = u.substring(startTagClose + 1, u.length() - 6);
+                // Check the user entered URL for a "javascript:" or "file:" link. Only
+                // append the user entered link if it doesn't contain 'javascript:' and 'file:'
+                String lcURL = url.toLowerCase();
+                if (lcURL.indexOf("javascript:") == -1 && lcURL.indexOf("file:") == -1) {
+                    URLBlock block = new URLBlock(start, end + 5, url);
+                    urlBlocks.add(block);
+                }
+            }
+
+            start = string.indexOf("[url", end + 6);
+        }
+
+        // now handle all the other urls
+        Iterator iter = schemes.iterator();
+
+        while (iter.hasNext()) {
+            String scheme = (String) iter.next();
+            start = string.indexOf(scheme, 0);
+
+            while (start != -1) {
+                int end = start;
+
+                // check context, don't handle patterns preceded by any of '"<=
+        		if (start > 0) {
+                    char c = string.charAt(start - 1);
+
+                    if (c == '\'' || c == '"' || c == '<' || c == '=') {
+                        start = string.indexOf(scheme, start + scheme.length());
+                        continue;
+                    }
+                }
+
+                // check to verify we're not in another block
+                if (withinAnotherBlock(urlBlocks, start)) {
+                        start = string.indexOf(scheme, start + scheme.length());
+                        continue;
+                }
+
+                // find the end of the url
+                int cur = start + scheme.length();
+                while (end == start && cur < length) {
+                    char c = string.charAt(cur);
+
+                    switch (c) {
+                        case ' ':
+                            end = cur;
+                             break;
+                        case '\t':
+                            end = cur;
+                            break;
+                        case '\'':
+                            end = cur;
+                            break;
+                        case '\"':
+                            end = cur;
+                            break;
+                        case '<':
+                            end = cur;
+                            break;
+                        case '[':
+                            end = cur;
+                            break;
+                        case '\n':
+                            end = cur;
+                            break;
+                        case '\r':
+                            end = cur;
+                            break;
+                        default:
+                            // acceptable character
+                    }
+
+                    cur++;
+                }
+
+                // if this is true it means the url goes to the end of the string
+                if (end == start) {
+                    end = length - 1;
+                }
+
+                URLBlock block = new URLBlock(start, end-1, string.substring(start, end));
+                urlBlocks.add(block);
+
+                start = string.indexOf(scheme, end);
+            }
+        }
+
+        // sort the blocks so that they are in start index order
+        sortBlocks(urlBlocks);
+
+        // now, markup the urls and pass along the filter chain the rest
+        Iterator blocks = urlBlocks.iterator();
+        int last = 0;
+
+        while (blocks.hasNext()) {
+            URLBlock block = (URLBlock) blocks.next();
+
+            if (block.getStart() > 0) {
+                filtered.append(string.substring(last, block.getStart()));
+            }
+
+            last = block.getEnd() + 1;
+
+            filtered.append("<a href='").append(block.getUrl()).append("' target='_blank'>");
+            if (block.getDescription().length() > 0) {
+                filtered.append(block.getDescription());
+            }
+            else {
+                filtered.append(block.getUrl());
+            }
+            filtered.append("</a>");
+        }
+
+        if (last < string.length() - 1) {
+             filtered.append(string.substring(last));
+        }
+
+        return filtered.toString();
+    }
+
+    /**
+     * Returns the current supported uri schemes as a comma seperated string.
+     *
+     * @return the current supported uri schemes as a comma seperated string.
+     */
+    public String getSchemes() {
+        StringBuffer buf = new StringBuffer(50);
+
+        for (int i = 0; i < schemes.size(); i++) {
+            buf.append((String) schemes.get(i)).append(",");
+        }
+        buf.deleteCharAt(buf.length() - 1);
+
+        return buf.toString();
+    }
+
+    /**
+     * Sets the current supported uri schemes as a comma seperated string.
+     *
+     * @param schemes a comma seperated string of uri schemes.
+     */
+    public void setSchemes(String schemes) {
+        if (schemes == null) {
+            return;
+        }
+
+        // enpty the current list
+        this.schemes.clear();
+
+        StringTokenizer st = new StringTokenizer(schemes, ",");
+
+        while (st.hasMoreElements()) {
+            this.schemes.add(st.nextElement());
+        }
+    }
+
+    private void sortBlocks(ArrayList blocks) {
+        Collections.sort(blocks, new Comparator() {
+            public int compare(Object object1, Object object2) {
+                URLBlock b1 = (URLBlock) object1;
+                URLBlock b2 = (URLBlock) object2;
+                return (b1.getStart() > b2.getStart()) ? 1 : -1;
+            }
+        });
+    }
+
+    private boolean withinAnotherBlock(List blocks, int start) {
+        for (int i = 0; i < blocks.size(); i++) {
+            URLBlock block = (URLBlock) blocks.get(i);
+
+            if (start >= block.getStart() && start < block.getEnd()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    class URLBlock {
+        int start = 0;
+        int end = 0;
+        String description = "";
+        String url = "";
+
+        public String getDescription() {
+            return description;
+        }
+
+        public void setDescription(String description) {
+            this.description = description;
+        }
+
+        public String getUrl() {
+            return url;
+        }
+
+        public void setUrl(String url) {
+            this.url = url;
+        }
+
+        URLBlock(int start, int end, String url) {
+            this.start = start;
+            this.end = end;
+            this.url = url;
+        }
+
+        URLBlock(int start, int end, String url, String description) {
+            this.start = start;
+            this.end = end;
+            this.description = description;
+            this.url = url;
+        }
+
+        public int getStart() {
+            return start;
+        }
+
+        public void setStart(int start) {
+            this.start = start;
+        }
+
+        public int getEnd() {
+            return end;
+        }
+
+        public void setEnd(int end) {
+            this.end = end;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java
new file mode 100644
index 000000000..84310eca0
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/java/org/jivesoftware/webchat/URLTranscoder.java
@@ -0,0 +1,188 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 1999-2003 Jive Software. All rights reserved.
+ *
+ * This software is the proprietary information of Jive Software.
+ * Use is subject to license terms.
+ */
+
+package org.jivesoftware.webchat;
+
+import java.util.*;
+
+/**
+ * This is a really good example of why software development projects have frameworks, and the
+ *  other apps in their own modules that sit on top of the frameworks... this class should not
+ *  be confused with com.jivesoftware.messenger.operator.util.URLTranscoder, which does a
+ *  variant of the functionality found here.<br>
+ *
+ * The default set of patterns recognized are <code>ftp://path-of-url</code>,
+ * <code>http://path-of-url</code>, <code>https://path-of-url</code> but can be expanded upon.</br>
+ *
+ * This was originally URLTranscoder, from CoolServlets, but that class did basically nothing that
+ *  i wanted, so i kept the schemes collection and that was about it.<br>
+ *
+ * @author loki der quaeler
+ */
+public class URLTranscoder {
+
+    static protected final String A_HREF_PREFIX = "<a href='";
+    static protected final String A_HREF_SUFFIX = "' target=_new>";
+    static protected final String A_HREF_CLOSING_TAG = "</a>";
+
+
+    protected ArrayList schemes;
+
+    public URLTranscoder () {
+        super();
+
+        this.schemes = new ArrayList();
+
+        this.schemes.add("http://");
+        this.schemes.add("https://");
+        this.schemes.add("ftp://");
+    }
+
+    /**
+     * Sets the current supported uri schemes.
+     *
+     * @param schemeCollection a collection of String instances of uri schemes.
+     */
+    public synchronized void setSchemes (Collection schemeCollection) {
+        // MAY EXIT THIS BLOCK
+        if (schemes == null) {
+            return;
+        }
+
+        this.schemes.clear();
+
+        this.schemes.addAll(schemeCollection);
+    }
+
+    /**
+     * Returns a String based off the original text, but now with any a.href blocks html-ized
+     *  inside. (for example, supplying the string "this: http://dict.leo.org/ is a cool url"
+     *  returns "this: <a href='http://dict.leo.org/' target=_new>http://dict.leo.org/</a>
+     *  is a cool url"
+     */
+    public String encodeURLsInText (String text) {
+        StringBuffer rhett = null;;
+        List runs = this.getURLRunsInString(text);
+        Iterator it = null;
+        int lastStart = 0;
+
+        // MAY RETURN THIS BLOCK
+        if (runs.size() == 0) {
+            return text;
+        }
+
+        rhett = new StringBuffer();
+        it = runs.iterator();
+        while (it.hasNext()) {
+            URLRun run = (URLRun)it.next();
+            String url = text.substring(run.getStartIndex(), run.getEndIndex());
+
+            if (lastStart < run.getStartIndex()) {
+                rhett.append(text.substring(lastStart, run.getStartIndex()));
+
+                lastStart += run.getEndIndex();
+            }
+
+            rhett.append(A_HREF_PREFIX).append(url).append(A_HREF_SUFFIX).append(url);
+            rhett.append(A_HREF_CLOSING_TAG);
+        }
+
+        if (lastStart < text.length()) {
+            rhett.append(text.substring(lastStart, text.length()));
+        }
+
+        return rhett.toString();
+    }
+
+    protected List getURLRunsInString (String text) {
+        ArrayList rhett = new ArrayList();
+        Vector vStarts = new Vector();
+        Iterator sIt = this.schemes.iterator();
+        Integer[] iStarts = null;
+        char[] tArray = null;
+
+        while (sIt.hasNext()) {
+            String scheme = (String)sIt.next();
+            int index = text.indexOf(scheme);
+
+            while (index != -1) {
+                vStarts.add(new Integer(index));
+
+                index = text.indexOf(scheme, (index + 1));
+            }
+        }
+
+        // MAY RETURN THIS BLOCK
+        if (vStarts.size() == 0) {
+            return rhett;
+        }
+
+        iStarts = (Integer[])vStarts.toArray(new Integer[0]);
+        Arrays.sort(iStarts);
+
+        tArray = text.toCharArray();
+
+        for (int i = 0; i < iStarts.length; i++) {
+            int start = iStarts[i].intValue();
+            int end = start + 1;
+
+            while ((end < tArray.length) && (! this.characterIsURLTerminator(tArray[end]))) {
+                end++;
+            }
+
+            if (end == tArray.length) {
+                end--;
+            }
+
+            rhett.add(new URLRun(start, end));
+        }
+
+        return rhett;
+    }
+
+    protected boolean characterIsURLTerminator (char c) {
+        switch (c) {
+            case ' ':
+            case '\n':
+            case '(':
+            case ')':
+            case '>':
+            case '\t':
+            case '\r':  return true;
+        }
+
+        return false;
+    }
+
+
+    protected class URLRun {
+
+        protected int start;
+        protected int end;
+
+        protected URLRun (int s, int e) {
+            super();
+
+            this.start = s;
+            this.end = e;
+        }
+
+        protected int getStartIndex () {
+            return this.start;
+        }
+
+        protected int getEndIndex () {
+            return this.end;
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/CopyOftrunk/apps/webchat/source/web/account_creation.jsp b/CopyOftrunk/apps/webchat/source/web/account_creation.jsp
new file mode 100644
index 000000000..b30e1f503
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/account_creation.jsp
@@ -0,0 +1,116 @@
+<%--
+  -
+  -
+--%>
+
+<%@	page import="java.util.*" %>
+
+<%	// Get error map as a request attribute:
+	Map errors = (Map)request.getAttribute("messenger.servlet.errors");
+	if (errors == null) { errors = new HashMap(); }
+%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+	<head>
+		<title>Create an account</title>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+	</head>
+
+	<body class="deffr">
+
+		<table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">
+			<tr>
+				<td align="center" valign="middle">
+					<table cellpadding="0" cellspacing="0" border="0">
+						<tr>
+                            <td>
+                                <h3>Jive Account Creation</h3>
+
+	                            <%	if (errors.get("general") != null) { %>
+                                    <p>
+                                        <span class="error-text">
+		                                    Error creating account. <%= errors.get("general") %>
+		                                </span>
+		                            </p>
+                                    <br>
+                                <%	} %>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align="center">
+	                            <form action="<%= request.getContextPath() %>/ChatServlet"
+                                        method="post" name="createform">
+	                            <input type="hidden" name="command" value="create_account">
+
+	                            <table cellpadding="2" cellspacing="0" border="0">
+	                                <tr>
+		                                <td>Desired username:</td>
+		                                <td>
+			                                <input type="text" size="40" name="username"
+                                                    class="text">
+                                            <% if (errors.get("empty_username") != null) { %>
+                                                <span class="error-text"><br>
+				                                    Please enter a username.
+				                                </span>
+			                                <%	} %>
+		                                </td>
+	                                </tr>
+	                                <tr>
+                                        <td>Desired password:</td>
+                                        <td>
+    	                                    <input type="password" size="40" name="password"
+                                                     class="text">
+			                                <% if (errors.get("empty_password") != null) { %>
+                                                <span class="error-text"><br>
+				                                    Please enter a password.
+				                                </span>
+			                                <%	} %>
+			                                <% if (errors.get("mismatch_password") != null) { %>
+                                				<span class="error-text"><br>
+				                                    Your passwords did not match.
+			                                    </span>
+			                                <%	} %>
+		                                </td>
+	                                </tr>
+	                                <tr>
+		                                <td>Retype your password:</td>
+		                                <td>
+		                                    <input type="password" size="40" name="password_zwei"
+                                                class="text">
+			                                <% if (errors.get("empty_password_two") != null) { %>
+                                                <span class="error-text"><br>
+                                                    You must retype your password.
+				                                </span>
+                                   		    <%	} %>
+                                        </td>
+	                                </tr>
+	                            </table>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align="center">
+                                <br>
+                                <input type="submit" name="" value="Create account"
+                                    class="submit">
+                            	</form>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align="center">
+                                <br><a href="index.jsp">Click here to return to the login page.</a>
+                            </td>
+                        </tr>
+					</table>
+				</td>
+			</tr>
+		</table>
+
+	    <script language="JavaScript" type="text/javascript">
+	        document.createform.username.focus();
+	    </script>
+	</body>
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp b/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp
new file mode 100644
index 000000000..c29c3d0a4
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/chat-hiddenform.jsp
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+	<head>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+		<title>Chat Form</title>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+	</head>
+
+	<body>
+		<form name="chatform" action="<%= request.getContextPath() %>/ChatServlet" method="post">
+			<input type="hidden" name="command" value="write">
+			<input type="hidden" name="message" value="">
+		</form>
+	</body>
+
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp b/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp
new file mode 100644
index 000000000..fba6cceb2
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/chat-launcher.jsp
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+	    <title>Web Chat Session</title>
+	    <script language="JavaScript" type="text/javascript">
+
+	        function launchWin() {
+		        var newWin = window.open("frame_master.jsp", "chatWin",
+			     "location=no,status=no,toolbar=no,personalbar=no,menubar=no,width=650,height=430");
+	        }
+
+	    </script>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+    </head>
+
+    <body class="deffr" onload="launchWin();">
+
+        <h3>Chat Session Options</h3>
+
+            Your chat session should have already started. If for some reason it did
+            not, click <a href="#" onclick="launchWin(); return false;">this link</a>
+            to start your chat session.
+
+        <br><br>
+
+        Other options:
+
+        <ul>
+	        <li><a href="email" onclick="alert('Coming soon'); return false;">Email Transcript</a>
+	        <li><a href="index.jsp">Return to the login page.</a>
+        </ul>
+
+    </body>
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/common.js b/CopyOftrunk/apps/webchat/source/web/common.js
new file mode 100644
index 000000000..ace272e48
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/common.js
@@ -0,0 +1,182 @@
+function addChatText (someText, isAnnouncement) {
+    var yakDiv = window.parent.frames['yak'].document.getElementById('ytext');
+    var children = yakDiv.childNodes.length;
+    var appendFailed = false;
+    var spanElement = document.createElement("span");
+
+    if (! isAnnouncement) {
+        spanElement.setAttribute("class", "chat_text");
+    } else {
+        spanElement.setAttribute("class", "chat_announcement");
+    }
+    // it's easier to dump the possibily html-containing text into the innerHTML
+    //  of the span element than deciphering and building sub-elements.
+    spanElement.innerHTML = someText;
+
+    try {
+        // various versions of IE crash out on this, and safari
+        yakDiv.appendChild(spanElement);
+    } catch (exception) {
+        appendFailed = true;
+    }
+
+    if (! appendFailed) {
+        // really make sure the browser appended
+        appendFailed = (children == yakDiv.childNodes.length);
+    }
+
+    if (appendFailed) {
+        // try this, the only way left
+        var inn = yakDiv.innerHTML;
+
+        inn += "<span class=\"";
+        inn += (isAnnouncement ? "chat_announcement\">" : "chat_text\">");
+        inn += someText + "</span><br>";
+
+        yakDiv.innerHTML = inn;
+    } else {
+        yakDiv.appendChild(document.createElement("br"));
+    }
+
+    scrollYakToEnd();
+}
+
+function addUserName (userName) {
+    var yakDiv = window.parent.frames['yak'].document.getElementById('ytext');
+    var children = yakDiv.childNodes.length;
+    var appendFailed = false;
+    var spanElement = document.createElement("span");
+    var userIsClientOwner = false;
+    var announcement = false;
+
+    if (userName == "") {
+        announcement = true;
+
+        spanElement.setAttribute("class", "chat_announcement");
+
+	    userName = "room announcement";
+    } else if (userName == nickname) {
+	    userIsClientOwner = true;
+
+        spanElement.setAttribute("class", "chat_owner");
+    } else {
+        spanElement.setAttribute("class", "chat_participant");
+    }
+
+    try {
+        spanElement.appendChild(document.createTextNode(userName + ": "));
+
+        // various versions of IE crash out on this, and safari
+        yakDiv.appendChild(spanElement);
+    } catch (exception) {
+        appendFailed = true;
+    }
+
+    if (! appendFailed) {
+        // really make sure the browser appended
+        appendFailed = (children == yakDiv.childNodes.length);
+    }
+
+    if (appendFailed) {
+        // try this, the only way left
+        var inn = yakDiv.innerHTML
+
+        inn += "<span class=\"";
+
+	if (announcement) {
+	    inn += "chat_announcement"
+	} else if (userIsClientOwner) {
+            inn += "chat_owner";
+        } else {
+            inn += "chat_participant";
+        }
+
+        inn += "\">" + userName + ": </span>";
+
+        yakDiv.innerHTML = inn;
+    }
+}
+
+function scrollYakToEnd () {
+    var endDiv = window.parent.frames['yak'].document.getElementById('enddiv');
+
+    window.parent.frames['yak'].window.scrollTo(0, endDiv.offsetTop);
+}
+
+function userJoined (username) {
+    var parentDIV = window.parent.frames['participants'].document.getElementById('par__list');
+    var children = parentDIV.childNodes.length;
+    var appendFailed = false;
+    var divElement = document.createElement("div");
+
+    divElement.setAttribute("id", username);
+
+    try {
+        divElement.appendChild(document.createTextNode(username));
+        divElement.appendChild(document.createElement("br"));
+
+        parentDIV.appendChild(divElement);
+    } catch (exception) {
+        appendFailed = true;
+    }
+
+    if (! appendFailed) {
+        // really make sure the browser appended
+        appendFailed = (children == parentDIV.childNodes.length);
+    }
+
+    if (appendFailed) {
+        // try this, the only way left
+        var inn = parentDIV.innerHTML;
+
+        inn += "<div id=\"" + username + "\"> &middot; " + username + "<br></div>";
+
+        parentDIV.innerHTML = inn;
+    }
+}
+
+function userDeparted (username) {
+    var partDoc = window.parent.frames['participants'].document;
+    var parentDIV = partDoc.getElementById('par__list');
+    var userDIV = partDoc.getElementById(username);
+    var children = parentDIV.childNodes.length;
+    var removeFailed = false;
+
+    // MAY RETURN THIS BLOCK
+    if (userDIV == null) {
+        return;
+    }
+
+    try {
+        parentDIV.removeChild(userDIV);
+    } catch (exception) {
+        removeFailed = true;
+    }
+
+    if (! removeFailed) {
+        // really make sure the browser appended
+        removeFailed = (children == parentDIV.childNodes.length);
+    }
+
+    if (removeFailed) {
+        // try this, the only way left
+        var inn = parentDIV.innerHTML;
+        var openingTag = "<div id=\"" + username + "\">";
+        var index = inn.toLowerCase().indexOf(openingTag);
+        var patchedHTML = inn.substring(0, index);
+        var secondIndex = openingTag.length + username.length + 13;
+
+        patchedHTML += inn.substring(secondIndex, (inn.length));
+
+        parentDIV.innerHTML = inn;
+    }
+}
+
+function writeDate () {
+    var msg = "This frame loaded at: ";
+    var now = new Date();
+
+    msg += now + "<br><hr>";
+
+    document.write(msg);
+}
diff --git a/CopyOftrunk/apps/webchat/source/web/frame_master.jsp b/CopyOftrunk/apps/webchat/source/web/frame_master.jsp
new file mode 100644
index 000000000..a96b88f4c
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/frame_master.jsp
@@ -0,0 +1,33 @@
+<html>
+	<head>
+		<title><%= request.getSession().getAttribute("messenger.servlet.room") %>
+                 - Jive Web Chat Client</title>
+
+        <script>
+
+            function frameSetLoaded () {
+                window.frames['poller'].location.href
+                        = "<%= request.getContextPath() %>/ChatServlet?command=read";
+            }
+
+            function attemptLogout () {
+                window.frames['participants'].document.logout.submit();
+            }
+
+        </script>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+	</head>
+
+	<frameset cols="*, 125" border="0" frameborder="0" framespacing="0"
+        onLoad="frameSetLoaded();" onUnload="attemptLogout();">
+	    <frameset rows="0, 200, *, 0" border="0" frameborder="0" framespacing="0">
+    		<frame name="submitter" src="chat-hiddenform.jsp" frameborder="0">
+    		<frame name="yak" src="transcript_frame.html" frameborder="0">
+    		<frame name="input" src="input_frame.jsp" frameborder="0">
+    		<frame name="poller" src="" frameborder="0">
+	    </frameset>
+        <frame name="participants" src="participants_frame.jsp" class="bordered_left">
+	</frameset>
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif
new file mode 100644
index 000000000..6489c9bc9
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/angry.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif
new file mode 100644
index 000000000..22f1db550
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/blush.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif
new file mode 100644
index 000000000..ef1107bec
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/confused.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif
new file mode 100644
index 000000000..2d805e5da
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cool.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif
new file mode 100644
index 000000000..e244944c8
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/cry.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif
new file mode 100644
index 000000000..31bca49fb
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/devil.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif
new file mode 100644
index 000000000..e5c92e2d5
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/grin.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif
new file mode 100644
index 000000000..20a93e32b
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/happy.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif
new file mode 100644
index 000000000..e808a2583
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/laugh.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif
new file mode 100644
index 000000000..d1c320e3d
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/love.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif
new file mode 100644
index 000000000..238db06f3
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/mischief.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif
new file mode 100644
index 000000000..a5beeea43
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/plain.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif
new file mode 100644
index 000000000..0320885df
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/sad.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif
new file mode 100644
index 000000000..20a70f8cd
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/shocked.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif
new file mode 100644
index 000000000..a162605bd
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/silly.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif b/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif
new file mode 100644
index 000000000..6d91b0fa2
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/emoticons/wink.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/logo.gif b/CopyOftrunk/apps/webchat/source/web/images/logo.gif
new file mode 100644
index 000000000..ad9573d92
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/logo.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif b/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif
new file mode 100644
index 000000000..b380a7576
Binary files /dev/null and b/CopyOftrunk/apps/webchat/source/web/images/logout-16x16.gif differ
diff --git a/CopyOftrunk/apps/webchat/source/web/index.jsp b/CopyOftrunk/apps/webchat/source/web/index.jsp
new file mode 100644
index 000000000..617b63b77
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/index.jsp
@@ -0,0 +1,164 @@
+<%--
+  -
+  -
+--%>
+
+<%@	page import="java.util.*" %>
+
+<%	// Get error map as a request attribute:
+	Map errors = (Map)request.getAttribute("messenger.servlet.errors");
+    boolean allowAnonymous = true;
+    boolean allowAccountCreate = true;
+    boolean allowLogin = true;
+    String param = null;
+	if (errors == null) { errors = new HashMap(); }
+    param = application.getInitParameter("allowAnonymous");
+    if ((param != null) && (param.equalsIgnoreCase("false"))) {
+        allowAnonymous = false;
+    }
+    param = application.getInitParameter("allowAccountCreation");
+    if ((param != null) && (param.equalsIgnoreCase("false"))) {
+        allowAccountCreate = false;
+    }
+    param = application.getInitParameter("allowLogin");
+    if ((param != null) && (param.equalsIgnoreCase("false"))) {
+        allowLogin = false;
+    }
+%>
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+    <head>
+	    <title>Jive Web Chat Client Login</title>
+        
+	    <script language="JavaScript" type="text/javascript">
+	        function submitForm (el) {
+	    	    el.form.submit();
+	        }
+
+            function anonClick () {
+                document.loginform.command.value = "anon_login";
+
+                return true;
+            }
+	    </script>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+    </head>
+
+    <body class="deffr">
+
+		<table width="100%" height="100%" cellpadding="0" cellspacing="0" border="0">
+			<tr>
+				<td align="center" valign="middle">
+					<table cellpadding="0" cellspacing="0" border="0">
+						<tr>
+                            <td>
+                                <h3>Welcome to the Jive Web Chat Client - Please Login</h3>
+                                    <%	if (errors.get("general") != null) { %>
+                                    	<p class="error-text">
+                                            Error logging in. Make sure your username and
+                                                password are correct. <%= errors.get("general") %>
+	                                    </p>
+                                    <%	} %>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align="center">
+                                <form action="<%= request.getContextPath() %>/ChatServlet"
+                                    method="post" name="loginform">
+                                <input type="hidden" name="command" value="login">
+
+                                <table cellpadding="2" cellspacing="0" border="0">
+                                    <% if (allowLogin) { %>
+                                        <tr>
+	                                        <td>Username:</td>
+	                                        <td>
+		                                        <input type="text" size="40" name="username"
+                                                    class="text">
+		                                        <%	if (errors.get("username") != null) { %>
+                                    			    <span class="error-text"><br>
+			                                            Please enter a valid username.
+			                                        </span>
+                                    		    <%	} %>
+	                                        </td>
+                                        </tr>
+                                        <tr>
+	                                        <td>Password:</td>
+	                                        <td>
+		                                        <input type="password" size="40" name="password"
+                                                    class="text">
+		                                        <%	if (errors.get("password") != null) { %>
+                                    			    <span class="error-text"><br>
+			                                            Please enter a valid password.
+			                                        </span>
+                                    		    <%	} %>
+	                                        </td>
+                                        </tr>
+                                    <%  } %>
+                                    <tr>
+	                                    <td>Nickname:</td>
+	                                    <td>
+		                                    <input type="text" size="40" name="nickname"
+                                                    class="text">
+		                                    <%	if (errors.get("nickname") != null) { %>
+                                  			    <span class="error-text"><br>
+			                                        Please enter a nickname.
+			                                    </span>
+		                                    <%	} %>
+	                                    </td>
+                                    </tr>
+                                    <tr>
+	                                    <td>Room:</td>
+	                                    <td>
+		                                    <input type="text" size="40" name="room"
+                                                    value="test@chat.jivesoftware.com" class="text">
+		                                    <%	if (errors.get("room") != null) { %>
+                                    			<span class="error-text"><br>
+			                                        Please enter a valid room.
+			                                    </span>
+		                                    <%	} %>
+	                                    </td>
+                                    </tr>
+                                </table>
+                            </td>
+                        </tr>
+                        <tr>
+                            <td align="center">
+                                <br>
+                                <% if (allowLogin) { %>
+                                    <input type="submit" name="" value="Login and Chat"
+                                            class="submit">
+                                <%	} %>
+                                <% if (allowAnonymous) { %>
+                                    <input type="submit" name="" value="Anonymously Chat"
+                                            onClick="return anonClick();" class="submit">
+                                <%	} %>
+                                </form>
+                            </td>
+						</tr>
+                        <% if (allowAccountCreate) { %>
+                            <tr>
+                                <td align="center">
+                                    <br>Don't have an account and would like to create one?
+                                        <a href="account_creation.jsp">Click here.</a>
+                                </td>
+                            </tr>
+                        <%  } %>
+					</table>
+				</td>
+			</tr>
+		</table>
+
+        <script language="JavaScript" type="text/javascript">
+            <% if (allowLogin) { %>
+                document.loginform.username.focus();
+            <% } else { %>
+                document.loginform.nickname.focus();
+            <%  } %>
+        </script>
+
+    </body>
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/input_frame.jsp b/CopyOftrunk/apps/webchat/source/web/input_frame.jsp
new file mode 100644
index 000000000..352863160
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/input_frame.jsp
@@ -0,0 +1,75 @@
+<%@	page import="javax.servlet.*" %>
+
+<%	// Get error map as a request attribute:
+    String logoFilename = application.getInitParameter("logoFilename");
+    if (logoFilename == null) {
+        logoFilename = "images/logo.gif";
+    }
+%>
+<html>
+
+	<head>
+		<meta http-equiv="expires" content="0">
+
+		<script>
+			function updateButtonState (textAreaElement) {
+				if (textAreaElement.value != '') {
+					textAreaElement.form.send.disabled = false;
+				} else {
+					textAreaElement.form.send.disabled = true;
+				}
+			}
+
+			function handleKeyEvent (event, textAreaElement) {
+				var form = textAreaElement.form;
+				var keyCode = event.keyCode;
+
+				if (keyCode == null) {
+					keyCode = event.which;
+				}
+
+				if (keyCode == 13) {
+					submitForm(form);
+
+					form.message.value = '';
+				}
+
+				updateButtonState(textAreaElement);
+			}
+
+			function submitForm (formElement) {
+				var textAreaElement = formElement.message;
+				var text = textAreaElement.value;
+				var sForm = window.parent.frames['submitter'].document.chatform;
+
+				sForm.message.value = text;
+				sForm.submit();
+
+				textAreaElement.value = '';
+
+				updateButtonState(textAreaElement);
+
+				return false;
+			}
+
+		</script>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+	</head>
+
+	<body class="deffr">
+        <center>
+		    <form name="chat" onsubmit="return submitForm(this);">
+			    <textarea name="message"
+				        onkeyup="handleKeyEvent(event, this);"
+				        onchange="updateButtonState(this);"></textarea>
+                <br>
+			    <input type="submit" name="send" value="Send" class="submit_right" disabled>
+		    </form>
+        </center>
+
+		<img src="<%= logoFilename %>" class="logo">
+	</body>
+
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp b/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp
new file mode 100644
index 000000000..a2369de83
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/participants_frame.jsp
@@ -0,0 +1,46 @@
+<html>
+
+	<head>
+		<meta http-equiv="expires" content="0">
+
+        <script>
+
+			function verifyLogout () {
+				if (confirm("Are you sure you'd like to logout")) {
+                    // hacky solution to avoid logging out twice due to parent frame's onUnload
+                    try {
+                        document.logout.command.value = "silence";
+                    } catch (e) { }
+
+					window.parent.close();
+
+					return true;
+				}
+
+				return false;
+			}
+
+        </script>
+
+	    <link rel="stylesheet" href="<%= request.getContextPath() %>/style_sheet.jsp"
+                type="text/css">
+	</head>
+
+	<body class="deffr">
+	    <center>In the room:</center>
+	    <br>
+	    <hr width=67%>
+
+        <div id="par__list"> </div>
+
+        <form name="logout" action="<%= request.getContextPath() %>/ChatServlet" method="post">
+		    <input type="hidden" name="command" value="logout">
+	    </form>
+
+        <span class="logout">
+            <a href="<%= request.getContextPath() %>/ChatServlet?command=logout"
+                    onclick="return verifyLogout();" >
+		        <img src="images/logout-16x16.gif" border="0" class="logout"> Logout</a></span>
+	</body>
+
+</html>
diff --git a/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp b/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp
new file mode 100644
index 000000000..381dceeef
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/style_sheet.jsp
@@ -0,0 +1,132 @@
+<%  // Set the content type of the this page to be CSS
+    String contentType = "text/css";
+    String chatAnnouncementColor = application.getInitParameter("chat.announcement-color");
+    String chatOwnerLabelColor = application.getInitParameter("chat.owner-label-color");
+    String chatParticipantLabelColor = application.getInitParameter("chat.participant-label-color");
+    String chatTextColor = application.getInitParameter("chat.text-color");
+    String errorTextColor = application.getInitParameter("error.text-color");
+    String linkColor = application.getInitParameter("link.color");
+    String linkHoverColor = application.getInitParameter("link.hover-color");
+    String linkVisitedColor = application.getInitParameter("link.visited-color");
+    String bodyBGColor = application.getInitParameter("body.background-color");
+    String bodyTextColor = application.getInitParameter("body.text-color");
+    String frameDividerColor = application.getInitParameter("frame.divider-color");
+    String buttonColor = application.getInitParameter("button.color");
+    String buttonTextColor = application.getInitParameter("button.text-color");
+    String textFieldColor = application.getInitParameter("textfield.color");
+    String textFieldTextColor = application.getInitParameter("textfield.text-color");
+    response.setContentType(contentType);
+    if (chatAnnouncementColor == null) {
+        chatAnnouncementColor = "#009d00";
+    }
+    if (chatOwnerLabelColor == null) {
+        chatOwnerLabelColor = "#aa0000";
+    }
+    if (chatParticipantLabelColor == null) {
+        chatParticipantLabelColor = "#0000aa";
+    }
+    if (chatTextColor == null) {
+        chatTextColor = "#434343";
+    }
+    if (errorTextColor == null) {
+        errorTextColor = "#ff0000";
+    }
+    if (linkColor == null) {
+        linkColor = "#045d30";
+    }
+    if (linkHoverColor == null) {
+        linkHoverColor = "#350000";
+    }
+    if (linkVisitedColor == null) {
+        linkVisitedColor = "#3b3757";
+    }
+    if (bodyBGColor == null) {
+        bodyBGColor = "#ffffff";
+    }
+    if (bodyTextColor == null) {
+        bodyTextColor = "#362f2d";
+    }
+    if (frameDividerColor == null) {
+        frameDividerColor = "#83272b";
+    }
+    if (buttonColor == null) {
+        buttonColor = "#d6dfdf";
+    }
+    if (buttonTextColor == null) {
+        buttonTextColor = "#333333";
+    }
+    if (textFieldColor == null) {
+        textFieldColor = "#f7f7fb";
+    }
+    if (textFieldTextColor == null) {
+        textFieldTextColor = "#333333";
+    }
+%>
+
+BODY, TD, TH    { font-family : Tahoma, Arial, Verdana, sans serif; font-size: 13px; }
+
+H3              { font-size : 1.2em; }
+
+.error-text     { color : <%= errorTextColor %>; }
+
+
+/* default unvisited, visited and hover link presentation */
+A:link			{ background: transparent; color: <%= linkColor %>;
+					text-decoration: none; }
+A:visited		{ background: transparent; color: <%= linkVisitedColor %>;
+					text-decoration: none; }
+A:hover			{ background: transparent; color: <%= linkHoverColor %>;
+					text-decoration: underline; }
+
+/**
+ * site wide BODY style rule; the scrollbar stuff only works in IE for windows,
+ *	but doesn't seem to hurt on other browsers..
+ */
+BODY.deffr		{ background-color: <%= bodyBGColor %>; color: <%= bodyTextColor %>;
+				scrollbar-face-color: <%= bodyBGColor %>;
+				scrollbar-shadow-color: <%= bodyTextColor %>;
+				scrollbar-highlight-color: <%= bodyBGColor %>;
+				scrollbar-darkshadow-color: <%= bodyBGColor %>;
+				scrollbar-track-color: <%= bodyBGColor %>;
+				scrollbar-arrow-color: <%= bodyTextColor %>; }
+
+
+FRAME.bordered_left   { border-left: 3px solid <%= frameDividerColor %>; }
+
+
+IMG.logo         { position: absolute; bottom: 12px; left: 10px; }
+
+IMG.logout       { vertical-align: middle; }
+
+
+INPUT.submit		{ background-color: <%= buttonColor %>; color: <%= buttonTextColor %>;
+					font-size: 12px; font-family: Arial, Verdana, sans serif;
+					border-style: ridge; margin: 1px 5px 1px 5px; }
+
+INPUT.submit_right	{ background-color: <%= buttonColor %>; color: <%= buttonTextColor %>;
+					font-size: 12px; font-family: Arial, Verdana, sans serif;
+					border-style: ridge; margin: 1px 5px 1px 5px;
+					position: absolute; right: 10px; }
+
+INPUT.text		    { background-color: <%= textFieldColor %>; color: <%= textFieldTextColor %>;
+					font: normal 12px Arial, Verdana, sans serif; height: 20px; width: 271px;
+					border-style: groove; margin-left: 10px; }
+
+
+SPAN.chat_text  { font: normal 11px Arial, Verdana, sans serif;
+                  color: <%= chatTextColor %>; }
+
+SPAN.chat_announcement  { font: italic 11px Arial, Verdana, sans serif;
+                  color: <%= chatAnnouncementColor %>; }
+
+SPAN.chat_owner  { font: bold 11px Arial, Verdana, sans serif;
+                  color: <%= chatOwnerLabelColor %>; }
+
+SPAN.chat_participant  { font: bold 11px Arial, Verdana, sans serif;
+                  color: <%= chatParticipantLabelColor %>; }
+
+SPAN.logout     { position: absolute; bottom: 12px; right: 15px; }
+
+
+TEXTAREA        { color: <%= textFieldTextColor %>; font: normal 12px Arial, Verdana, sans serif;
+                  width: 500px; height: 130px; }
diff --git a/CopyOftrunk/apps/webchat/source/web/transcript_frame.html b/CopyOftrunk/apps/webchat/source/web/transcript_frame.html
new file mode 100644
index 000000000..a4cc1faf5
--- /dev/null
+++ b/CopyOftrunk/apps/webchat/source/web/transcript_frame.html
@@ -0,0 +1,13 @@
+<html>
+
+	<head>
+		<meta http-equiv="expires" content="0">
+
+		<link rel="stylesheet" href="style_sheet.jsp" type="text/css" media="screen">
+	</head>	
+
+	<body class="deffr">
+		<div id="ytext"> <br></div><div id="enddiv"></div>
+	</body>
+
+</html>
diff --git a/CopyOftrunk/build/README.html b/CopyOftrunk/build/README.html
new file mode 100644
index 000000000..5d6cc5d92
--- /dev/null
+++ b/CopyOftrunk/build/README.html
@@ -0,0 +1,309 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<head>
+	<title>Smack Source Distribution</title>
+    <style type="text/css">
+        BODY {
+            font-size : 100%;
+        }
+        BODY, TD, TH {
+            font-family : tahoma, verdana, arial, helvetica, sans-serif;
+            font-size : 0.8em;
+        }
+        A:hover {
+            text-decoration : none;
+        }
+        .pageheader {
+            font-family : arial, helvetica, sans-serif;
+            font-size : 14pt;
+            font-weight: bold;
+        }
+        .header {
+            font-family : arial, helvetica, sans-serif;
+            font-size : 12pt;
+            font-weight: bold;
+        }
+        .subheader {
+            font-weight: bold;
+            color: #600;
+        }
+        .path {
+            color : #3333cc;
+        }
+        .question {
+            font-style : italic;
+        }
+        .answer {
+            padding-left : 15px;
+        }
+        .code {
+            font-family : courier new;
+            border : 1px #ccc solid;
+            padding : 6px;
+            margin : 5px 20px 5px 20px;
+        }
+        TT {
+            font-family : courier new;
+            font-weight : bold;
+            color : #060;
+        }
+        PRE, CODE {
+            font-family : courier new;
+            font-size : 100%;
+        }
+        .footer {
+            font-size : 0.8em;
+            color : #666;
+            text-align : center;
+        }
+    </style>
+</head>
+
+<body bgcolor="#ffffff">
+
+<font size=4>
+Smack Source Distribution<br>
+</font><br>
+<p>
+
+This document provides detailed information for developers that wish to 
+compile and make changes to the Smack source code.
+
+<p>For additional developer resources, please visit: 
+<a href="http://www.jivesoftware.org/smack/">
+http://www.jivesoftware.org/smack/</a>. The Smack build process is based on Ant. Visit the
+<a href="http://jakarta.apache.org/ant/index.html">Ant website</a>
+for more information. There is no need to download and install Ant - a version of it is included
+in this distribution.
+<p>
+This documentation is divided into two sections:
+<ol>
+	<li> <a href="#setup">Setup</a> -- how to setup your environment for Smack development.
+	<li> <a href="#tasks">Build tasks</a> -- tasks that can be performed using the build program.
+</ol>
+
+<p><a name="setup"><b><font color="#0066cc">1.</font> Setup Your Environment</b></a><p>
+
+Getting your machine ready for Smack development requires a few steps. Wherever
+possible, instructions are provided for both Unix/Linux and Windows users.
+<p>
+<b><a name="javaSetup">Configure Java</a></b>
+<ul>
+	Java 2 (JDK 1.2 or later) must be installed and setup on your machine. To test the installation,
+	open a shell in a Unix or a MS-DOS prompt in Windows. Check your version of
+	Java with "java -version" -- it must version 1.2 or greater.
+	If Java isn't installed, download a copy from the 
+	<a href="http://java.sun.com/">Java website</a>.
+	<p>
+	<font color="red">Important!</font> -- the Smack build tool needs to know
+	where Java is installed on your system. You must configure the "JAVA_HOME"
+	environment variable to point to the correct directory. Instructions on
+	how to set this variable for various platforms are as follows:
+	<p>
+	<ul>
+		<li> Unix/Linux
+			<ol>
+				<li>Edit the ".profile" file in your home directory (or 
+					corresponding file for your shell).
+				<li>Set the JAVA_HOME environment variable by adding the
+					following line to the file:
+					<p></font><code>
+					export JAVA_HOME=/usr/local/jdk1.3
+					</code><font face="verdana, arial, helvetica" size=2>
+					<p>
+					The value "/usr/local/jdk1.3" should be replaced with your actual
+					Java directory. Be sure there are no spaces after the end of 
+					the directory name. Do not add an extra slash after the directory name.
+				<li>Save changes to the file and then "source" it:
+					<p></font><code>
+					source .profile
+					</code><font face="verdana, arial, helvetica" size=2>
+					<p>
+					The JAVA_HOME variable should now be configured correctly.					 
+			</ol>
+	 	<p>
+		<li> WindowsNT/2000 
+			<ol>
+				<li>Navigate to your desktop and right click on "My Computer";
+					choose properties.
+				<li>Select the "Advanced" tab and click on the 
+					"Environment Variables" button.
+				<li>Click the "New..." button in the System variables section.
+					Enter the variable name "JAVA_HOME" and set the variable 
+					value to the full path of your Java installation. For example,
+					"c:\jdk1.3". Be sure to not add an extra slash to the end
+					of the directory name.
+				<li>Click "OK" in all of the menus to accept the changes.
+				<li>Close any open command prompt windows. The next time you
+					open a command prompt, the "JAVA_HOME" variable will be set
+					correctly.
+			</ol>
+		<p>
+		<li> Windows95/98
+			<ol>
+				<li>Open your autoexec.bat file (often at "c:\autoexec.bat") using Notepad.
+				<li>Add a line to the end of the file that resembles the following:
+					<p></font><code>
+					set JAVA_HOME=c:\jdk1.3
+					</code><font face="verdana, arial, helvetica" size=2>
+					<p>
+					The value "c:\jdk1.3" should be replaced with your actual
+					Java directory. Be sure there are no spaces between
+					the "=" sign or after the end of the directory name. Do
+					not add an extra slash after the directory name.
+				<li>Save changes to autoexec.bat and restart your computer.					
+			</ol>
+	</ul>
+</ul>
+
+<p><b><a name="checkout">Test the Build Script</a></b><p>
+<ul>
+Navigate into the subdirectory of this distribution named "build" via the command-line.<p>
+
+<table boder=0 cellpadding=2 cellspacing=0><td bgcolor="#EEEEEE">
+<font face="verdana, arial, helvetica" size=2> 
+<font color="red">Linux/Unix users only:</font>You must make the ant script
+executable. From the build directory, type:
+<p></font><code>
+chmod u+x ant
+</code><font face="verdana, arial, helvetica" size=2> 
+</td></table>
+
+<p>
+Now, invoke the build tool to compile the Smack source code
+
+<p>
+Windows:</font><code> ant  <br>
+</code><font face="verdana, arial, helvetica" size=2>
+Unix/Linux:</font><code> ./ant 
+</code><font face="verdana, arial, helvetica" size=2>
+<p>
+
+If the build tool is invoked correctly and Smack compiles, you've correctly
+configured your copy of the Smack developer distribution.
+
+</ul>
+
+<p><b>Finished!</b><p>
+<ul>
+If you've gotten this far, you've finished setting up the Smack developer
+distribution. Now, read below to learn about all of the tasks that you can perform
+with the build tool.
+</ul>
+
+<br><br>
+	
+<p><b><a name="tasks"><font color="#0066cc">2.</font> Build Tasks</a></b><p>
+
+	The list of build tasks is below. All build commands should be
+	run from the "build" directory of your Smack distribution.
+
+	<br><br>
+
+	For a list of the commands and a brief description from the command line, type
+	<code>ant -projecthelp</code>. For more complete help, read the documentation below.
+
+	<br><br>
+
+	To execute a build task, type <code>ant [options] targetname</code> where "targetname" is
+	one of the targets listed below:
+	
+	<ul>
+        <li><a href="#noparams"><i>Default</i></a>
+        <li><a href="#compile">compile</a>
+        <li><a href="#jar">jar</a>
+        <li><a href="#javadoc">javadoc</a>
+        <li><a href="#clean">clean</a>
+	</ul>
+<p>
+Each task is documented with a syntax guide and description. Optional paramaters 
+for each task are enclosed with braces.
+	
+<!--COMPILE-->	
+<p><b><a name="noparams"><i>Default</i></a></b>	
+<ul>
+<i>Syntax:</i><p>
+
+</font><code>
+ant<br>
+</code><font face="verdana, arial, helvetica" size=2>
+
+<p><i>Description:</i></p>
+
+Equivalent of calling "ant <a href="#jar">jar</a>".
+
+<p>[<a href="#tasks">return to task list</a>]
+</ul>
+	
+<!--COMPILE-->	
+<p><b><a name="compile">compile</a></b>	
+<ul>
+<i>Syntax:</i><p>
+
+</font><code>
+ant compile <br>
+</code><font face="verdana, arial, helvetica" size=2>
+
+<p><i>Description:</i></p>
+
+Compiles all the Smack source code.
+The build directory is the "classes" directory under your Smack source distribution.
+
+<p>[<a href="#tasks">return to task list</a>]
+</ul>
+
+
+<!--JAR-->
+<p><b><a name="jar">jar</a></b>	
+<ul>
+<i>Syntax:</i><p>
+
+</font><code>
+ant jar <br>
+</code><font face="verdana, arial, helvetica" size=2>
+
+<p><i>Description:</i></p>
+
+Bundles the Smack class files into a JAR file (smack.jar)
+that is suitable for adding
+into the classpath of an application server.
+<p>[<a href="#tasks">return to task list</a>]
+</ul>
+
+
+<!--JAVADOC-->
+<p><b><a name="javadoc">javadoc</a></b>
+<ul>
+<i>Syntax:</i><p>
+
+</font><code>
+ant javadoc <br>
+</code><font face="verdana, arial, helvetica" size=2>
+
+<p><i>Description:</i></p>
+
+JavaDocs all Smack source code in the source directory.
+
+<p>[<a href="#tasks">return to task list</a>]
+</ul>
+
+<!--CLEAN-->
+<p><b><a name="clean">clean</a></b>	
+<ul>
+<i>Syntax:</i><p>
+
+</font><code>
+ant clean<br>
+</code><font face="verdana, arial, helvetica" size=2>
+
+<p><i>Description:</i></p>
+
+Cleans your Smack distribution directory by deleting compiled class files, the
+smack.jar file and Javadoc files.<p>
+
+<p>[<a href="#tasks">return to task list</a>]
+</ul>
+
+</body>
+</html>
diff --git a/CopyOftrunk/build/ant b/CopyOftrunk/build/ant
new file mode 100644
index 000000000..0543a62e2
--- /dev/null
+++ b/CopyOftrunk/build/ant
@@ -0,0 +1,42 @@
+#! /bin/sh
+
+# //--------------------------------------------------------------------------//
+# // $RCSfile$
+# // $Revision$
+# // $Date$
+# //
+# // Standard Jive Software ant file. Do not change this file. If you do,
+# // you will have seven years of bad luck and bad builds.
+# //--------------------------------------------------------------------------//
+
+# //--------------------------------------------------------------------------//
+# // Uncomment the following lines if you wish to set JAVA_HOME in this script
+# //--------------------------------------------------------------------------//
+# JAVA_HOME=
+# EXPORT JAVA_HOME
+
+# //--------------------------------------------------------------------------//
+# // Check for the JAVA_HOME environment variable                             //
+# //--------------------------------------------------------------------------//
+if [ "$JAVA_HOME" != "" ] ; then
+    # //----------------------------------------------------------------------//
+    # // Create Ant's classpath                                               //
+    # //----------------------------------------------------------------------//
+    CP=$JAVA_HOME/lib/tools.jar:./ant.jar:./junit.jar
+    
+    # //----------------------------------------------------------------------//
+    # // Run ant                                                              //
+    # //----------------------------------------------------------------------//
+    $JAVA_HOME/bin/java -classpath $CP -Dant.home=. org.apache.tools.ant.Main $@
+else
+    # //----------------------------------------------------------------------//
+    # // No JAVA_HOME error message                                           //
+    # //----------------------------------------------------------------------//
+    echo "Jive Forums Build Error:"
+    echo ""
+    echo "The JAVA_HOME environment variable is not set. JAVA_HOME should point"
+    echo "to your java directory, ie: /usr/local/bin/jdk1.3. You can set"
+    echo "this via the command line like so:"
+    echo "  export JAVA_HOME=/usr/local/bin/jdk1.3"
+fi
+
diff --git a/CopyOftrunk/build/ant-contrib.jar b/CopyOftrunk/build/ant-contrib.jar
new file mode 100644
index 000000000..14bfe5a26
Binary files /dev/null and b/CopyOftrunk/build/ant-contrib.jar differ
diff --git a/CopyOftrunk/build/ant.bat b/CopyOftrunk/build/ant.bat
new file mode 100644
index 000000000..14c97c382
--- /dev/null
+++ b/CopyOftrunk/build/ant.bat
@@ -0,0 +1,51 @@
+@echo off
+
+rem //------------------------------------------------------------------------//
+rem // $RCSfile$
+rem // $Revision$
+rem // $Date$
+rem //
+rem // Standard Jive Software ant.bat file. Do not change this file. If you do,
+rem // you will have seven years of bad luck and bad builds.
+rem //------------------------------------------------------------------------//
+
+rem //------------------------------------------------------------------------//
+rem // Uncomment the following if you wish to set JAVA_HOME in this bat file:
+rem //------------------------------------------------------------------------//
+rem SET JAVA_HOME=
+
+rem //------------------------------------------------------------------------//
+rem // Check for the JAVA_HOME environment variable
+rem //------------------------------------------------------------------------//
+if "%JAVA_HOME%" == "" goto noJavaHome
+
+rem //------------------------------------------------------------------------//
+rem // Make the correct classpath (should include the java jars and the
+rem // Ant jars)
+rem //------------------------------------------------------------------------//
+SET CP=%JAVA_HOME%\lib\tools.jar;.\ant.jar;.\junit.jar
+
+rem //------------------------------------------------------------------------//
+rem // Run Ant
+rem // Note for Win 98/95 users: You need to change "%*" in the following
+rem // line to be "%1 %2 %3 %4 %5 %6 %7 %8 %9"
+rem //------------------------------------------------------------------------//
+%JAVA_HOME%\bin\java -Xms32m -Xmx128m -classpath %CP% -Dant.home=. org.apache.tools.ant.Main %*
+goto end
+
+rem //------------------------------------------------------------------------//
+rem // Error message for missing JAVA_HOME
+rem //------------------------------------------------------------------------//
+:noJavaHome
+echo.
+echo Jive Forums Build Error:
+echo.
+echo The JAVA_HOME environment variable is not set. JAVA_HOME should point to
+echo your java directory, ie: c:\jdk1.3.1. You can set this via the command
+echo line like so:
+echo   SET JAVA_HOME=c:\jdk1.3
+echo.
+goto end
+
+:end
+
diff --git a/CopyOftrunk/build/ant.jar b/CopyOftrunk/build/ant.jar
new file mode 100644
index 000000000..dcd4b8da8
Binary files /dev/null and b/CopyOftrunk/build/ant.jar differ
diff --git a/CopyOftrunk/build/build.properties b/CopyOftrunk/build/build.properties
new file mode 100644
index 000000000..ca1880e71
--- /dev/null
+++ b/CopyOftrunk/build/build.properties
@@ -0,0 +1,15 @@
+#
+# $RCSfile$
+# $Revision$
+# $Date$
+#
+
+# Test properties. Uncomment these to override default values declared
+# in the build.xml file.
+
+# test.host=
+# test.port=
+# test.admin.username=
+# test.admin.password=
+# test.admin.resource=
+# test.smack.debug=
\ No newline at end of file
diff --git a/CopyOftrunk/build/build.xml b/CopyOftrunk/build/build.xml
new file mode 100644
index 000000000..e4141d4ad
--- /dev/null
+++ b/CopyOftrunk/build/build.xml
@@ -0,0 +1,311 @@
+<?xml version="1.0"?>
+
+<!-- Smack Build Script ========================================== -->
+<!-- Jive Software      ============================================== -->
+
+<!--
+    $RCSfile$
+    $Revision$
+    $Date$
+-->
+
+<project name="Smack" default="all" basedir="..">
+
+    <!-- TASKDEFS -->
+    <!-- ======================================================================================= -->
+    <!-- ======================================================================================= -->
+
+    <taskdef resource="net/sf/antcontrib/antcontrib.properties">
+        <classpath>
+            <pathelement location="${basedir}/build/ant-contrib.jar"/>
+        </classpath>
+    </taskdef>
+
+    <!-- PROPERTIES -->
+    <!-- ======================================================================================= -->
+
+    <property file="${basedir}/build/build.properties" />
+
+    <property name="compile.dir" value="${basedir}/classes" />
+    <property name="compile.test.dir" value="${basedir}/classes-test" />
+    <property name="jar.dest.dir" value="${basedir}" />
+    <property name="javadoc.dest.dir" value="${basedir}/javadoc" />
+    <property name="build.lib.dir" value="${basedir}/build/lib" />
+    <property name="merge.lib.dir" value="${basedir}/build/merge" />
+
+    <property name="version.major" value="1" />
+    <property name="version.minor" value="5" />
+    <property name="version.revision" value="1" />
+    <property name="version.name" value="${version.major}.${version.minor}.${version.revision}" />
+
+    <!-- Test props - override these defaults in the properties file or in command line -->
+    <property name="test.host" value="localhost" />
+    <property name="test.port" value="5222" />
+    <property name="test.admin.username" value="admin" />
+    <property name="test.admin.password" value="admin" />
+    <property name="test.admin.resource" value="Test" />
+    <property name="test.smack.debug" value="false" />
+
+    <!-- PATHS, DEPENDIENCIES, PATTERNS -->
+    <!-- ======================================================================================= -->
+    <!-- ======================================================================================= -->
+
+    <patternset id="test.cases">
+        <include name="org/jivesoftware/smack/**/*Test.java" />
+        <exclude name="org/jivesoftware/smack/**/Messenger*Test.java" />
+    </patternset>
+
+    <patternset id="messenger.test.cases">
+        <include name="org/jivesoftware/smack/**/Messenger*Test.java" />
+    </patternset>
+
+    <!-- TARGETS -->
+    <!-- ======================================================================================= -->
+
+    <!-- all -->
+    <!-- ======================================================================================= -->
+    <target name="all" depends="jar" description="Calls 'jar' target by default">
+    </target>
+
+    <!-- compile -->
+    <!-- ======================================================================================= -->
+    <target name="compile" description="Compiles all source to ${compile.dir}.">
+        <!-- make target dir -->
+        <mkdir dir="${compile.dir}" />
+        <javac
+            destdir="${compile.dir}"
+            includeAntRuntime="no"
+            debug="on"
+            source="1.3"
+            target="1.2"
+        >
+            <src path="${basedir}/source" />
+            <classpath>
+                <fileset dir="${build.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${merge.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+            </classpath>
+        </javac>
+    </target>
+
+    <!-- compile-test -->
+    <!-- ======================================================================================= -->
+    <target name="compile-test" description="Compiles all source to ${compile.dir}.">
+        <!-- make target dir -->
+        <mkdir dir="${compile.test.dir}" />
+        <javac
+            destdir="${compile.test.dir}"
+            includeAntRuntime="no"
+            debug="on"
+            source="1.3"
+            target="1.2"
+        >
+            <src path="${basedir}/test" />
+            <classpath>
+                <fileset dir="${build.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${merge.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${basedir}/build">
+                    <include name="junit.jar"/>
+                    <include name="xmlunit.jar"/>
+                </fileset>
+                <pathelement location="${compile.dir}" />
+            </classpath>
+        </javac>
+    </target>
+
+    <!-- jar -->
+    <!-- ======================================================================================= -->
+    <target name="jar" depends="compile" unless="jar.uptodate" description="Produces smack.jar">
+
+        <copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack-config.xml" />
+        <jar destfile="${jar.dest.dir}/smack.jar"
+            basedir="${compile.dir}"
+            includes="org/jivesoftware/smack/**/*.class, **/smack-config.xml"
+         >
+             <zipfileset src="${merge.lib.dir}/xpp.jar"/>
+        </jar>
+        <copy todir="${compile.dir}/META-INF" file="${basedir}/build/resources/META-INF/smack.providers" />
+        <jar destfile="${jar.dest.dir}/smackx.jar"
+            basedir="${compile.dir}"
+            includes="org/jivesoftware/smackx/**/*.class, **/*.providers"
+            excludes="org/jivesoftware/smackx/debugger/*.class"
+        >
+            <manifest>
+                <attribute name="Class-Path" value="smack.jar" />
+            </manifest>
+        </jar>
+        <copy todir="${compile.dir}/images">
+            <fileset dir="${basedir}/build/resources/images">
+                <include name="*.png"/>
+            </fileset>
+        </copy>
+        <jar destfile="${jar.dest.dir}/smackx-debug.jar"
+            basedir="${compile.dir}"
+            includes="org/jivesoftware/smackx/debugger/*.class, **/*.png"
+        >
+            <manifest>
+                <attribute name="Class-Path" value="smack.jar" />
+            </manifest>
+        </jar>
+        <delete file="${compile.dir}/META-INF/smack-config.xml" />
+        <delete file="${compile.dir}/META-INF/smack.providers" />
+        <delete>
+            <fileset dir="${compile.dir}/images">
+                <include name="*.png"/>
+            </fileset>
+        </delete>
+    </target>
+
+    <!-- jar -->
+    <!-- ======================================================================================= -->
+    <target name="jar-test" depends="compile-test" description="Produces jar of test code">
+        <jar destfile="${jar.dest.dir}/smack-test.jar"
+            basedir="${compile.test.dir}"
+            includes="org/jivesoftware/smack/**/*.class"
+        />
+    </target>
+
+    <!-- javadoc -->
+    <!-- ======================================================================================= -->
+    <target name="javadoc" description="JavaDocs the Smack source code">
+
+        <mkdir dir="${javadoc.dest.dir}" />
+        <javadoc
+            packagenames="org.jivesoftware.smack.*, org.jivesoftware.smackx.*"
+            sourcepath="${basedir}/source"
+            destdir="${javadoc.dest.dir}"
+            author="true"
+            windowtitle="Smack ${version.name} Documentation"
+            overview="${basedir}/source/overview.html"
+        >
+            <classpath>
+                <fileset dir="${build.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${merge.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+            </classpath>
+            <doctitle><![CDATA[<font face="arial,helvetica">Smack ${version.name}</font>]]></doctitle>
+            <header><![CDATA[<b>Smack</b>]]></header>
+            <bottom><![CDATA[<i>Copyright &copy; 2003 Jive Software. </i>]]></bottom>
+            <link href="http://java.sun.com/j2se/1.3/docs/api/" />
+            <link href="http://java.sun.com/j2ee/sdk_1.2.1/techdocs/api/" />
+        </javadoc>
+    </target>
+
+
+    <!-- test -->
+    <!-- ======================================================================================= -->
+    <target name="test" depends="compile, jar-test" unless="no.test">
+
+        <echo>
+
+
+            **** no.test: ${no.test}
+
+
+        </echo>
+
+        <property name="test.messenger" value="false" />
+
+        <if>
+            <not><equals arg1="test.messenger" arg2="true" /></not>
+            <then>
+                <property name="test.classes" value="test.cases" />
+            </then>
+        </if>
+
+        <junit printsummary="on"
+            fork="false"
+            haltonfailure="false"
+            failureproperty="tests.failed"
+            showoutput="true">
+
+            <sysproperty key="smack.test.host" value="${test.host}" />
+            <sysproperty key="smack.test.port" value="${test.port}" />
+            <sysproperty key="smack.test.admin.username" value="${test.admin.username}" />
+            <sysproperty key="smack.test.admin.password" value="${test.admin.password}" />
+            <sysproperty key="smack.test.admin.resource" value="${test.admin.resource}" />
+            <sysproperty key="smack.debug" value="${test.smack.debug}" />
+
+            <classpath>
+                <fileset dir="${build.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${basedir}/build">
+                    <include name="xmlunit.jar"/>
+                </fileset>
+                <fileset dir="${merge.lib.dir}">
+                    <include name="*.jar"/>
+                </fileset>
+                <fileset dir="${basedir}">
+                    <include name="smack-test.jar"/>
+                </fileset>
+                <pathelement location="${compile.dir}" />
+            </classpath>
+
+            <formatter type="brief" usefile="false"/>
+
+            <batchtest>
+                <fileset dir="${basedir}/test">
+                    <patternset refid="${test.classes}" />
+                </fileset>
+            </batchtest>
+        </junit>
+
+        <fail if="tests.failed" message="** Tests failed, see test log. **" />
+    </target>
+
+
+    <!-- test -->
+    <!-- ======================================================================================= -->
+    <target name="test.messenger" depends="compile, jar-test" unless="no.test">
+        <antcall target="test" inheritall="true" inheritrefs="true">
+            <param name="test.messenger" value="true" />
+            <param name="test.classes" value="messenger.test.cases" />
+        </antcall>
+    </target>
+
+
+    <!-- release -->
+    <!-- ======================================================================================= -->
+    <target name="release" if="release.exists" depends="release-exists">
+        <antcall target="jar">
+            <param name="no.test" value="true" />
+        </antcall>
+        <antcall target="javadoc">
+            <param name="no.test" value="true" />
+        </antcall>
+        <ant antfile="${basedir}/build/release.xml" />
+    </target>
+
+
+    <!-- release-exists -->
+    <!-- ======================================================================================= -->
+    <target name="release-exists" >
+        <available file="${basedir}/build/release.xml" property="release.exists"/>
+    </target>
+
+
+    <!-- clean -->
+    <!-- ======================================================================================= -->
+    <target name="clean" description="Deletes all generated content.">
+    	<delete dir="${javadoc.dest.dir}" />
+        <delete dir="${compile.dir}" />
+        <delete dir="${compile.test.dir}" />
+        <delete file="${basedir}/smack.jar" />
+        <delete file="${basedir}/smackx.jar" />
+        <delete file="${basedir}/smackx-debug.jar" />
+        <delete file="${basedir}/smack-test.jar" />
+        <delete dir="${basedir}/release" />
+    </target>
+
+</project>
diff --git a/CopyOftrunk/build/junit.jar b/CopyOftrunk/build/junit.jar
new file mode 100644
index 000000000..02ac8fb5b
Binary files /dev/null and b/CopyOftrunk/build/junit.jar differ
diff --git a/CopyOftrunk/build/lib/jcert.jar b/CopyOftrunk/build/lib/jcert.jar
new file mode 100644
index 000000000..6d1c90e23
Binary files /dev/null and b/CopyOftrunk/build/lib/jcert.jar differ
diff --git a/CopyOftrunk/build/lib/jnet.jar b/CopyOftrunk/build/lib/jnet.jar
new file mode 100644
index 000000000..2f794650a
Binary files /dev/null and b/CopyOftrunk/build/lib/jnet.jar differ
diff --git a/CopyOftrunk/build/lib/jsse.jar b/CopyOftrunk/build/lib/jsse.jar
new file mode 100644
index 000000000..6a3f64614
Binary files /dev/null and b/CopyOftrunk/build/lib/jsse.jar differ
diff --git a/CopyOftrunk/build/merge/xpp.jar b/CopyOftrunk/build/merge/xpp.jar
new file mode 100644
index 000000000..80f659ca2
Binary files /dev/null and b/CopyOftrunk/build/merge/xpp.jar differ
diff --git a/CopyOftrunk/build/projects/Smack.iml b/CopyOftrunk/build/projects/Smack.iml
new file mode 100644
index 000000000..9fe263f33
--- /dev/null
+++ b/CopyOftrunk/build/projects/Smack.iml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module version="4" relativePaths="true" type="JAVA_MODULE">
+  <component name="ModuleRootManager" />
+  <component name="NewModuleRootManager">
+    <output url="file://$MODULE_DIR$/../../classes" />
+    <exclude-output />
+    <exclude-exploded />
+    <content url="file://$MODULE_DIR$/../..">
+      <sourceFolder url="file://$MODULE_DIR$/../../source" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/../../test" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../lib/jsse.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../merge/xpp.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../lib/jcert.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../lib/jnet.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../ant.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../ant-contrib.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/../junit.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntryProperties />
+  </component>
+</module>
+
diff --git a/CopyOftrunk/build/projects/Smack.ipr b/CopyOftrunk/build/projects/Smack.ipr
new file mode 100644
index 000000000..87af33d40
--- /dev/null
+++ b/CopyOftrunk/build/projects/Smack.ipr
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4" relativePaths="true">
+  <component name="AntConfiguration">
+    <defaultAnt bundledAnt="true" />
+    <buildFile url="file://$PROJECT_DIR$/../build.xml">
+      <additionalClassPath />
+      <antReference projectDefault="true" />
+      <customJdkName value="" />
+      <maximumHeapSize value="128" />
+      <properties />
+    </buildFile>
+  </component>
+  <component name="CodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="LINE_SEPARATOR" value="&#10;" />
+        <option name="ELSE_ON_NEW_LINE" value="true" />
+        <option name="WHILE_ON_NEW_LINE" value="true" />
+        <option name="CATCH_ON_NEW_LINE" value="true" />
+        <option name="FINALLY_ON_NEW_LINE" value="true" />
+        <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+        <option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
+        <option name="RIGHT_MARGIN" value="100" />
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </component>
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <option name="CLEAR_OUTPUT_DIRECTORY" value="false" />
+    <option name="DEPLOY_AFTER_MAKE" value="0" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+  </component>
+  <component name="DataSourceManagerImpl" />
+  <component name="DependenciesAnalyzeManager">
+    <option name="myForwardDirection" value="false" />
+  </component>
+  <component name="DependencyValidationManager" />
+  <component name="EntryPointsManager">
+    <entry_points />
+  </component>
+  <component name="ExportToHTMLSettings">
+    <option name="PRINT_LINE_NUMBERS" value="false" />
+    <option name="OPEN_IN_BROWSER" value="false" />
+    <option name="OUTPUT_DIRECTORY" />
+  </component>
+  <component name="GUI Designer component loader factory" />
+  <component name="JavacSettings">
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="DEPRECATION" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+    <option name="MAXIMUM_HEAP_SIZE" value="128" />
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="JikesSettings">
+    <option name="JIKES_PATH" value="" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="DEPRECATION" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="IS_EMACS_ERRORS_MODE" value="true" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+    </group>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/Smack.iml" filepath="$PROJECT_DIR$/Smack.iml" />
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="false" jdk-15="false" project-jdk-name="JDK 1.4.2" />
+  <component name="RmicSettings">
+    <option name="IS_EANABLED" value="false" />
+    <option name="DEBUGGING_INFO" value="true" />
+    <option name="GENERATE_NO_WARNINGS" value="false" />
+    <option name="GENERATE_IIOP_STUBS" value="false" />
+    <option name="ADDITIONAL_OPTIONS_STRING" value="" />
+  </component>
+  <component name="libraryTable">
+    <library name="Smack">
+      <CLASSES>
+        <root url="jar://$PROJECT_DIR$/../lib/jcert.jar!/" />
+        <root url="jar://$PROJECT_DIR$/../lib/jnet.jar!/" />
+        <root url="jar://$PROJECT_DIR$/../lib/jsse.jar!/" />
+        <root url="jar://$PROJECT_DIR$/../merge/xpp.jar!/" />
+      </CLASSES>
+      <JAVADOC />
+      <SOURCES />
+    </library>
+  </component>
+  <component name="uidesigner-configuration">
+    <option name="INSTRUMENT_CLASSES" value="true" />
+    <option name="COPY_FORMS_RUNTIME_TO_OUTPUT" value="true" />
+  </component>
+  <UsedPathMacros />
+</project>
+
diff --git a/CopyOftrunk/build/release.xml b/CopyOftrunk/build/release.xml
new file mode 100644
index 000000000..53005e411
--- /dev/null
+++ b/CopyOftrunk/build/release.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+
+<!-- Smack Build Script ========================================== -->
+<!-- Jive Software      ============================================== -->
+
+<!--
+    $RCSfile$
+    $Revision$
+    $Date$
+-->
+
+<project name="Smack Release Script" default="all" basedir="..">
+
+    <!-- Include Ant Optional Tasks -->
+    <taskdef resource="net/sf/antcontrib/antcontrib.properties">
+        <classpath>
+            <pathelement location="${basedir}/build/ant-contrib.jar"/>
+        </classpath>
+    </taskdef>
+
+    <!-- PROPERTIES -->
+    <!-- ======================================================================================= -->
+
+    <!-- TARGETS -->
+    <!-- ======================================================================================= -->
+
+    <!-- all -->
+    <!-- ======================================================================================= -->
+    <target name="all">
+        <!-- create release properties -->
+        <if>
+            <equals arg1="${dailybuild}" arg2="true" />
+            <then>
+                <tstamp>
+                    <format property="build.date" pattern="yyyy-MM-dd" locale="en"/>
+                </tstamp>
+                <property name="release.name" value="smack-${build.date}" />
+                <property name="release-dev.name" value="smack-dev-${build.date}" />
+            </then>
+            <else>
+                <property name="release.name" value="smack-${version.name}" />
+                <property name="release-dev.name" value="smack-dev-${version.name}" />
+            </else>
+        </if>
+        <property name="release.dir" value="${basedir}/release/${release.name}" />
+        <property name="release-dev.dir" value="${basedir}/release/${release-dev.name}" />
+        <!-- create release dirs -->
+        <mkdir dir="${release.dir}" />
+        <mkdir dir="${release-dev.dir}" />
+        <!-- Copy smack.jar -->
+        <copy todir="${release.dir}">
+            <fileset dir="${jar.dest.dir}" includes="smack.jar" />
+            <fileset dir="${jar.dest.dir}" includes="smackx.jar" />
+            <fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" />
+        </copy>
+        <copy todir="${release-dev.dir}">
+            <fileset dir="${jar.dest.dir}" includes="smack.jar" />
+            <fileset dir="${jar.dest.dir}" includes="smackx.jar" />
+            <fileset dir="${jar.dest.dir}" includes="smackx-debug.jar" />
+        </copy>
+        <!-- Copy build dir -->
+        <copy todir="${release-dev.dir}/build">
+            <fileset dir="${basedir}/build">
+                <include name="ant*" />
+                <include name="junit.jar" />
+                <include name="build.xml" />
+                <include name="README.html" />
+                <include name="lib/*.jar" />
+                <include name="merge/*.jar" />
+            </fileset>
+        </copy>
+        <!-- Copy Javadocs -->
+        <copy todir="${release.dir}/javadoc">
+            <fileset dir="${basedir}/javadoc" includes="**/*.*" />
+        </copy>
+        <copy todir="${release-dev.dir}/javadoc">
+            <fileset dir="${basedir}/javadoc" includes="**/*.*" />
+        </copy>
+        <!-- Copy documentation -->
+        <copy todir="${release.dir}/documentation">
+            <fileset dir="${basedir}/documentation" includes="**/*.*" />
+        </copy>
+        <copy todir="${release-dev.dir}/documentation">
+            <fileset dir="${basedir}/documentation" includes="**/*.*" />
+        </copy>
+        <!-- Copy source -->
+        <copy todir="${release-dev.dir}/source">
+            <fileset dir="${basedir}/source" includes="**/*.java" />
+            <fileset dir="${basedir}/source" includes="**/*.html" />
+        </copy>
+        <copy todir="${release-dev.dir}/test">
+            <fileset dir="${basedir}/test" includes="**/*.java" />
+        </copy>
+        <!-- Copy resources -->
+        <copy todir="${release-dev.dir}/build/resources">
+            <fileset dir="${basedir}/build/resources" includes="META-INF/smack.providers" />
+            <fileset dir="${basedir}/build/resources" includes="META-INF/smack-config.xml" />
+        </copy>
+        <copy todir="${release-dev.dir}/build/resources/images">
+            <fileset dir="${basedir}/build/resources/images">
+                <include name="*.png"/>
+            </fileset>
+        </copy>
+        <!-- Copy readme.html and changelog.html -->
+        <copy todir="${release.dir}">
+            <fileset dir="${basedir}/build/resources/releasedocs" includes="*.html" />
+        </copy>
+        <copy todir="${release-dev.dir}">
+            <fileset dir="${basedir}/build/resources/releasedocs" includes="*.html" />
+        </copy>
+        <!-- Package -->
+        <if>
+            <equals arg1="${dailybuild}" arg2="true" />
+            <then>
+                <zip destfile="${basedir}/release/${release-dev.name}.zip"
+                    basedir="${release-dev.dir}/.."
+                    includes="${release-dev.name}/**/*.*"
+                />
+                <tar destfile="${basedir}/release/${release-dev.name}.tar.gz"
+                    basedir="${release-dev.dir}/.."
+                    includes="${release-dev.name}/**/*.*"
+                    compression="gzip"
+                />
+            </then>
+            <else>
+                <zip destfile="${basedir}/release/${release.name}.zip"
+                    basedir="${release.dir}/.."
+                    includes="${release.name}/**/*.*"
+                />
+                <tar destfile="${basedir}/release/${release.name}.tar.gz"
+                    basedir="${release.dir}/.."
+                    includes="${release.name}/**/*.*"
+                    compression="gzip"
+                />
+                <zip destfile="${basedir}/release/${release-dev.name}.zip"
+                    basedir="${release-dev.dir}/.."
+                    includes="${release-dev.name}/**/*.*"
+                />
+                <tar destfile="${basedir}/release/${release-dev.name}.tar.gz"
+                    basedir="${release-dev.dir}/.."
+                    includes="${release-dev.name}/**/*.*"
+                    compression="gzip"
+                />
+            </else>
+        </if>
+
+        <echo>
+-----------------------------------------------
+Release made, testing Ant targets of release...
+-----------------------------------------------
+        </echo>
+
+        <!-- call the release tester -->
+        <antcall target="test" />
+    </target>
+
+    <!-- test -->
+    <!-- ======================================================================================= -->
+    <target name="test">
+        <property name="testdir" value="${release-dev.dir}/.test" />
+
+        <!-- copy the build to a temp dir so we can run sanity tests -->
+        <mkdir dir="${testdir}" />
+        <copy todir="${testdir}">
+            <fileset dir="${release-dev.dir}">
+                <exclude name=".test/**/*.*" />
+            </fileset>
+        </copy>
+
+        <!-- run sanity tests -->
+        <ant dir="${testdir}" antfile="build/build.xml" target="jar" inheritAll="false">
+            <property name="no.test" value="true" />
+        </ant>
+        <ant dir="${testdir}" antfile="build/build.xml" target="javadoc" inheritAll="false">
+            <property name="no.test" value="true" />
+        </ant>
+        <ant dir="${testdir}" antfile="build/build.xml" target="clean" inheritAll="false">
+            <property name="no.test" value="true" />
+        </ant>
+
+        <echo>
+----------------------------
+...release tests pass, done.
+----------------------------
+        </echo>
+    </target>
+
+</project>
diff --git a/CopyOftrunk/build/resources/META-INF/smack-config.xml b/CopyOftrunk/build/resources/META-INF/smack-config.xml
new file mode 100644
index 000000000..6d373de68
--- /dev/null
+++ b/CopyOftrunk/build/resources/META-INF/smack-config.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!-- Smack configuration file. -->
+<smack>
+
+    <!-- Classes that will be loaded when Smack starts -->
+    <startupClasses>
+        <className>org.jivesoftware.smackx.ServiceDiscoveryManager</className>
+        <className>org.jivesoftware.smackx.XHTMLManager</className>
+        <className>org.jivesoftware.smackx.muc.MultiUserChat</className>
+    </startupClasses>
+
+    <!-- Paket reply timeout in milliseconds -->
+    <packetReplyTimeout>5000</packetReplyTimeout>
+
+    <!-- Keep-alive interval in milleseconds -->
+    <keepAliveInterval>30000</keepAliveInterval>
+
+</smack>
\ No newline at end of file
diff --git a/CopyOftrunk/build/resources/META-INF/smack.providers b/CopyOftrunk/build/resources/META-INF/smack.providers
new file mode 100644
index 000000000..040576ee8
--- /dev/null
+++ b/CopyOftrunk/build/resources/META-INF/smack.providers
@@ -0,0 +1,124 @@
+<?xml version="1.0"?> 
+<!-- Providers file for default Smack extensions -->
+<smackProviders> 
+    
+    <!-- Private Data Storage -->
+    <iqProvider> 
+        <elementName>query</elementName> 
+        <namespace>jabber:iq:private</namespace> 
+        <className>org.jivesoftware.smackx.PrivateDataManager$PrivateDataIQProvider</className> 
+    </iqProvider>
+    
+    <!-- Time -->
+    <iqProvider> 
+        <elementName>query</elementName> 
+        <namespace>jabber:iq:time</namespace> 
+        <className>org.jivesoftware.smackx.packet.Time</className> 
+    </iqProvider>
+    
+    <!-- Roster Exchange -->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>jabber:x:roster</namespace>
+        <className>org.jivesoftware.smackx.provider.RosterExchangeProvider</className>
+    </extensionProvider>
+    
+    <!-- Message Events -->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>jabber:x:event</namespace>
+        <className>org.jivesoftware.smackx.provider.MessageEventProvider</className>
+    </extensionProvider>
+    
+    <!-- XHTML -->
+    <extensionProvider>
+        <elementName>html</elementName>
+        <namespace>http://jabber.org/protocol/xhtml-im</namespace>
+        <className>org.jivesoftware.smackx.provider.XHTMLExtensionProvider</className>
+    </extensionProvider>
+
+    <!-- Group Chat Invitations -->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>jabber:x:conference</namespace>
+        <className>org.jivesoftware.smackx.GroupChatInvitation$Provider</className>
+    </extensionProvider>	
+
+    <!-- Service Discovery # Items -->
+    <iqProvider> 
+        <elementName>query</elementName> 
+        <namespace>http://jabber.org/protocol/disco#items</namespace> 
+        <className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className> 
+    </iqProvider>
+
+    <!-- Service Discovery # Info -->
+    <iqProvider>
+        <elementName>query</elementName>
+        <namespace>http://jabber.org/protocol/disco#info</namespace>
+        <className>org.jivesoftware.smackx.provider.DiscoverInfoProvider</className>
+    </iqProvider>
+
+    <!-- Data Forms-->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>jabber:x:data</namespace>
+        <className>org.jivesoftware.smackx.provider.DataFormProvider</className>
+    </extensionProvider>
+
+    <!-- MUC User -->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>http://jabber.org/protocol/muc#user</namespace>
+        <className>org.jivesoftware.smackx.provider.MUCUserProvider</className>
+    </extensionProvider>
+
+    <!-- MUC Admin -->
+    <iqProvider> 
+        <elementName>query</elementName> 
+        <namespace>http://jabber.org/protocol/muc#admin</namespace> 
+        <className>org.jivesoftware.smackx.provider.MUCAdminProvider</className> 
+    </iqProvider>
+
+    <!-- MUC Owner -->
+    <iqProvider> 
+        <elementName>query</elementName> 
+        <namespace>http://jabber.org/protocol/muc#owner</namespace> 
+        <className>org.jivesoftware.smackx.provider.MUCOwnerProvider</className> 
+    </iqProvider>
+
+    <!-- Delayed Delivery -->
+    <extensionProvider>
+        <elementName>x</elementName>
+        <namespace>jabber:x:delay</namespace>
+        <className>org.jivesoftware.smackx.provider.DelayInformationProvider</className>
+    </extensionProvider>
+    
+    <!-- Version -->
+    <iqProvider>
+        <elementName>query</elementName>
+        <namespace>jabber:iq:version</namespace>
+        <className>org.jivesoftware.smackx.packet.Version</className> 
+    </iqProvider>
+
+    <!-- VCard -->
+    <iqProvider>
+        <elementName>vCard</elementName>
+        <namespace>vcard-temp</namespace>
+        <className>org.jivesoftware.smackx.provider.VCardProvider</className> 
+    </iqProvider>
+
+    <!-- Offline Message Requests -->
+    <iqProvider>
+        <elementName>offline</elementName>
+        <namespace>http://jabber.org/protocol/offline</namespace>
+        <className>org.jivesoftware.smackx.packet.OfflineMessageRequest$Provider</className>
+    </iqProvider>
+
+    <!-- Offline Message Indicator -->
+    <extensionProvider>
+        <elementName>offline</elementName>
+        <namespace>http://jabber.org/protocol/offline</namespace>
+        <className>org.jivesoftware.smackx.packet.OfflineMessageInfo$Provider</className>
+    </extensionProvider>
+
+</smackProviders>
\ No newline at end of file
diff --git a/CopyOftrunk/build/resources/images/message.png b/CopyOftrunk/build/resources/images/message.png
new file mode 100644
index 000000000..b964bbfbc
Binary files /dev/null and b/CopyOftrunk/build/resources/images/message.png differ
diff --git a/CopyOftrunk/build/resources/images/nav_left_blue.png b/CopyOftrunk/build/resources/images/nav_left_blue.png
new file mode 100644
index 000000000..ed2bae72c
Binary files /dev/null and b/CopyOftrunk/build/resources/images/nav_left_blue.png differ
diff --git a/CopyOftrunk/build/resources/images/nav_right_red.png b/CopyOftrunk/build/resources/images/nav_right_red.png
new file mode 100644
index 000000000..c4fc5bda2
Binary files /dev/null and b/CopyOftrunk/build/resources/images/nav_right_red.png differ
diff --git a/CopyOftrunk/build/resources/images/photo_portrait.png b/CopyOftrunk/build/resources/images/photo_portrait.png
new file mode 100644
index 000000000..650b39d8c
Binary files /dev/null and b/CopyOftrunk/build/resources/images/photo_portrait.png differ
diff --git a/CopyOftrunk/build/resources/images/question_and_answer.png b/CopyOftrunk/build/resources/images/question_and_answer.png
new file mode 100644
index 000000000..0f9b23b2e
Binary files /dev/null and b/CopyOftrunk/build/resources/images/question_and_answer.png differ
diff --git a/CopyOftrunk/build/resources/images/trafficlight_green.png b/CopyOftrunk/build/resources/images/trafficlight_green.png
new file mode 100644
index 000000000..63bc3088a
Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_green.png differ
diff --git a/CopyOftrunk/build/resources/images/trafficlight_off.png b/CopyOftrunk/build/resources/images/trafficlight_off.png
new file mode 100644
index 000000000..d2684fdea
Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_off.png differ
diff --git a/CopyOftrunk/build/resources/images/trafficlight_red.png b/CopyOftrunk/build/resources/images/trafficlight_red.png
new file mode 100644
index 000000000..2325af01a
Binary files /dev/null and b/CopyOftrunk/build/resources/images/trafficlight_red.png differ
diff --git a/CopyOftrunk/build/resources/images/unknown.png b/CopyOftrunk/build/resources/images/unknown.png
new file mode 100644
index 000000000..1fa8a1eb4
Binary files /dev/null and b/CopyOftrunk/build/resources/images/unknown.png differ
diff --git a/CopyOftrunk/build/resources/images/warning.png b/CopyOftrunk/build/resources/images/warning.png
new file mode 100644
index 000000000..56ad042e5
Binary files /dev/null and b/CopyOftrunk/build/resources/images/warning.png differ
diff --git a/CopyOftrunk/build/resources/releasedocs/README.html b/CopyOftrunk/build/resources/releasedocs/README.html
new file mode 100644
index 000000000..9f4fac0d0
--- /dev/null
+++ b/CopyOftrunk/build/resources/releasedocs/README.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<head>
+	<title>Smack Readme</title>
+    <style type="text/css">
+        BODY {
+            font-size : 100%;
+        }
+        BODY, TD, TH {
+            font-family : tahoma, verdana, arial, helvetica, sans-serif;
+            font-size : 0.8em;
+        }
+        A:hover {
+            text-decoration : none;
+        }
+        .pageheader {
+            font-family : arial, helvetica, sans-serif;
+            font-size : 14pt;
+            font-weight: bold;
+        }
+        .header {
+            font-family : tahoma, arial, helvetica, sans-serif;
+            font-size : 1.4em;
+            font-weight: bold;
+            border-bottom : 1px #ccc solid;
+            padding-bottom : 2px;
+        }
+        .subheader {
+            font-weight: bold;
+            color: #600;
+        }
+        TT {
+            font-family : courier new;
+            font-weight : bold;
+            color : #060;
+        }
+        PRE {
+            font-family : courier new;
+            font-size : 100%;
+        }
+        .footer {
+            font-size : 0.8em;
+            color : #666;
+            text-align : center;
+        }
+    </style>
+</head>
+<body>
+
+<div class="header">
+Smack Readme
+</div>
+
+<p>
+<table boder=0>
+<tr>
+	<td>version:</td>
+	<td><b>1.5.1</b></td>
+</tr><tr>
+	<td>released:</td>
+	<td><b>August 12, 2005</b></td>
+</tr>
+</table>
+
+<p>
+Thank you for downloading Smack!
+<p>
+
+Start off by viewing the <a href="documentation/index.html">documentation</a>
+that can be found in the "documentation" directory included with this distribution.
+<p>
+Further information can be found on the <a href="http://www.jivesoftware.org/smack">
+Smack website</a>. If you need help using or would like to make contributions or
+fixes to the code, please visit the 
+<a href="http://www.jivesoftware.org/forums/forum.jspa?forumID=39">online forum</a>.
+ 
+<p><b>About the Distribution</b><p>
+
+The <tt>smack.jar</tt> file in the main distribution folder is the only binary file 
+required for embedding XMPP functionality into client applications. The optional 
+<tt>smackx.jar</tt> contains the <a href="documentation/extensions/index.html">Smack extensions</a> 
+while <tt>smackx-debug.jar</tt> contains an enhanced debugger.<p>
+
+If you downloaded the developer release, the full source of the library is included in 
+the <tt>source</tt> directory and can be compiled using the build scripts found in the 
+<tt>build</tt> directory (please see the README file in the build directory for further details).   
+ 
+<p><b>Changelog and Upgrading</b><p>
+
+View the <a href="changelog.html">changelog</a> for a list of changes since the 
+last release.
+
+<p><b>License Agreements</b><p>
+<ul>
+<li>Use of thie Smack source code is governed by the Apache License:
+<pre>
+ Copyright 2002-2005 Jive Software.
+
+ All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ </pre></li> 
+ 
+<li>Smack contains icons and images licensed from INCORS GmbH. You are not licensed
+to use these icons outside of Smack.</li>
+
+<li>Third-party source code is licensed as noted in their source files. 
+
+</ul> 
+</body>
+</html>
diff --git a/CopyOftrunk/build/resources/releasedocs/changelog.html b/CopyOftrunk/build/resources/releasedocs/changelog.html
new file mode 100644
index 000000000..6f1703d51
--- /dev/null
+++ b/CopyOftrunk/build/resources/releasedocs/changelog.html
@@ -0,0 +1,296 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+
+<html>
+<head>
+	<title>Smack Changelog</title>
+    <style type="text/css">
+        BODY {
+            font-size : 100%;
+        }
+        BODY, TD, TH {
+            font-family : tahoma, verdana, arial, helvetica, sans-serif;
+            font-size : 0.8em;
+        }
+        A:hover {
+            text-decoration : none;
+        }
+        .pageheader {
+            font-family : arial, helvetica, sans-serif;
+            font-size : 14pt;
+            font-weight: bold;
+        }
+        .header {
+            font-family : tahoma, arial, helvetica, sans-serif;
+            font-size : 1.4em;
+            font-weight: bold;
+            border-bottom : 1px #ccc solid;
+            padding-bottom : 2px;
+        }
+        .bugNum {
+            color: #666;
+        }
+        .subheader {
+            font-weight: bold;
+            color: #600;
+        }
+        TT {
+            font-family : courier new;
+            font-weight : bold;
+            color : #060;
+        }
+        PRE {
+            font-family : courier new;
+            font-size : 100%;
+        }
+        .footer {
+            font-size : 0.8em;
+            color : #666;
+            text-align : center;
+        }
+    </style>
+</head>
+<body>
+
+<div class="header">
+Smack Changelog
+</div>
+
+<p>
+<b>1.5.1</b> -- August 12, 2005
+<p>
+<ul>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-60'>SMACK-60</a>] - Presence priorities out of range were crashing the connection.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-63'>SMACK-63</a>] - Sometimes XMPPConnection#getRoster() was taking too long.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-66'>SMACK-66</a>] - Wrong attribute name and date format when requesting history since a given date.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-70'>SMACK-70</a>] - IQ Time now uses a 0-23 hour format.</li>
+
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-25'>SMACK-25</a>] - Added support for JEP-13: Flexible Offline Message Retrieval.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-58'>SMACK-58</a>] - Added support for JEP-54: vCards. Thanks to Kirill Maximov.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-53'>SMACK-53</a>] - Added support for JEP-92: Software Version.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-61'>SMACK-61</a>] - Added new debugger that prints on the stdout.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-71'>SMACK-71</a>] - Created new FromMatchesFilter that checks for exact matching.</li>
+
+
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-52'>SMACK-52</a>] - Added constructor to XMPPConnection for better connection control.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-56'>SMACK-56</a>] - Reported data can now hold more than one value.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-57'>SMACK-57</a>] - RoomInfo now includes the room JID.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-59'>SMACK-59</a>] - Date format for delayed dates is configurable.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-62'>SMACK-62</a>] - The username and password fields are now optional in Registration.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-67'>SMACK-67</a>] - Parsing of delayed dates was improved to be smarter.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-68'>SMACK-68</a>] - PacketParserUtils#parseProperties is now public.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-69'>SMACK-69</a>] - Adding or removing entries from a group can now throw an XMPPException.</li>
+</ul>
+
+<p>
+<b>1.5.0</b> -- March 30, 2005
+<p>
+<ul>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-7'>SMACK-7</a>] - Fixed issue that caused Smack to fail when X11 was not installed on Unix.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-32'>SMACK-32</a>] - Getting the system classloader could raise a security exception.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-33'>SMACK-33</a>] - MUCOwner.Item now includes the "role" attribute.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-37'>SMACK-37</a>] - Fixing timing issue that could make logins slow.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-42'>SMACK-42</a>] - The pretty print of the EnhancedDebugger was not working well with Java 1.5.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-43'>SMACK-43</a>] - Occupant#getNick() answers null when the info is available.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-46'>SMACK-46</a>] - Support for cancelling notifications in message events was missing.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-48'>SMACK-48</a>] - PacketListeners were not being removed from the connection when the chat finishes.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-50'>SMACK-50</a>] - XML representation of Presence packets did not include error element.</li>
+
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-4'>SMACK-4</a>] - Implemented room management in batch mode.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-11'>SMACK-11</a>] - Implemented discovery of extended information of MUC rooms.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-13'>SMACK-13</a>] - Implemented discovery of MUC rooms hosted by a service.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-38'>SMACK-38</a>] - Notify when an occupant joins or leaves a group chat room.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-39'>SMACK-39</a>] - Added support for discovering MUC services.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-41'>SMACK-41</a>] - Added support for JEP-91: Delayed Delivery.</li>
+
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-49'>SMACK-49</a>] - Modified Smack to use latest minimal version of XPP.</li>
+
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-40'>SMACK-40</a>] - Packet extensions can now be sent when inviting a user to a room.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-44'>SMACK-44</a>] - PacketReader can now parse errors that follow XMPP 1.0.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-45'>SMACK-45</a>] - Sorted the list of providers in the EnhancedDebugger window.</li>
+<li>[<a href='http://www.jivesoftware.org/issues/browse/SMACK-47'>SMACK-47</a>] - Added a public API for parsing Messages and Presences.</li>
+</ul>
+
+<p>
+<b>1.4.1</b> - November 15, 2004
+<p>
+<ul>
+    <li><font color="#777777">(SMACK-21)</font> The reader and listener threads in PacketReader are now stopped if an error occurs while starting up PacketReader. Thanks to Steve Reichert.
+    <li><font color="#777777">(SMACK-22)</font> XMPP addresses are now treated as case insensitive.
+    <li><font color="#777777">(SMACK-3)</font> Fixed possible NullPointerException when parsing invalid XHTML text.
+    <li><font color="#777777">(SMACK-8)</font> Answer an item-not-found error when Smack receives a service discovery for information and node is not null.
+    <li><font color="#777777">(SMACK-17)</font> An incorrect namespace was being used for granting and revoking admin/owner privileges.
+    <li><font color="#777777">(SMACK-19)</font> The roster should be able to hold any valid JID format. Thanks to Adam Olsen.
+    <li><font color="#777777">(SMACK-20</font> Parsing MUCOwner packets could freeze the client.
+    <li><font color="#777777">(SMACK-18)</font> Implemented JEP-128. A service discovery for information can now include a dataform or any possible packet extension.
+    <li><font color="#777777">(SMACK-15)</font> Allow registration entries with blank values.
+    <li><font color="#777777">(SMACK-16)</font> Added <tt>String getAccountAttribute(String)</tt> method to AccountManager class.
+    <li><font color="#777777">(SMACK-23)</font> Connection listeners can now be removed while notifying that the connection is being closed. Fixes ConcurrentModificationException triggered on close.
+</ul>
+
+<p>
+<b>1.4.0</b> - August 10, 2004
+<p>
+<ul>
+    <li><font color="#777777">(SMACK-99)</font> Added support for Multi User Chat (JEP 45) as a Smack Extension.
+    <li><font color="#777777">(SMACK-125)</font> Added support for Data Forms (JEP 04) as a Smack Extension.
+    <li><font color="#777777">(SMACK-143)</font> Fixed memory leak problem by closing ObjectOutputStream while writing out Object properties.
+    <li><font color="#777777">(SMACK-145)</font> Fixed memory leak problem by cancelling unused collectors.
+    <li><font color="#777777">(SMACK-138)</font> Fixed error parsing properties of Messages.
+    <li><font color="#777777">(SMACK-135)</font> Invalid characters in the TO and FROM fields of any packet were escaped in order to 
+	prevent the connection from closing. Thanks to Ian Sollars.
+    <li><font color="#777777">(SMACK-140)</font> Added new constructor to XMPPConnection <tt>XMPPConnection(String host, int port, 
+	SocketFactory socketFactory)</tt> that allows a user to pass in the SocketFactory to use.
+    <li><font color="#777777">(SMACK-131)</font> Remove cached presence info when user was deleted from roster.
+    <li><font color="#777777">(SMACK-123)</font> TimerTask was removed to make Smack JDK 1.2 compatible again.
+    <li><font color="#777777">(SMACK-130)</font> Use notifyAll() instead of notify() in PacketWriter.
+    <li><font color="#777777">(SMACK-137)</font> Fixed security exception with unsigned applets using try/catch around System.getProperty.
+    <li><font color="#777777">(SMACK-127)</font> IQ packets can now have extensions.
+    <li><font color="#777777">(SMACK-128)</font> Registration can now include a registration data form.
+    <li><font color="#777777">(SMACK-136)</font> Keep-alive process should flush stream.
+    <li><font color="#777777">(SMACK-121)</font> Delay of keep-alive is now configurable.
+    <li><font color="#777777">(SMACK-149)</font> A disco info request directed to a Smack client didn't answer the client's identity.
+    <li><font color="#777777">(SMACK-150)</font> Added <tt>canPublishItems(String entityID)</tt> method to ServiceDiscoveryManager in 
+	order to discover whether a server supports publishing of items or not.
+    <li><font color="#777777">(SMACK-133)</font> Node attribute was missing in DiscoverInfo and DiscoverItems XML representations.
+    <li><font color="#777777">(SMACK-134)</font> Added <tt>setNodeInformationProvider(String node, NodeInformationProvider listener)</tt> 
+	method to ServiceDiscoveryManager and created new <tt>NodeInformationProvider</tt> interface in order to provide information about 
+	nodes defined in the client.
+    <li><font color="#777777">(SMACK-139)</font> Added new menu option to the enhanced debugger in order to close all the tabs of which 
+	their connections are not active anymore.
+    <li><font color="#777777">(SMACK-124)</font> Don't set L&F in debuggers.
+    <li><font color="#777777">(SMACK-122)</font> Added documentation about the new enhanced debugger.
+    <li><font color="#777777">(SMACK-142)</font> Base class for existing test cases was created.
+</ul>
+
+<p>
+<b>1.3.0</b> - March 11, 2004
+<p>
+<ul>
+    <li><font color="#777777">(SMACK-103, SMACK-105)</font> Fixed bugs with error packets (sending and receiving).    	   	
+    <li><font color="#777777">(SMACK-109)</font> Renaming RosterGroups fails. 		
+   	<li><font color="#777777">(SMACK-91)</font> Add support for Service Discovery (JEP 30) as a Smack Extension. 		
+   	<li><font color="#777777">(SMACK-94)</font> Host name as reported by server should be used in the Connection object. 		
+   	<li><font color="#777777">(SMACK-97)</font> SUBSCRIPTION_* constants are misspelled in the Roster class.	
+   	<li><font color="#777777">(SMACK-107)</font> Allow packet reply timeout to be set. 				
+   	<li><font color="#777777">(SMACK-41)</font> Unrecognized IQs should generate a "not implemented" error. 		
+   	<li><font color="#777777">(SMACK-116)</font> Roster entries are not being removed from the group immediately when deleted. 		
+   	<li><font color="#777777">(SMACK-100)</font> Incoming packets should only have ID's if they are set. 			
+   	<li><font color="#777777">(SMACK-104)</font> Fixed bug parsing server information. 		
+   	<li><font color="#777777">(SMACK-112)</font> Add a mechanism to set the roster's subscription mode before login. 		
+   	<li><font color="#777777">(SMACK-117)</font> PacketWriter never terminates daemon threads. 		
+   	<li><font color="#777777">(SMACK-113)</font> Once a debugger gets closed it still collects packets - OutOfMemory problem. 		
+   	<li><font color="#777777">(SMACK-102)</font> Add methods to get all packet providers. 		
+   	<li><font color="#777777">(SMACK-95)</font> Add group chat invitation support as a Smack Extension. 		
+   	<li><font color="#777777">(SMACK-93)</font> New debug window with many enhancements. 		
+   	<li><font color="#777777">(SMACK-110)</font> Added keep-alives so the TCP-IP timeouts wouldn't break connections to a server. 		
+   	<li><font color="#777777">(SMACK-101)</font> Add version number information to API. 		
+   	<li><font color="#777777">(SMACK-96)</font> Make AndFilter and OrFilter chainable. 		
+   	<li><font color="#777777">(SMACK-108)</font> Handle multiple presences when a user is connected from different resources. 		
+   	<li><font color="#777777">(SMACK-111)</font> Add listener support for new connections. 		
+   	<li><font color="#777777">(SMACK-92)</font> Add support for "Discovering Support for XHTML-IM". 		
+   	<li><font color="#777777">(SMACK-106)</font> Chat objects no longer have to depend on a threadID (this is settable).
+    <li><font color="#777777">(SMACK-120)</font> Chat.getChatID() is now Chat.getThreadID().
+</ul>
+
+<p>
+<b>1.2.1</b> - September 28, 2003
+<p>
+<ul>
+    <li><font color="#777777">(SMACK-79)</font> Added XHTML message support as a Smack extension, which allows sending
+        richly formatted messages.
+    <li><font color="#777777">(SMACK-88)</font> Fixed bug with parsing registation packets that contain extra data.
+    <li><font color="#777777">(SMACK-90)</font> Added support for getting registration instructions.
+    <li><font color="#777777">(SMACK-85)</font> Exceptions in the PacketWriter now correctly generates a connection
+        error event.
+    <li><font color="#777777">(SMACK-84)</font> Added <tt>isSecureConnection()</tt> method to XMPPConnection class.
+    <li><font color="#777777">(SMACK-86)</font> Added <tt>isJoined()</tt> method to GroupChat class. 
+    <li><font color="#777777">(SMACK-87, SMACK-82)</font> Added the following methods related to rosters:
+        <tt>Roster.contains(String user)</tt>, <tt>Roster.getEntry(String user)</tt>, 
+        <tt>RosterGroup.getEntry(String user)</tt>, <tt>Roster.removeEntry(RosterEntry entry)</tt>.
+    <li><font color="#777777">(SMACK-73)</font> Fixed bugs handling roster remove and update operations.    
+</ul>
+
+<p>
+<b>1.2.0</b> - August 29, 2003
+<p>
+<ul>
+    <li><font color="red"><b>!</b></font> A package structure and documentation has been added for Smack extensions,
+        which cover extensions to the XMPP protocol. The initial extensions are
+        for message events (JEP 22), roster item exchange (JEP 93), entity
+        time (JEP 90), and private data storage (JEP 49). 
+    <li><font color="red"><b>!</b></font> The smack.providers file is now loaded from META-INF/smack.providers
+        rather than WEB-INF/smack.providers. This location makes much more sense
+        for generic JAR files, but may break existing provider implementations
+        until the provider file is moved.
+    <li>Fixed IQ error sub-packets.
+    <li>The default packet extension handler didn't deal with empty 
+        elements well and also had a bug with attribute handling.
+    <li>Added a ConnectionListener feature which allows clients
+        to be notified of normally closed connections, and connections
+        closed due to errors.
+    <li>Fixed bug where the roster list could become corrupted after 
+        moving a user back and forther between groups.
+    <li>Fixed bug where in some cases presence packets were not getting
+        tracked by the Roster class correctly.
+    <li>RosterListener has a new notification method that is called every time
+        the presence of a user in the roster is updated.
+    <li>Added Roster.getEntries() method to return all entries in the roster.
+    <li>Added RosterGroup.contains(String) method to check to see if an XMPP
+        address is part of the group.
+    <li>Minor fixes to Javadocs.
+    <li>Content can be copied and cleared from the debug window using
+        a pop-up menu.
+    <li>The Chat constructor that took an existing chatID as an argument
+        did not propertly initialize support for message listeners. 
+    <li>Added support for anonymous logins.
+    <li>IQ is now an abstract class.
+    <li>Fixed bug where XHTML messgaes could cause parsing errors. 
+</ul>
+
+<p>
+<b>1.1.1</b> - June 25, 2003
+<p>
+<ul>
+    <li>Setting Object packet properties was broken.
+    <li>Added getRoom() method to GroupChat.
+</ul>
+
+<p>
+<b>1.1.0</b> - June 19, 2003
+<p>
+<ul>
+    <li>New system to handle custom IQ packets and custom packet extensions through
+	    the new provider sub-package.
+	<li>Added packet filters for packet extensions.
+    <li>Added additional options for responding to subscription requests.
+    <li>Added method to retrieve the roster item count from roster packets.
+	<li>Added ability to set the ItemStatus on a roster packet.
+	<li>Added remove option to roster packet.
+	<li>Various documentation fixes/improvements.
+	<li>Fixed NullPointer exception on the setName method of the RosterEntry class.
+	<li>Groupchat class was listening for wrong message types -- fixed.
+    <li>Changed properties element name to "properties" instead of "x". <b>Note:</b> this will
+	    break compatability between earlier versions if they are trying to send packet
+		properties back and forth. However, we thought it was best to make this change now.
+    <li>Turning on debugging via a system property wasn't working.
+	<li>Fixed spelling error in Roster class method name.
+	<li>Fixed stream not being closed properly.
+	<li>The "to contains" and "from contains" filters now ignore case.
+</ul>
+
+<p>
+<b>1.0.1</b> - April 30, 2003
+<p>
+<ul>
+    <li>Fixed bug that caused applets using Smack to crash with a security exception.
+</ul>
+
+<p>
+<b>1.0.0</b> - April 25, 2003
+<p>
+<ul>
+    <li>Initial official release.
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/debugging.html b/CopyOftrunk/documentation/debugging.html
new file mode 100644
index 000000000..b5e6e4bbe
--- /dev/null
+++ b/CopyOftrunk/documentation/debugging.html
@@ -0,0 +1,116 @@
+<html>
+<head>
+	<title>Smack: Debugging - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Debugging with Smack
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+Smack includes two built-in debugging consoles that will let you track all XML traffic between 
+the client and server. A <a href="#lite">lite debugger</a> which is part of the <tt>smack.jar</tt> 
+and an <a href="#enhanced">enhanced debugger</a> contained in <tt>smackx-debug.jar</tt>.
+</p>
+
+<p>
+Debugging mode can be enabled in two different ways:
+</p>
+
+<ol>
+	<li>Add the following line of code <b>before</b> creating new connections:<p> 
+		<tt>XMPPConnection.DEBUG_ENABLED = true;</tt><p>
+
+	<li>Set the Java system property <tt>smack.debugEnabled</tt> to true. The 
+		system property can be set on the command line such as:<p> 
+		<tt>java -Dsmack.debugEnabled=true SomeApp </tt>
+</ol>
+
+<p>
+If you wish to explicitly disable debug mode in your application, including using the command-line parameter, 
+add the following line to your application before opening new connections:
+</p>
+
+<p>
+<tt>XMPPConnection.DEBUG_ENABLED = false;</tt>
+</p>
+
+<p>
+Smack uses the following logic to decide the debugger console to use:
+</p>
+
+<ol>
+	<li>It will first try use the debugger class specified in the Java system property 
+	<tt>smack.debuggerClass</tt>. If you need to develop your own debugger, 
+	implement the <tt>SmackDebugger</tt> interface and then set the system property 
+	on the command line such as:<p> 
+		<tt>java -Dsmack.debuggerClass=my.company.com.MyDebugger SomeApp </tt><p>
+
+	<li>If step 1 fails then Smack will try to use the enhanced debugger. The
+	file <tt>smackx-debug.jar</tt> contains the enhanced debugger. Therefore you will need
+	to place the jar file in the classpath. For situations where space is an issue you
+	may want to only deploy <tt>smack.jar</tt> in which case the enhanced debugger won't be
+	available.<p> 
+
+	<li>The last option if the previous two steps fail is to use the lite debugger. The lite 
+	debugger is a very good option for situations where you need to have low memory footprint.
+</ol>
+
+<p class="subheader">
+<a name="enhanced">Enhanced Debugger</a>
+</p>
+
+<img src="images/enhanceddebugger.png" width="479" height="400" alt="Full Debug Window" border="0" align="right">
+
+When debugging mode is enabled, a debug window will appear containing tabs for each new created connection.
+The window will contain the following information:
+
+<ul>
+    <li>Connection tabs -- each tab shows debugging information related to the connection.
+	<li>Smack info tab -- shows information about Smack (e.g. Smack version, installed components, etc.).
+</ul>
+
+The connection tab will contain the following information:
+<ul>
+    <li>All Packets -- shows sent and received packets information parsed by Smack.
+	<li>Raw Sent Packets -- raw XML traffic generated by Smack and sent to the server.
+    <li>Raw Received Packets -- raw XML traffic sent by the server to the client.
+    <li>Ad-hoc message -- allows to send ad-hoc packets of any type.
+    <li>Information -- shows connection state and statistics.
+</ul>
+
+<br clear="right">
+
+<p class="subheader">
+<a name="lite">Lite Debugger</a>
+</p>
+
+<img src="images/debugwindow.gif" width="359" height="399" alt="Lite Debug Window" border="0" align="right">
+
+When debugging mode is enabled, a debug window will appear when each new connection is created.
+The window will contain the following information:
+
+<ul>
+	<li>Client Traffic (red text) -- raw XML traffic generated by Smack and sent to the server.
+    <li>Server Traffic (blue text) -- raw XML traffic sent by the server to the client.
+    <li>Interpreted Packets (green text) -- shows XML packets from the server as parsed by Smack.
+</ul>
+
+Right click on any of the panes to bring up a menu with the choices to copy of the contents 
+to the system clipboard or to clear the contents of the pane.
+
+<br clear="all" /><br><br>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/extensions/dataforms.html b/CopyOftrunk/documentation/extensions/dataforms.html
new file mode 100644
index 000000000..1c2aee457
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/dataforms.html
@@ -0,0 +1,137 @@
+<html>
+<head>
+<title>Data Forms</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Data Forms</div><p> 
+
+Allows to exchange structured data between users and applications for common 
+tasks such as registration and searching using Forms. 
+
+<ul>
+  <li><a href="#gather">Create a Form to fill out</a></li>
+  <li><a href="#fillout">Answer a Form</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0004.html">JEP-4</a>
+
+<hr>
+
+<div class="subheader"><a name="gather">Create a Form to fill out</a></div><p>
+
+<b>Description</b><p>
+
+An XMPP entity may need to gather data from another XMPP entity. Therefore, the data-gathering 
+entity will need to create a new Form, specify the fields that will conform the Form and finally 
+send the Form to the data-providing entity.</p>
+
+<b>Usage</b><p>
+
+In order to create a Form to fill out use the <i><b>Form</b></i>'s constructor passing the constant 
+<b>Form.TYPE_FORM</b> as the parameter. The next step is to create the form fields and add them to 
+the form. In order to create and customize a <i><b>FormField</b></i> use the <i><b>FormField</b></i>'s 
+constructor specifying the variable name of the field as the parameter. Then use <b>setType(String type)</b> 
+to set the field's type (e.g. FormField.TYPE_HIDDEN, FormField.TYPE_TEXT_SINGLE). Once we have the 
+<i><b>Form</b></i> instance and the <i><b>FormFields</b></i> the last step is to send <b>addField(FormField field)</b> 
+for each field that we want to add to the form.</p><p>
+
+Once the form to fill out is finished we will want to send it in a message. Send <b>getDataFormToSend()</b> to 
+the form and add the answer as an extension to the message to send.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to create and send a form to fill out: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a new form to gather data</font>
+        Form formToSend = new Form(Form.TYPE_FORM);
+        formToSend.setInstructions(
+            "Fill out this form to report your case.\nThe case will be created automatically.");
+        formToSend.setTitle("Case configurations");
+        <font color="#3f7f5f">// Add a hidden variable to the form</font>
+        FormField field = new FormField("hidden_var");
+        field.setType(FormField.TYPE_HIDDEN);
+        field.addValue("Some value for the hidden variable");
+        formToSend.addField(field);
+        <font color="#3f7f5f">// Add a fixed variable to the form</font>
+        field = new FormField();
+        field.addValue("Section 1: Case description");
+        formToSend.addField(field);
+        <font color="#3f7f5f">// Add a text-single variable to the form</font>
+        field = new FormField("name");
+        field.setLabel("Enter a name for the case");
+        field.setType(FormField.TYPE_TEXT_SINGLE);
+        formToSend.addField(field);
+        <font color="#3f7f5f">// Add a text-multi variable to the form</font>
+        field = new FormField("description");
+        field.setLabel("Enter a description");
+        field.setType(FormField.TYPE_TEXT_MULTI);
+        formToSend.addField(field);
+
+        <font color="#3f7f5f">// Create a chat with "user2@host.com"</font>
+        Chat chat = conn1.createChat("user2@host.com" );
+
+        Message msg = chat.createMessage();
+        msg.setBody("To enter a case please fill out this form and send it back to me");
+        <font color="#3f7f5f">// Add the form to fill out to the message to send</font>
+        msg.addExtension(formToSend.getDataFormToSend());
+
+        <font color="#3f7f5f">// Send the message with the form to fill out</font>
+        chat.sendMessage(msg);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="fillout">Answer a Form</a></div><p>
+
+<b>Description</b><p>
+
+Under many situations an XMPP entity could receive a form to fill out. For example, some hosts
+may require to fill out a form in order to register new users. Smack lets the data-providing entity 
+to complete the form in an easy way and send it back to the data-gathering entity.</p>
+
+<b>Usage</b><p>
+
+The form to fill out contains useful information that could be used for rendering the form. But it 
+cannot be used to actually complete it. Instead it's necessary to create a new form based on the original 
+form whose purpose is to hold all the answers.</p><p>
+
+In order to create a new <i><b>Form</b></i> to complete based on the original <i><b>Form</b></i> just send 
+<b>createAnswerForm()</b> to the original <i><b>Form</b></i>. Once you have a valid form that could be actually
+completed all you have to do is send <b>setAnswer(String variable, String value)</b> to the form where variable
+is the variable of the <i><b>FormField</b></i> that you want to answer and value is the String representation 
+of the answer. If the answer consist of several values you could then use <b>setAnswer(String variable, List values)</b> 
+where values is a List of Strings.</p><p>
+
+Once the form has been completed we will want to send it back in a message. Send <b>getDataFormToSend()</b> to 
+the form and add the answer as an extension to the message to send back.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to retrieve a form to fill out, complete the form and send it back: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Get the message with the form to fill out</font>
+        Message msg2 = chat2.nextMessage();
+        <font color="#3f7f5f">// Retrieve the form to fill out from the message</font>
+        Form formToRespond = Form.getFormFrom(msg2);
+        <font color="#3f7f5f">// Obtain the form to send with the replies</font>
+        Form completedForm = formToRespond.createAnswerForm();
+        <font color="#3f7f5f">// Add the answers to the form</font>
+        completedForm.setAnswer("name", "Credit card number invalid");
+        completedForm.setAnswer(
+            "description",
+            "The ATM says that my credit card number is invalid. What's going on?");
+			
+        msg2 = chat2.createMessage();
+        msg2.setBody("To enter a case please fill out this form and send it back to me");
+        <font color="#3f7f5f">// Add the completed form to the message to send back</font>
+        msg2.addExtension(completedForm.getDataFormToSend());
+        <font color="#3f7f5f">// Send the message with the completed form</font>
+        chat2.sendMessage(msg2);
+</pre>
+</blockquote>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/disco.html b/CopyOftrunk/documentation/extensions/disco.html
new file mode 100644
index 000000000..6f8058dd9
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/disco.html
@@ -0,0 +1,236 @@
+<html>
+<head>
+<title>Service Discovery</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Service Discovery</div><p>
+
+The service discovery extension allows to discover items and information about XMPP
+entities. Follow these links to learn how to use this extension.
+
+<ul>
+  <li><a href="#discoregister">Manage XMPP entity features</a></li>
+  <li><a href="#disconodeinfo">Provide node information</a></li>
+  <li><a href="#discoitems">Discover items associated with an XMPP entity</a></li>
+  <li><a href="#discoinfo">Discover information about an XMPP entity</a></li>
+  <li><a href="#discopublish">Publish publicly available items</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0030.html">JEP-30</a>
+
+<hr>
+
+<div class="subheader"><a name="discoregister">Manage XMPP entity features</a></div><p>
+
+<b>Description</b><p>
+
+Any XMPP entity may receive a discovery request and must answer with its associated items or 
+information. Therefore, your Smack client may receive a discovery request that must respond
+to (i.e., if your client supports XHTML-IM). This extension automatically responds to a 
+discovery request with the information that you previously configured.</p>
+
+<b>Usage</b><p>
+
+In order to configure the supported features by your client you should first obtain the 
+ServiceDiscoveryManager associated with your XMPPConnection. To get your ServiceDiscoveryManager
+send <b>getInstanceFor(connection)</b> to the class <i><b>ServiceDiscoveryManager</b></i> where
+connection is your XMPPConnection.<br></p>
+
+<p>Once you have your ServiceDiscoveryManager you will be able to manage the supported features. To
+register a new feature send <b>addFeature(feature)</b> to your <i><b>ServiceDiscoveryManager</b></i>
+where feature is a String that represents the supported feature. To remove a supported feature send
+<b>removeFeature(feature)</b> to your <i><b>ServiceDiscoveryManager</b></i> where feature is a 
+String that represents the feature to remove.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to add and remove supported features: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font>
+      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+      
+      <font color="#3f7f5f">// Register that a new feature is supported by this XMPP entity</font>
+      discoManager.addFeature(namespace1);
+
+      <font color="#3f7f5f">// Remove the specified feature from the supported features by this XMPP entity</font>
+      discoManager.removeFeature(namespace2);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="disconodeinfo">Provide node information</a></div><p>
+
+<b>Description</b><p>
+
+Your XMPP entity may receive a discovery request for items non-addressable as a JID such as
+the MUC rooms where you are joined. In order to answer the correct information it is necessary 
+to configure the information providers associated to the items/nodes within the Smack client.</p>
+
+<b>Usage</b><p>
+
+In order to configure the associated nodes within the Smack client you will need to create a
+NodeInformationProvider and register it with the <i><b>ServiceDiscoveryManager</b></i>. To get 
+your ServiceDiscoveryManager send <b>getInstanceFor(connection)</b> to the class <i><b>ServiceDiscoveryManager</b></i> 
+where connection is your XMPPConnection.<br></p>
+
+<p>Once you have your ServiceDiscoveryManager you will be able to register information providers 
+for the XMPP entity's nodes. To register a new node information provider send <b>setNodeInformationProvider(String node, NodeInformationProvider listener)</b> 
+to your <i><b>ServiceDiscoveryManager</b></i> where node is the item non-addressable as a JID and 
+listener is the <i><b>NodeInformationProvider</b></i> to register. To unregister a <i><b>NodeInformationProvider</b></i> 
+send <b>removeNodeInformationProvider(String node)</b> to your <i><b>ServiceDiscoveryManager</b></i> where 
+node is the item non-addressable as a JID whose information provider we want to unregister.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to register a NodeInformationProvider with a ServiceDiscoveryManager that will provide
+information concerning a node named "http://jabber.org/protocol/muc#rooms": <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Set the NodeInformationProvider that will provide information about the</font>
+      <font color="#3f7f5f">// joined rooms whenever a disco request is received </font>
+      ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(
+          <font color="#0000FF">"http://jabber.org/protocol/muc#rooms"</font>,
+          new NodeInformationProvider() {
+              public Iterator getNodeItems() {
+                  ArrayList answer = new ArrayList();
+                  Iterator rooms = MultiUserChat.getJoinedRooms(connection);
+                  while (rooms.hasNext()) {
+                      answer.add(new DiscoverItems.Item((String)rooms.next()));
+                  }
+                  return answer.iterator(); 
+              }
+          });
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discoitems">Discover items associated with an XMPP entity</a></div><p>
+
+<b>Description</b><p>
+
+In order to obtain information about a specific item you have to first discover the items available
+in an XMPP entity.</p>
+
+<b>Usage</b><p>
+
+<p>Once you have your ServiceDiscoveryManager you will be able to discover items associated with 
+an XMPP entity. To discover the items of a given XMPP entity send <b>discoverItems(entityID)</b> 
+to your <i><b>ServiceDiscoveryManager</b></i> where entityID is the ID of the entity. The message 
+<b>discoverItems(entityID)</b> will answer an instance of <i><b>DiscoverItems</b></i> that contains 
+the discovered items.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to discover the items associated with an online catalog service: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font>
+      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+      
+      <font color="#3f7f5f">// Get the items of a given XMPP entity</font>
+      <font color="#3f7f5f">// This example gets the items associated with online catalog service</font>
+      DiscoverItems discoItems = discoManager.discoverItems("plays.shakespeare.lit");
+
+      <font color="#3f7f5f">// Get the discovered items of the queried XMPP entity</font>
+      Iterator it = discoItems.getItems();
+      <font color="#3f7f5f">// Display the items of the remote XMPP entity</font>
+      while (it.hasNext()) {
+          DiscoverItems.Item item = (DiscoverItems.Item) it.next();
+          System.out.println(item.getEntityID());
+          System.out.println(item.getNode());
+          System.out.println(item.getName());
+      }
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discoinfo">Discover information about an XMPP entity</a></div><p>
+
+<b>Description</b><p>
+
+Once you have discovered the entity ID and name of an item, you may want to find out more 
+about the item. The information desired generally is of two kinds: 1) The item's identity 
+and 2) The features offered by the item.</p>
+
+<p>This information helps you determine what actions are possible with regard to this 
+item (registration, search, join, etc.) as well as specific feature types of interest, if 
+any (e.g., for the purpose of feature negotiation).</p>
+
+<b>Usage</b><p>
+
+<p>Once you have your ServiceDiscoveryManager you will be able to discover information associated with 
+an XMPP entity. To discover the information of a given XMPP entity send <b>discoverInfo(entityID)</b> 
+to your <i><b>ServiceDiscoveryManager</b></i> where entityID is the ID of the entity. The message 
+<b>discoverInfo(entityID)</b> will answer an instance of <i><b>DiscoverInfo</b></i> that contains 
+the discovered information.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to discover the information of a conference room: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font>
+      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+      
+      <font color="#3f7f5f">// Get the information of a given XMPP entity</font>
+      <font color="#3f7f5f">// This example gets the information of a conference room</font>
+      DiscoverInfo discoInfo = discoManager.discoverInfo("balconyscene@plays.shakespeare.lit");
+
+      <font color="#3f7f5f">// Get the discovered identities of the remote XMPP entity</font>
+      Iterator it = discoInfo.getIdentities();
+      <font color="#3f7f5f">// Display the identities of the remote XMPP entity</font>
+      while (it.hasNext()) {
+          DiscoverInfo.Identity identity = (DiscoverInfo.Identity) it.next();
+          System.out.println(identity.getName());
+          System.out.println(identity.getType());
+          System.out.println(identity.getCategory());
+      }
+
+      <font color="#3f7f5f">// Check if room is password protected</font>
+      discoInfo.containsFeature("muc_passwordprotected");
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discopublish">Publish publicly available items</a></div><p>
+
+<b>Description</b><p>
+
+Publish your entity items to some kind of persistent storage. This enables other entities to query 
+that entity using the disco#items namespace and receive a result even when the entity being queried 
+is not online (or available).</p>
+
+<b>Usage</b><p>
+
+<p>Once you have your ServiceDiscoveryManager you will be able to publish items to some kind of 
+persistent storage. To publish the items of a given XMPP entity you have to first create an instance
+of <i><b>DiscoverItems</b></i> and configure it with the items to publish. Then you will have to 
+send <b>publishItems(String entityID, DiscoverItems discoverItems)</b> to your <i><b>ServiceDiscoveryManager</b></i> 
+where entityID is the address of the XMPP entity that will persist the items and discoverItems contains the items
+to publish.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to publish new items: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Obtain the ServiceDiscoveryManager associated with my XMPPConnection</font>
+      ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+      
+      <font color="#3f7f5f">// Create a DiscoverItems with the items to publish</font>
+      DiscoverItems itemsToPublish = new DiscoverItems();
+      DiscoverItems.Item itemToPublish = new DiscoverItems.Item("pubsub.shakespeare.lit");
+      itemToPublish.setName("Avatar");
+      itemToPublish.setNode("romeo/avatar");
+      itemToPublish.setAction(DiscoverItems.Item.UPDATE_ACTION);
+      itemsToPublish.addItem(itemToPublish);
+
+      <font color="#3f7f5f">// Publish the new items by sending them to the server</font>
+      discoManager.publishItems("host", itemsToPublish);
+</pre>
+</blockquote>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/index.html b/CopyOftrunk/documentation/extensions/index.html
new file mode 100644
index 000000000..e22836b9c
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/index.html
@@ -0,0 +1,15 @@
+<html>
+
+<head>
+<title>Smack Extensions User Manual</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<frameset cols="200,*">
+<frame src="toc.html" name="navFrame" target="mainFrame">
+<frame src="intro.html" name="mainFrame">
+</frameset>
+<noframes>
+<H2>Smack Extensions User Manual</H2>
+
+<a href="toc.html">Smack Extensions User Manual</a></noframes></html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/intro.html b/CopyOftrunk/documentation/extensions/intro.html
new file mode 100644
index 000000000..2005966e1
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/intro.html
@@ -0,0 +1,70 @@
+<html>
+<head>
+<title>Smack Extensions User Manual</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+  	<div class="header">Smack Extensions Manual</div>
+  	<p>The XMPP protocol includes a base protocol and many optional extensions 
+    typically documented as "JEP's". Smack provides the	org.jivesoftware.smack 
+    package for the core XMPP protocol, and  the org.jivesoftware.smackx package for
+  	many of the protocol extensions.</p>
+  
+ 	<p>This manual provides details about each of the "smackx" extensions, including what
+  	it is, how to use it, and some simple example code.<p> 
+
+    <div class="subheader">Current Extensions</div><p>
+  
+    <table border="0" width="85%" cellspacing="0" cellpadding="3" style="border:1px #bbb solid;">
+        <tr bgcolor="#ddeeff">
+            <td><b>Name</b></td><td><b>JEP #</b></td><td><b>Description</b></td>
+        </tr>
+        <tr>
+            <td><a href="privatedata.html">Private Data</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a></td>
+            <td>Manages private data.</td>
+        </tr>
+        <tr>
+            <td><a href="xhtml.html">XHTML Messages</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a></td>
+            <td>Allows send and receiving formatted messages using XHTML.</td>
+        </tr>
+        <tr>
+            <td><a href="messageevents.html">Message Events</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0022.html">JEP-22</a></td>
+            <td>Requests and responds to message events.</td>
+        </tr>
+        <tr>
+            <td><a href="dataforms.html">Data Forms</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0004.html">JEP-4</a></td>
+            <td>Allows to gather data using Forms.</td>
+        </tr>
+        <tr>
+            <td><a href="muc.html">Multi User Chat</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a></td>
+            <td>Allows configuration of, participation in, and administration of individual text-based conference rooms.</td>
+        </tr>
+        <tr>
+            <td><a href="rosterexchange.html">Roster Item Exchange</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0093.html">JEP-93</a></td>
+            <td>Allows roster data to be shared between users.</td>
+        </tr>
+        <tr>
+            <td><a href="time.html">Time Exchange</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a></td>
+            <td>Allows local time information to be shared between users.</td>
+        </tr> 
+        <tr>
+            <td><a href="invitation.html">Group Chat Invitations</a></td>
+            <td>N/A</td>
+            <td>Send invitations to other users to join a group chat room.</td>
+        </tr>
+        <tr>
+            <td><a href="disco.html">Service Discovery</a></td>
+            <td><a href="http://www.jabber.org/jeps/jep-0030.html">JEP-30</a></td>
+            <td>Allows to discover services in XMPP entities.</td>
+        </tr> 
+    </table>
+</body>
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/invitation.html b/CopyOftrunk/documentation/extensions/invitation.html
new file mode 100644
index 000000000..c15b15824
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/invitation.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<title>Group Chat Invitations</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Group Chat Invitations</div><p>
+
+The group chat invitation packet extension is used to invite other
+users to a group chat room. 
+
+<ul>
+  <li><a href="#send">Inviting Other Users</a></li>
+  <li><a href="#listen">Listen for Invitations</a></li>
+</ul>
+
+<p>
+<b>JEP related:</b> N/A -- this protocol is outdated now that the Multi-User Chat (MUC) JEP is available
+(<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most
+existing clients still use this older protocol. Once MUC support becomes more
+widespread, this API may be deprecated. 
+
+<hr>
+
+<p><div class="subheader"><a name="send">Inviting Other Users</a></div><p>
+
+To use the GroupChatInvitation packet extension
+to invite another user to a group chat room, address a new message to the 
+user and set the room name appropriately, as in the following code example:
+
+<pre>
+Message message = new Message(<font color="#0000FF">"user@chat.example.com"</font>);
+message.setBody(<font color="#0000FF">"Join me for a group chat!"</font>);
+message.addExtension(new GroupChatInvitation(<font color="#0000FF">"room@chat.example.com"</font>));
+con.sendPacket(message);
+</pre>
+
+The XML generated for the invitation portion of the code above would be:
+
+<pre>
+&lt;x xmlns="jabber:x:conference" jid="room@chat.example.com"/&gt;
+</pre><p>
+
+<hr>
+<div class="subheader"><a name="listen">Listening for Invitations</a></div><p>
+
+To listen for group chat invitations, use a PacketExtensionFilter for the
+<tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the
+following code example:
+
+<pre>
+PacketFilter filter = new PacketExtensionFilter(<font color="#0000FF">"x"</font>, <font color="#0000FF">"jabber:x:conference"</font>);
+<font color="#3f7f5f">// Create a packet collector or packet listeners using the filter...</font>
+</pre>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/messageevents.html b/CopyOftrunk/documentation/extensions/messageevents.html
new file mode 100644
index 000000000..d91492b3f
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/messageevents.html
@@ -0,0 +1,244 @@
+<html>
+<head>
+    <title>Message Events</title>
+    <link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Message Events</div><p>
+
+This extension is used to request and respond to events relating to the delivery, 
+display, and composition of messages. There are three stages in this extension:<ol>
+    <li>Request for event notifications,
+    <li>Receive the event notification requests and send event notifications, and
+    <li>Receive the event notifications.</ol> 
+<p>For more information on each stage please follow these links:</p>
+<ul>
+  <li><a href="#reqevnot">Requesting Event Notifications</a></li>
+  <li><a href="#lstevnotreq">Reacting to Event Notification Requests</a></li>
+  <li><a href="#lstevnot">Reacting to Event Notifications</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0022.html">JEP-22</a>
+<hr>
+<div class="subheader"><a name="reqevnot">Requesting Event Notifications</a></div><p>
+<b>Description</b><p>
+
+In order to receive event notifications for a given message you first have to specify 
+which events are you interested in. Each message that you send has to request its own event 
+notifications. Therefore, every message that you send as part of a chat should request its own event 
+notifications.</p>
+
+<b>Usage</b><p>
+
+The class <i>MessageEventManager</i> provides an easy way for requesting event notifications. All you have to do is specify
+the message that requires the event notifications and the events that you are interested in.
+<p>Use the static method <i><b>MessageEventManager.addNotificationsRequests(Message message, boolean offline, boolean 
+delivered, boolean displayed, boolean composing)</b></i> for requesting event notifications.
+</p>
+
+<b>Example</b><p>
+Below you can find an example that logs in a user to the server, creates a message, adds the requests
+for notifications and sends the message.
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+    
+      <font color="#3f7f5f">// Create a chat with user2</font>
+      Chat chat1 = conn1.createChat(user2);
+    
+      <font color="#3f7f5f">// Create a message to send</font>
+      Message msg = chat1.createMessage();
+      msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
+      msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
+      <font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font>
+      <font color="#3f7f5f">// composing)</font>
+      MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
+    
+      <font color="#3f7f5f">// Send the message that contains the notifications request</font>
+      chat1.sendMessage(msg);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="lstevnotreq">Reacting to Event Notification Requests</a></div><p>
+
+<b>Description</b><p>
+
+You can receive notification requests for the following events: delivered, displayed, composing and offline. You 
+<b>must</b> listen for these requests and react accordingly.</p>
+
+<b>Usage</b><p>
+
+The general idea is to create a new <i>DefaultMessageEventRequestListener</i> that will listen to the event notifications 
+requests and react with custom logic.  Then you will have to add the listener to the 
+<i>MessageEventManager</i> that works on 
+the desired <i>XMPPConnection</i>.
+<p>Note that <i>DefaultMessageEventRequestListener</i> is a default implementation of the 
+<i>MessageEventRequestListener</i> interface.
+The class <i>DefaultMessageEventRequestListener</i> automatically sends a delivered notification to the sender of the message 
+if the sender has requested to be notified when the message is delivered. If you decide to create a new class that
+implements the <i>MessageEventRequestListener</i> interface, please remember to send the delivered notification.</p>
+<ul>
+  <li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor. 
+  </li>
+  <li>To create an event notification requests listener create a subclass of <i><b>DefaultMessageEventRequestListener</b></i> or
+  create a class that implements the <i><b>MessageEventRequestListener</b></i> interface.
+  </li>
+  <li>To add a listener to the messageEventManager use the MessageEventManager's message 
+<i><b>addMessageEventRequestListener(MessageEventRequestListener)</b></i>.</li>
+</ul></p>
+
+<b>Example</b><p>
+
+Below you can find an example that connects two users to the server. One user will create a message, add the requests
+for notifications and will send the message to the other user. The other user will add a 
+<i>DefaultMessageEventRequestListener</i> 
+to a <i>MessageEventManager</i> that will listen and react to the event notification requested by the other user.
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in the users</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+      conn2 = new XMPPConnection(host);
+      conn2.login(server_user2, pass2);
+  
+      <font color="#3f7f5f">// User2 creates a MessageEventManager</font>
+      MessageEventManager messageEventManager = new MessageEventManager(conn2);
+      <font color="#3f7f5f">// User2 adds the listener that will react to the event notifications requests</font>
+      messageEventManager.addMessageEventRequestListener(new DefaultMessageEventRequestListener() {
+          public void deliveredNotificationRequested(
+              String from,
+              String packetID,
+              MessageEventManager messageEventManager) {
+              super.deliveredNotificationRequested(from, packetID, messageEventManager);
+              <font color="#3f7f5f">// DefaultMessageEventRequestListener automatically responds that the message was delivered when receives this request</font>
+              System.out.println(<font color="#0000FF">"Delivered Notification Requested (" + from + ", " + packetID + ")"</font>);
+          }
+
+          public void displayedNotificationRequested(
+              String from,
+              String packetID,
+              MessageEventManager messageEventManager) {
+              super.displayedNotificationRequested(from, packetID, messageEventManager);
+              <font color="#3f7f5f">// Send to the message's sender that the message was displayed</font>
+              messageEventManager.sendDisplayedNotification(from, packetID);
+          }
+
+          public void composingNotificationRequested(
+              String from,
+              String packetID,
+              MessageEventManager messageEventManager) {
+              super.composingNotificationRequested(from, packetID, messageEventManager);
+              <font color="#3f7f5f">// Send to the message's sender that the message's receiver is composing a reply</font>
+              messageEventManager.sendComposingNotification(from, packetID);
+          }
+
+          public void offlineNotificationRequested(
+              String from,
+              String packetID,
+              MessageEventManager messageEventManager) {
+              super.offlineNotificationRequested(from, packetID, messageEventManager);
+              <font color="#3f7f5f">// The XMPP server should take care of this request. Do nothing.</font>
+              System.out.println(<font color="#0000FF">"Offline Notification Requested (" + from + ", " + packetID + ")"</font>);
+          }
+      });
+
+      <font color="#3f7f5f">// User1 creates a chat with user2</font>
+      Chat chat1 = conn1.createChat(user2);
+    
+      <font color="#3f7f5f">// User1 creates a message to send to user2</font>
+      Message msg = chat1.createMessage();
+      msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
+      msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
+      <font color="#3f7f5f">// User1 adds to the message all the notifications requests (offline, delivered, displayed,</font>
+      <font color="#3f7f5f">// composing)</font>
+      MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
+    
+      <font color="#3f7f5f">// User1 sends the message that contains the notifications request</font>
+      chat1.sendMessage(msg);
+      Thread.sleep(500);
+      <font color="#3f7f5f">// User2 sends to the message's sender that the message's receiver cancelled composing a reply</font>
+      messageEventManager.sendCancelledNotification(user1, msg.getPacketID());
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="lstevnot">Reacting to Event Notifications</a></div><p>
+
+<b>Description</b><p>
+
+Once you have requested for event notifications you will start to receive notifications of events. You can 
+receive notifications of the following events: delivered, displayed, composing, offline and cancelled. You 
+will probably want to react to some or all of these events.</p>
+
+<b>Usage</b><p>
+
+The general idea is to create a new <i>MessageEventNotificationListener</i> that will listen to the event notifications 
+and react with custom logic.  Then you will have to add the listener to the <i>MessageEventManager</i> that works on 
+the desired <i>XMPPConnection</i>.
+<ul>
+  <li>To create a new <i>MessageEventManager</i> use the <i><b>MessageEventManager(XMPPConnection)</b></i> constructor. 
+  </li>
+  <li>To create an event notifications listener create a class that implements the <i><b>MessageEventNotificationListener</b></i> 
+  interface.
+  </li>
+  <li>To add a listener to the messageEventManager use the MessageEventManager's message 
+<i><b>addMessageEventNotificationListener(MessageEventNotificationListener)</b></i>.</li>
+</ul></p>
+
+<b>Example</b><p>
+Below you can find an example that logs in a user to the server, adds a <i>MessageEventNotificationListener</i>
+to a <i>MessageEventManager</i> that will listen and react to the event notifications, creates a message, adds 
+the requests for notifications and sends the message.
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+  
+      <font color="#3f7f5f">// Create a MessageEventManager</font>
+      MessageEventManager messageEventManager = new MessageEventManager(conn1);
+      <font color="#3f7f5f">// Add the listener that will react to the event notifications</font>
+      messageEventManager.addMessageEventNotificationListener(new MessageEventNotificationListener() {
+          public void deliveredNotification(String from, String packetID) {
+              System.out.println(<font color="#0000FF">"The message has been delivered (" + from + ", " + packetID + ")"</font>);
+          }
+    
+          public void displayedNotification(String from, String packetID) {
+              System.out.println(<font color="#0000FF">"The message has been displayed (" + from + ", " + packetID + ")"</font>);
+          }
+    
+          public void composingNotification(String from, String packetID) {
+              System.out.println(<font color="#0000FF">"The message's receiver is composing a reply (" + from + ", " + packetID + ")"</font>);
+          }
+    
+          public void offlineNotification(String from, String packetID) {
+              System.out.println(<font color="#0000FF">"The message's receiver is offline (" + from + ", " + packetID + ")"</font>);
+          }
+    
+          public void cancelledNotification(String from, String packetID) {
+              System.out.println(<font color="#0000FF">"The message's receiver cancelled composing a reply (" + from + ", " + packetID + ")"</font>);
+          }
+      });
+
+      <font color="#3f7f5f">// Create a chat with user2</font>
+      Chat chat1 = conn1.createChat(user2);
+    
+      <font color="#3f7f5f">// Create a message to send</font>
+      Message msg = chat1.createMessage();
+      msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
+      msg.setBody(<font color="#0000FF">"An interesting body comes here..."</font>);
+      <font color="#3f7f5f">// Add to the message all the notifications requests (offline, delivered, displayed,</font>
+      <font color="#3f7f5f">// composing)</font>
+      MessageEventManager.addNotificationsRequests(msg, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>, <font COLOR="#7f0055"><b>true</b></font>);
+    
+      <font color="#3f7f5f">// Send the message that contains the notifications request</font>
+      chat1.sendMessage(msg);
+</pre>
+</blockquote>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/muc.html b/CopyOftrunk/documentation/extensions/muc.html
new file mode 100644
index 000000000..ead69564a
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/muc.html
@@ -0,0 +1,619 @@
+<html>
+<head>
+<title>Multi User Chat</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Multi User Chat</div><p>
+
+Allows configuration of, participation in, and administration of individual text-based conference rooms.<p>
+
+<ul>
+  <li><a href="#create">Create a new Room</a></li>
+  <li><a href="#join">Join a room</a></li>
+  <li><a href="#invite">Manage room invitations</a></li>
+  <li><a href="#discomuc">Discover MUC support</a></li>
+  <li><a href="#discojoin">Discover joined rooms</a></li>
+  <li><a href="#discoroom">Discover room information</a></li>
+  <li><a href="#privchat">Start a private chat</a></li>
+  <li><a href="#subject">Manage changes on room subject</a></li>
+  <li><a href="#role">Manage role modifications</a></li>
+  <li><a href="#afiliation">Manage affiliation modifications</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>
+
+<hr>
+
+<div class="subheader"><a name="create">Create a new Room</a></div><p>
+
+<b>Description</b><p>
+
+Allowed users may create new rooms. There are two types of rooms that you can create. <b>Instant rooms</b>
+which are available for immediate access and are automatically created based on some default 
+configuration and <b>Reserved rooms</b> which are manually configured by the room creator before 
+anyone is allowed to enter.</p>
+
+<b>Usage</b><p>
+
+In order to create a room you will need to first create an instance of <i><b>MultiUserChat</b></i>. The 
+room name passed to the constructor will be the name of the room to create. The next step is to send 
+<b>create(String nickname)</b> to the <i><b>MultiUserChat</b></i> instance where nickname is the nickname 
+to use when joining the room.</p><p>
+
+Depending on the type of room that you want to create you will have to use different configuration forms. In
+order to create an Instant room just send <b>sendConfigurationForm(Form form)</b> where form is an empty form.
+But if you want to create a Reserved room then you should first get the room's configuration form, complete
+the form and finally send it back to the server.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to create an instant room: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font>
+      MultiUserChat muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+
+      <font color="#3f7f5f">// Create the room</font>
+      muc.create(<font color="#0000FF">"testbot"</font>);
+
+      <font color="#3f7f5f">// Send an empty room configuration form which indicates that we want</font>
+      <font color="#3f7f5f">// an instant room</font>
+      muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
+</pre>
+</blockquote>
+
+In this example we can see how to create a reserved room. The form is completed with default values: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font>
+      MultiUserChat muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+
+      <font color="#3f7f5f">// Create the room</font>
+      muc.create(<font color="#0000FF">"testbot"</font>);
+
+      <font color="#3f7f5f">// Get the the room's configuration form</font>
+      Form form = muc.getConfigurationForm();
+      <font color="#3f7f5f">// Create a new form to submit based on the original form</font>
+      Form submitForm = form.createAnswerForm();
+      <font color="#3f7f5f">// Add default answers to the form to submit</font>
+      for (Iterator fields = form.getFields(); fields.hasNext();) {
+          FormField field = (FormField) fields.next();
+          if (!FormField.TYPE_HIDDEN.equals(field.getType()) && field.getVariable() != null) {
+              <font color="#3f7f5f">// Sets the default value as the answer</font>
+              submitForm.setDefaultAnswer(field.getVariable());
+          }
+      }
+      <font color="#3f7f5f">// Sets the new owner of the room</font>
+      List owners = new ArrayList();
+      owners.add(<font color="#0000FF">"johndoe@jabber.org"</font>);
+      submitForm.setAnswer(<font color="#0000FF">"muc#roomconfig_roomowners"</font>, owners);
+      <font color="#3f7f5f">// Send the completed form (with default values) to the server to configure the room</font>
+      muc.sendConfigurationForm(submitForm);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="join">Join a room</a></div><p>
+
+<b>Description</b><p>
+
+Your usual first step in order to send messages to a room is to join the room. Multi User Chat allows
+to specify several parameter while joining a room. Basically you can control the amount of history to
+receive after joining the room as well as provide your nickname within the room and a password if the 
+room is password protected.</p>
+
+<b>Usage</b><p>
+
+In order to join a room you will need to first create an instance of <i><b>MultiUserChat</b></i>. The 
+room name passed to the constructor will be the name of the room to join. The next step is to send 
+<b>join(...)</b> to the <i><b>MultiUserChat</b></i> instance. But first you will have to decide which
+join message to send. If you want to just join the room without a password and without specifying the amount
+of history to receive then you could use <b>join(String nickname)</b> where nickname if your nickname in
+the room. In case the room requires a password in order to join you could then use 
+<b>join(String nickname, String password)</b>. And finally, the most complete way to join a room is to send 
+<b>join(String nickname, String password, DiscussionHistory history, long timeout)</b>
+where nickname is your nickname in the room, , password is your password to join the room, history is
+an object that specifies the amount of history to receive and timeout is the milliseconds to wait
+for a response from the server.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to join a room with a given nickname: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font>
+      MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+
+      <font color="#3f7f5f">// User2 joins the new room</font>
+      <font color="#3f7f5f">// The room service will decide the amount of history to send</font>
+      muc2.join(<font color="#0000FF">"testbot2"</font>);
+</pre>
+</blockquote>
+
+In this example we can see how to join a room with a given nickname and password: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font>
+      MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+
+      <font color="#3f7f5f">// User2 joins the new room using a password</font>
+      <font color="#3f7f5f">// The room service will decide the amount of history to send</font>
+      muc2.join(<font color="#0000FF">"testbot2"</font>, <font color="#0000FF">"password"</font>);
+</pre>
+</blockquote>
+
+In this example we can see how to join a room with a given nickname specifying the amount of history
+to receive: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a MultiUserChat using an XMPPConnection for a room</font>
+      MultiUserChat muc2 = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+
+      <font color="#3f7f5f">// User2 joins the new room using a password and specifying</font>
+      <font color="#3f7f5f">// the amount of history to receive. In this example we are requesting the last 5 messages.</font>
+      DiscussionHistory history = new DiscussionHistory();
+      history.setMaxStanzas(5);
+      muc2.join(<font color="#0000FF">"testbot2"</font>, <font color="#0000FF">"password"</font>, history, SmackConfiguration.getPacketReplyTimeout());
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="invite">Manage room invitations</a></div><p>
+
+<b>Description</b><p>
+
+It can be useful to invite another user to a room in which one is an occupant. Depending on the
+room's type the invitee could receive a password to use to join the room and/or be added to the 
+member list if the room is of type members-only. Smack allows to send room invitations and let 
+potential invitees to listening for room invitations and inviters to listen for invitees' 
+rejections.</p>
+
+<b>Usage</b><p>
+
+In order to invite another user to a room you must be already joined to the room. Once you are 
+joined just send <b>invite(String participant, String reason)</b> to the <i><b>MultiUserChat</b></i>
+where participant is the user to invite to the room (e.g. hecate@shakespeare.lit) and reason is
+the reason why the user is being invited.</p><p>
+
+If potential invitees want to listen for room invitations then the invitee must add an <i><b>InvitationListener</b></i>
+to the <i><b>MultiUserChat</b></i> class. Since the <i><b>InvitationListener</b></i> is an <i>interface</i>,
+it is necessary to create a class that implements this <i>interface</i>. If an inviter wants to
+listen for room invitation rejections, just add an <i><b>InvitationRejectionListener</b></i>
+to the <i><b>MultiUserChat</b></i>. <i><b>InvitationRejectionListener</b></i> is also an
+interface so you will need to create a class that implements this interface.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to invite another user to the room and lister for possible rejections: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// User2 joins the room</font>
+      MultiUserChat muc2 = new MultiUserChat(conn2, room);
+      muc2.join(<font color="#0000FF">"testbot2"</font>);
+
+      <font color="#3f7f5f">// User2 listens for invitation rejections</font>
+      muc2.addInvitationRejectionListener(new InvitationRejectionListener() {
+          public void invitationDeclined(String invitee, String reason) {
+              <font color="#3f7f5f">// Do whatever you need here...</font>
+          }
+      });
+
+      <font color="#3f7f5f">// User2 invites user3 to join to the room</font>
+      muc2.invite(<font color="#0000FF">"user3@host.org/Smack"</font>, <font color="#0000FF">"Meet me in this excellent room"</font>);
+</pre>
+</blockquote>
+
+In this example we can see how to listen for room invitations and decline invitations: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// User3 listens for MUC invitations</font>
+      MultiUserChat.addInvitationListener(conn3, new InvitationListener() {
+          public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password) {
+              <font color="#3f7f5f">// Reject the invitation</font>
+              MultiUserChat.decline(conn, room, inviter, <font color="#0000FF">"I'm busy right now"</font>);
+          }
+      });
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discomuc">Discover MUC support</a></div><p>
+
+<b>Description</b><p>
+
+A user may want to discover if one of the user's contacts supports the Multi-User Chat protocol.</p>
+
+<b>Usage</b><p>
+
+In order to discover if one of the user's contacts supports MUC just send 
+<b>isServiceEnabled(XMPPConnection connection, String user)</b> to the <i><b>MultiUserChat</b></i> 
+class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will receive
+a boolean indicating whether the user supports MUC or not.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to discover support of MUC: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Discover whether user3@host.org supports MUC or not</font>
+      boolean supports = MultiUserChat.isServiceEnabled(conn, <font color="#0000FF">"user3@host.org/Smack"</font>);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discojoin">Discover joined rooms</a></div><p>
+
+<b>Description</b><p>
+
+A user may also want to query a contact regarding which rooms the contact is in.</p>
+
+<b>Usage</b><p>
+
+In order to get the rooms where a user is in just send
+<b>getJoinedRooms(XMPPConnection connection, String user)</b> to the <i><b>MultiUserChat</b></i>
+class where user is a fully qualified XMPP ID, e.g. jdoe@example.com. You will get an Iterator
+of Strings as an answer where each String represents a room name.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to get the rooms where a user is in: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Get the rooms where user3@host.org has joined</font>
+      Iterator joinedRooms = MultiUserChat.getJoinedRooms(conn, <font color="#0000FF">"user3@host.org/Smack"</font>);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="discoroom">Discover room information</a></div><p>
+
+<b>Description</b><p>
+
+A user may need to discover information about a room without having to actually join the room. The server
+will provide information only for public rooms.</p>
+
+<b>Usage</b><p>
+
+In order to discover information about a room just send <b>getRoomInfo(XMPPConnection connection, String room)</b>
+to the <i><b>MultiUserChat</b></i> class where room is the XMPP ID of the room, e.g.
+roomName@conference.myserver. You will get a RoomInfo object that contains the discovered room
+information.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to discover information about a room: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Discover information about the room roomName@conference.myserver</font>
+      RoomInfo info = MultiUserChat.getRoomInfo(conn, <font color="#0000FF">"roomName@conference.myserver"</font>);
+      System.out.println("Number of occupants:" + info.getOccupantsCount());
+      System.out.println("Room Subject:" + info.getSubject());
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="privchat">Start a private chat</a></div><p>
+
+<b>Description</b><p>
+
+A room occupant may want to start a private chat with another room occupant even though they 
+don't know the fully qualified XMPP ID (e.g. jdoe@example.com) of each other.</p>
+
+<b>Usage</b><p>
+
+To create a private chat with another room occupant just send <b>createPrivateChat(String participant)</b>
+to the <i><b>MultiUserChat</b></i> that you used to join the room. The parameter participant is the
+occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). You will receive
+a regular <i><b>Chat</b></i> object that you can use to chat with the other room occupant.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to start a private chat with another room occupant: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Start a private chat with another participant</font>
+      Chat chat = muc2.createPrivateChat(<font color="#0000FF">"myroom@conference.jabber.org/johndoe"</font>);
+      chat.sendMessage(<font color="#0000FF">"Hello there"</font>);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="subject">Manage changes on room subject</a></div><p>
+
+<b>Description</b><p>
+
+A common feature of multi-user chat rooms is the ability to change the subject within the room. As a 
+default, only users with a role of "moderator" are allowed to change the subject in a room. Although 
+some rooms may be configured to allow a mere participant or even a visitor to change the subject.</p><p>
+
+Every time the room's subject is changed you may want to be notified of the modification. The new subject
+could be used to display an in-room message.</p>
+
+<b>Usage</b><p>
+
+In order to modify the room's subject just send <b>changeSubject(String subject)</b> to the 
+<i><b>MultiUserChat</b></i> that you used to join the room where subject is the new room's subject. On
+the other hand, if you want to be notified whenever the room's subject is modified you should add a 
+<i><b>SubjectUpdatedListener</b></i> to the <i><b>MultiUserChat</b></i> by sending 
+<b>addSubjectUpdatedListener(SubjectUpdatedListener listener)</b> to the <i><b>MultiUserChat</b></i>.
+Since the <i><b>SubjectUpdatedListener</b></i> is an <i>interface</i>, it is necessary to create a class 
+that implements this <i>interface</i>.</p>
+
+<b>Examples</b><p>
+
+In this example we can see how to change the room's subject and react whenever the room's subject is 
+modified: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// An occupant wants to be notified every time the room's subject is changed</font>
+      muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() {
+          public void subjectUpdated(String subject, String from) {
+              ....
+          }
+      });
+
+      <font color="#3f7f5f">// A room's owner changes the room's subject</font>
+      muc2.changeSubject(<font color="#0000FF">"New Subject"</font>);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="role">Manage role modifications</a></div><p>
+
+<b>Description</b><p>
+
+There are four defined roles that an occupant can have:</p>
+<ol start="" type="">
+   <li>Moderator</li>
+   <li>Participant</li>
+   <li>Visitor</li>
+   <li>None (the absence of a role)</li>
+</ol><p>
+
+These roles are temporary in that they do not persist across a user's visits to the room 
+and can change during the course of an occupant's visit to the room.</p><p>
+
+A moderator is the most powerful occupant within the context of the room, and can to some 
+extent manage other occupants' roles in the room. A participant has fewer privileges than a 
+moderator, although he or she always has the right to speak. A visitor is a more restricted 
+role within the context of a moderated room, since visitors are not allowed to send messages 
+to all occupants.</p><p>
+
+Roles are granted, revoked, and maintained based on the occupant's room nickname or full 
+JID. Whenever an occupant's role is changed Smack will trigger specific events.</p>
+
+<b>Usage</b><p>
+
+In order to grant voice (i.e. make someone a <i>participant</i>) just send the message 
+<b>grantVoice(String nickname)</b> to <i><b>MultiUserChat</b></i>. Use <b>revokeVoice(String nickname)</b>
+to revoke the occupant's voice (i.e. make the occupant a <i>visitor</i>).</p><p>
+
+In order to grant moderator privileges to a participant or visitor just send the message 
+<b>grantModerator(String nickname)</b> to <i><b>MultiUserChat</b></i>. Use <b>revokeModerator(String nickname)</b>
+to revoke the moderator privilege from the occupant thus making the occupant a participant.</p><p>
+
+Smack allows you to listen for role modification events. If you are interested in listening role modification
+events of any occupant then use the listener <b><i>ParticipantStatusListener</i></b>. But if you are interested 
+in listening for your own role modification events, use the listener <b><i>UserStatusListener</i></b>. Both listeners 
+should be added to the <i><b>MultiUserChat</b></i> by using 
+<b>addParticipantStatusListener(ParticipantStatusListener listener)</b> or 
+<b>addUserStatusListener(UserStatusListener listener)</b> respectively. These listeners include several notification
+events but you may be interested in just a few of them. Smack provides default implementations for these listeners
+avoiding you to implement all the interfaces' methods. The default implementations are <b><i>DefaultUserStatusListener</i></b>
+and <b><i>DefaultParticipantStatusListener</i></b>. Below you will find the sent messages to the listeners whenever 
+an occupant's role has changed.</p><p>
+
+These are the triggered events when the role has been upgraded:
+</p>
+<table border="1">
+<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+      
+<tr><td>None</td><td>Visitor</td><td>--</td></tr>
+<tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr>
+<tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr>
+
+<tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr>
+<tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
+<tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
+</table><p>
+
+These are the triggered events when the role has been downgraded:
+</p>
+<table border="1">
+<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+      
+<tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr>
+<tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr>
+<tr><td>Visitor</td><td>None</td><td>kicked</td></tr>
+
+<tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr>
+<tr><td>Moderator</td><td>None</td><td>kicked</td></tr>
+<tr><td>Participant</td><td>None</td><td>kicked</td></tr>
+</table></p>
+
+<b>Examples</b><p>
+
+In this example we can see how to grant voice to a visitor and listen for the notification events: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// User1 creates a room</font>
+      muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc.create(<font color="#0000FF">"testbot"</font>);
+
+      <font color="#3f7f5f">// User1 (which is the room owner) configures the room as a moderated room</font>
+      Form form = muc.getConfigurationForm();
+      Form answerForm = form.createAnswerForm();
+      answerForm.setAnswer(<font color="#0000FF">"muc#roomconfig_moderatedroom"</font>, <font color="#0000FF">"1"</font>);
+      muc.sendConfigurationForm(answerForm);
+
+      <font color="#3f7f5f">// User2 joins the new room (as a visitor)</font>
+      MultiUserChat muc2 = new MultiUserChat(conn2, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc2.join(<font color="#0000FF">"testbot2"</font>);
+      <font color="#3f7f5f">// User2 will listen for his own "voice" notification events</font>
+      muc2.addUserStatusListener(new DefaultUserStatusListener() {
+          public void voiceGranted() {
+              super.voiceGranted();
+              ...
+          }
+          public void voiceRevoked() {
+              super.voiceRevoked();
+              ...
+          }
+      });
+
+      <font color="#3f7f5f">// User3 joins the new room (as a visitor)</font>
+      MultiUserChat muc3 = new MultiUserChat(conn3, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc3.join(<font color="#0000FF">"testbot3"</font>);
+      <font color="#3f7f5f">// User3 will lister for other occupants "voice" notification events</font>
+      muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+          public void voiceGranted(String participant) {
+              super.voiceGranted(participant);
+              ...
+          }
+
+          public void voiceRevoked(String participant) {
+              super.voiceRevoked(participant);
+              ...
+          }
+      });
+
+      <font color="#3f7f5f">// The room's owner grants voice to user2</font>
+      muc.grantVoice(<font color="#0000FF">"testbot2"</font>);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="afiliation">Manage affiliation modifications</a></div><p>
+
+<b>Description</b><p>
+
+There are five defined affiliations that a user can have in relation to a room:</p>
+<ol start="" type="">
+  <li>Owner</li>
+  <li>Admin</li>
+  <li>Member</li>
+  <li>Outcast</li>
+  <li>None (the absence of an affiliation)</li>
+</ol><p>
+
+These affiliations are semi-permanent in that they persist across a user's visits to the room and 
+are not affected by happenings in the room. Affiliations are granted, revoked, and maintained 
+based on the user's bare JID.</p><p>
+
+If a user without a defined affiliation enters a room, the user's affiliation is defined as &quot;none&quot;; 
+however, this affiliation does not persist across visits.</p><p>
+
+Owners and admins are by definition immune from certain actions. Specifically, an owner or admin cannot 
+be kicked from a room and cannot be banned from a room. An admin must first lose his or her affiliation 
+(i.e., have an affiliation of &quot;none&quot; or &quot;member&quot;) before such actions could be performed 
+on them.</p><p>
+
+The member affiliation provides a way for a room owner or admin to specify a &quot;whitelist&quot; of users 
+who are allowed to enter a members-only room. When a member enters a members-only room, his or her affiliation 
+does not change, no matter what his or her role is. The member affiliation also provides a way for users to 
+effectively register with an open room and thus be permanently associated with that room in some way (one 
+result may be that the user's nickname is reserved in the room).</p><p>
+
+An outcast is a user who has been banned from a room and who is not allowed to enter the room. Whenever a 
+user's affiliation is changed Smack will trigger specific events.</p>
+
+<b>Usage</b><p>
+
+In order to grant membership to a room, administrator privileges or owner priveliges just send
+<b>grantMembership(String jid)</b>, <b>grantAdmin(String jid)</b> or <b>grantOwnership(String jid)</b> 
+to <i><b>MultiUserChat</b></i> respectively. Use <b>revokeMembership(String jid)</b>, <b>revokeAdmin(String jid)</b> 
+or <b>revokeOwnership(String jid)</b> to revoke the membership to a room, administrator privileges or 
+owner priveliges respectively.</p><p>
+
+In order to ban a user from the room just send the message <b>banUser(String jid, String reason)</b> to 
+<i><b>MultiUserChat</b></i>.</p><p>
+
+Smack allows you to listen for affiliation modification events. If you are interested in listening affiliation modification
+events of any user then use the listener <b><i>ParticipantStatusListener</i></b>. But if you are interested 
+in listening for your own affiliation modification events, use the listener <b><i>UserStatusListener</i></b>. Both listeners 
+should be added to the <i><b>MultiUserChat</b></i> by using 
+<b>addParticipantStatusListener(ParticipantStatusListener listener)</b> or 
+<b>addUserStatusListener(UserStatusListener listener)</b> respectively. These listeners include several notification
+events but you may be interested in just a few of them. Smack provides default implementations for these listeners
+avoiding you to implement all the interfaces' methods. The default implementations are <b><i>DefaultUserStatusListener</i></b>
+and <b><i>DefaultParticipantStatusListener</i></b>. Below you will find the sent messages to the listeners whenever 
+a user's affiliation has changed.</p><p>
+
+These are the triggered events when the affiliation has been upgraded:
+</p>
+<table border="1">
+<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+
+<tr><td>None</td><td>Member</td><td>membershipGranted</td></tr>
+<tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr>
+<tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr>
+
+<tr><td>None</td><td>Admin</td><td>adminGranted</td></tr>
+<tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr>
+<tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr>
+</table><p>
+
+These are the triggered events when the affiliation has been downgraded:
+</p>
+<table border="1">
+<tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+
+<tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr>
+<tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr>
+<tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr>
+
+<tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr>
+<tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr>
+<tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr>
+<tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr>
+</table></p>
+
+<b>Examples</b><p>
+
+In this example we can see how to grant admin privileges to a user and listen for the notification events: <br>
+<blockquote>
+<pre>      <font color="#3f7f5f">// User1 creates a room</font>
+      muc = new MultiUserChat(conn1, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc.create(<font color="#0000FF">"testbot"</font>);
+
+      <font color="#3f7f5f">// User1 (which is the room owner) configures the room as a moderated room</font>
+      Form form = muc.getConfigurationForm();
+      Form answerForm = form.createAnswerForm();
+      answerForm.setAnswer(<font color="#0000FF">"muc#roomconfig_moderatedroom"</font>, <font color="#0000FF">"1"</font>);
+      muc.sendConfigurationForm(answerForm);
+
+      <font color="#3f7f5f">// User2 joins the new room (as a visitor)</font>
+      MultiUserChat muc2 = new MultiUserChat(conn2, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc2.join(<font color="#0000FF">"testbot2"</font>);
+      <font color="#3f7f5f">// User2 will listen for his own admin privileges</font>
+      muc2.addUserStatusListener(new DefaultUserStatusListener() {
+          public void membershipRevoked() {
+              super.membershipRevoked();
+              ...
+          }
+          public void adminGranted() {
+              super.adminGranted();
+              ...
+          }
+      });
+
+      <font color="#3f7f5f">// User3 joins the new room (as a visitor)</font>
+      MultiUserChat muc3 = new MultiUserChat(conn3, <font color="#0000FF">"myroom@conference.jabber.org"</font>);
+      muc3.join(<font color="#0000FF">"testbot3"</font>);
+      <font color="#3f7f5f">// User3 will lister for other users admin privileges</font>
+      muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+          public void membershipRevoked(String participant) {
+              super.membershipRevoked(participant);
+              ...
+          }
+          public void adminGranted(String participant) {
+              super.adminGranted(participant);
+              ...
+          }
+      });
+
+      <font color="#3f7f5f">// The room's owner grants admin privileges to user2</font>
+      muc.grantAdmin(<font color="#0000FF">"user2@jabber.org"</font>);
+</pre>
+</blockquote>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/privatedata.html b/CopyOftrunk/documentation/extensions/privatedata.html
new file mode 100644
index 000000000..4d0f65887
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/privatedata.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<title>Private Data</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Private Data</div><p>
+
+Manages private data, which is a mechanism to allow users to store arbitrary XML
+data on an XMPP server. Each private data chunk is defined by a element name and
+XML namespace. Example private data:
+
+<pre>
+&lt;color xmlns="http://example.com/xmpp/color"&gt;
+    &lt;favorite&gt;blue&lt;/blue&gt;
+    &lt;leastFavorite&gt;puce&lt;/leastFavorite&gt;
+&lt;/color&gt;
+</pre><p>
+
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>
+
+<hr>
+
+<em>More coming soon.</em>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/rosterexchange.html b/CopyOftrunk/documentation/extensions/rosterexchange.html
new file mode 100644
index 000000000..e014da111
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/rosterexchange.html
@@ -0,0 +1,179 @@
+<html>
+<head>
+<title>Roster Item Exchange</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Roster Item Exchange</div><p>
+This extension is used to send rosters, roster groups and roster entries from one XMPP 
+Entity to another. It also provides an easy way to hook up custom logic when entries 
+are received from other XMPP clients.
+<p>Follow these links to learn how to send and receive roster items:</p>
+<ul>
+  <li><a href="#riesendroster">Send a complete roster</a></li>
+  <li><a href="#riesendgroup">Send a roster's group</a></li>
+  <li><a href="#riesendentry">Send a roster's entry</a></li>
+  <li><a href="#riercventry">Receive roster entries</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0093.html">JEP-93</a>
+
+<hr>
+
+<div class="subheader"><a name="riesendroster">Send a entire roster</a></div><p>
+
+<b>Description</b><p>
+
+Sometimes it is useful to send a whole roster to another user. Smack provides a 
+very easy way to send a complete roster to another XMPP client.</p>
+
+<b>Usage</b><p>
+
+Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(Roster, String)</b>
+message to send a roster to a given user. The first parameter is the roster to send and 
+the second parameter is the id of the user that will receive the roster entries.</p>
+
+<b>Example</b><p>
+
+In this example we can see how user1 sends his roster to user2. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+    
+      <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font>
+      RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
+      <font color="#3f7f5f">// Send user1's roster to user2</font>
+      rosterExchangeManager.send(conn1.getRoster(), user2);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="riesendgroup">Send a roster group</a></div><p>
+
+<b>Description</b><p>
+
+It is also possible to send a roster group to another XMPP client. A roster group groups 
+a set of roster entries under a name.</p>
+
+<b>Usage</b><p>
+
+Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(RosterGroup, String)</b>
+message to send a roster group to a given user. The first parameter is the roster group to send and 
+the second parameter is the id of the user that will receive the roster entries.</p>
+
+<b>Example</b><p>
+
+In this example we can see how user1 sends his roster groups to user2. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+    
+      <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font>
+      RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
+      <font color="#3f7f5f">// Send user1's RosterGroups to user2</font>
+      for (Iterator it = conn1.getRoster().getGroups(); it.hasNext(); )
+          rosterExchangeManager.send((RosterGroup)it.next(), user2);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="riesendentry">Send a roster entry</a></div><p>
+
+<b>Description</b><p>
+
+Sometimes you may need to send a single roster entry to another XMPP client. Smack also lets you send
+items at this granularity level.</p>
+
+<b>Usage</b><p>
+
+Create an instance of <i><b>RosterExchangeManager</b></i> and use the <b>#send(RosterEntry, String)</b>
+message to send a roster entry to a given user. The first parameter is the roster entry to send and 
+the second parameter is the id of the user that will receive the roster entries.</p>
+
+<b>Example</b><p>
+
+In this example we can see how user1 sends a roster entry to user2. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+    
+      <font color="#3f7f5f">// Create a new roster exchange manager on conn1</font>
+      RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1);
+      <font color="#3f7f5f">// Send a roster entry (any) to user2</font>
+      rosterExchangeManager1.send((RosterEntry)conn1.getRoster().getEntries().next(), user2);
+ </pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="riercventry">Receive roster entries</a></div><p>
+
+<b>Description</b><p>
+
+Since roster items are sent between XMPP clients, it is necessary to listen to possible roster entries 
+receptions. Smack provides a mechanism that you can use to execute custom logic when roster entries are
+received.</p>
+
+<b>Usage</b><p>
+
+<ol>
+  <li>Create a class that implements the <i><b>RosterExchangeListener</b></i> interface.</li>
+  <li>Implement the method <b>entriesReceived(String, Iterator)</b> that will be called when new entries 
+  are received with custom logic.</li>
+  <li>Add the listener to the <i>RosterExchangeManager</i> that works on the desired <i>XMPPConnection</i>.</li>
+</ol></p>
+
+<b>Example</b><p>
+
+In this example we can see how user1 sends a roster entry to user2 and user2 adds the received 
+entries to his roster. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Connect to the server and log in the users</font>
+      conn1 = new XMPPConnection(host);
+      conn1.login(server_user1, pass1);
+      conn2 = new XMPPConnection(host);
+      conn2.login(server_user2, pass2);
+      final Roster user2_roster = conn2.getRoster();
+    
+      <font color="#3f7f5f">// Create a RosterExchangeManager that will help user2 to listen and accept
+      the entries received</font>
+      RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(conn2);
+      <font color="#3f7f5f">// Create a RosterExchangeListener that will iterate over the received roster entries</font>
+      RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() {
+          public void entriesReceived(String from, Iterator remoteRosterEntries) {
+              while (remoteRosterEntries.hasNext()) {
+                  try {
+                      <font color="#3f7f5f">// Get the received entry</font>
+                      RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) remoteRosterEntries.next();
+                      <font color="#3f7f5f">// Display the remote entry on the console</font>
+                      System.out.println(remoteRosterEntry);
+                      <font color="#3f7f5f">// Add the entry to the user2's roster</font>
+                      user2_roster.createEntry(
+                          remoteRosterEntry.getUser(),
+                          remoteRosterEntry.getName(),
+                          remoteRosterEntry.getGroupArrayNames());
+                  }
+                  catch (XMPPException e) {
+                      e.printStackTrace();
+                  }
+              }
+          }
+      };
+      <font color="#3f7f5f">// Add the RosterExchangeListener to the RosterExchangeManager that user2 is using</font>
+      rosterExchangeManager2.addRosterListener(rosterExchangeListener);
+    
+      <font color="#3f7f5f">// Create a RosterExchangeManager that will help user1 to send his roster</font>
+      RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(conn1);
+      <font color="#3f7f5f">// Send user1's roster to user2</font>
+      rosterExchangeManager1.send(conn1.getRoster(), user2);
+</pre>
+</blockquote>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/style.css b/CopyOftrunk/documentation/extensions/style.css
new file mode 100644
index 000000000..5fbf1a70f
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/style.css
@@ -0,0 +1,57 @@
+BODY {
+    font-size : 100%;
+    background-color : #fff;
+}
+BODY, TD, TH {
+    font-family : tahoma, arial, helvetica;
+    font-size : 0.8em;
+}
+PRE, TT, CODE {
+    font-family : courier new, monospaced;
+    font-size : 1.0em;
+}
+A:hover {
+    text-decoration : none;
+}
+LI {
+    padding-bottom : 4px;
+}
+.header {
+    font-size : 1.4em;
+    font-weight : bold;
+    width : 100%;
+    border-bottom : 1px #ccc solid;
+    padding-bottom : 2px;
+}
+.subheader {
+    font-size: 1.1em;
+    font-weight : bold;
+}
+.footer {
+    font-size : 0.8em;
+    color : #999;
+    text-align : center;
+    width : 100%;
+    border-top : 1px #ccc solid;
+    padding-top : 2px;
+}
+.code {
+    border : 1px #ccc solid;
+    padding : 0em 1.0em 0em 1.0em;
+    margin : 4px 0px 4px 0px;
+}
+.nav, .nav A {
+    font-family : verdana;
+    font-size : 0.85em;
+    color : #600;
+    text-decoration : none;
+    font-weight : bold;
+}
+.nav {
+    width : 100%;
+    border-bottom : 1px #ccc solid;
+    padding : 3px 3px 5px 1px;
+}
+.nav A:hover {
+    text-decoration : underline;
+}
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/time.html b/CopyOftrunk/documentation/extensions/time.html
new file mode 100644
index 000000000..7c00efa77
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/time.html
@@ -0,0 +1,22 @@
+<html>
+    <head>
+    <title>Time</title>
+    <link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">Entity Time Exchange</div><p>
+
+Supports a protocol that XMPP clients use to exchange their respective local
+times and time zones.<p>
+
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a>
+
+<hr>
+
+<em>More coming soon.</em>
+
+</body>
+
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/toc.html b/CopyOftrunk/documentation/extensions/toc.html
new file mode 100644
index 000000000..b05e0d8f4
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/toc.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+<title>Smack Extensions User Manual</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+<base target="mainFrame">
+</head>
+
+<body>
+
+<a href="intro.html">Introduction</a><p>
+
+<div class="subheader">Smack Extensions</div><p>
+
+<a href="privatedata.html">Private Data</a><br>
+<a href="xhtml.html">XHTML Messages</a><br>
+<a href="messageevents.html">Message Events</a><br>
+<a href="dataforms.html">Data Forms</a><br>
+<a href="muc.html">Multi User Chat</a><br>
+<a href="rosterexchange.html">Roster Item Exchange</a><br>
+<a href="time.html">Time Exchange</a><br>
+<a href="invitation.html">Group Chat Invitations</a><br>
+<a href="disco.html">Service Discovery</a><br>
+</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/extensions/xhtml.html b/CopyOftrunk/documentation/extensions/xhtml.html
new file mode 100644
index 000000000..f283f57ba
--- /dev/null
+++ b/CopyOftrunk/documentation/extensions/xhtml.html
@@ -0,0 +1,200 @@
+<html>
+<head>
+<title>XHTML Support</title>
+<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">XHTML Messages</div><p>
+
+Provides the ability to send and receive formatted messages using XHTML.
+
+<p>Follow these links to learn how to compose, send, receive and discover support for 
+XHTML messages:</p>
+<ul>
+  <li><a href="#xhtmlcompose">Compose an XHTML Message</a></li>
+  <li><a href="#xhtmlsend">Send an XHTML Message</a></li>
+  <li><a href="#xhtmlreceive">Receive an XHTML Message</a></li>
+  <li><a href="#xhtmldiscover">Discover support for XHTML Messages</a></li>
+</ul>
+<b>JEP related:</b> <a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a>
+
+<hr>
+
+<div class="subheader"><a name="xhtmlcompose">Compose an XHTML Message</a></div><p>
+
+<b>Description</b><p>
+
+The first step in order to send an XHTML message is to compose it. Smack provides a special
+class that helps to build valid XHTML messages hiding any low level complexity.
+For special situations, advanced users may decide not to use the helper class and generate 
+the XHTML by themselves. Even for these situations Smack provides a well defined entry point 
+in order to add the generated XHTML content to a given message.</p>
+
+<p>
+Note: not all clients are able to view XHTML formatted messages. Therefore,
+it's recommended that you include a normal body in that message that is either an
+unformatted version of the text or a note that XHTML support is required
+to view the message contents.</p> 
+
+<b>Usage</b><p>
+
+Create an instance of <i><b>XHTMLText</b></i> specifying the style and language of the body.
+You can add several XHTML bodies to the message but each body should be for a different language.
+Once you have an XHTMLText you can start to append tags and text to it. In order to append tags there
+are several messages that you can use. For each XHTML defined tag there is a message that you can send.
+In order to add text you can send the message <b>#append(String textToAppend)</b>.</p>
+
+<p>After you have configured the XHTML text, the last step you have to do is to add the XHTML text 
+to the message you want to send. If you decided to create the XHTML text by yourself, you will have to
+follow this last step too. In order to add the XHTML text to the message send the message 
+<b>#addBody(Message message, String body)</b> to the <i><b>XHTMLManager</b></i> class where <i>message</i> 
+is the message that will receive the XHTML body and <i>body</i> is the string to add as an XHTML body to 
+the message.</b></p>
+
+<b>Example</b><p>
+
+In this example we can see how to compose the following XHTML message: <br>
+<font color="#0000FF">&lt;body&gt;&lt;p style='font-size:large'&gt;Hey John, this is my new &lt;span
+ style='color:green'&gt;green&lt;/span&gt;&lt;em&gt;!!!!&lt;/em&gt;&lt;/p&gt;&lt;/body&gt;</font>
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a message to send</font>
+      Message msg = chat.createMessage();
+      msg.setSubject(<font color="#0000FF">"Any subject you want"</font>);
+      msg.setBody(<font color="#0000FF">"Hey John, this is my new green!!!!"</font>);
+
+      <font color="#3f7f5f">// Create an XHTMLText to send with the message</font>
+      XHTMLText xhtmlText = new XHTMLText(null, null);
+      xhtmlText.appendOpenParagraphTag(<font color="#0000FF">"font-size:large"</font>);
+      xhtmlText.append(<font color="#0000FF">"Hey John, this is my new "</font>);
+      xhtmlText.appendOpenSpanTag(<font color="#0000FF">"color:green"</font>);
+      xhtmlText.append(<font color="#0000FF">"green"</font>);
+      xhtmlText.appendCloseSpanTag();
+      xhtmlText.appendOpenEmTag();
+      xhtmlText.append(<font color="#0000FF">"!!!!"</font>);
+      xhtmlText.appendCloseEmTag();
+      xhtmlText.appendCloseParagraphTag();
+      
+      <font color="#3f7f5f">// Add the XHTML text to the message</font>
+      XHTMLManager.addBody(msg, xhtmlText.toString());
+
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="xhtmlsend">Send an XHTML Message</a></div><p>
+
+<b>Description</b><p>
+
+After you have composed an XHTML message you will want to send it. Once you have added 
+the XHTML content to the message you want to send you are almost done. The last step is to send 
+the message as you do with any other message.</p>
+
+<b>Usage</b><p>
+
+An XHTML message is like any regular message, therefore to send the message you can follow 
+the usual steps you do in order to send a message. For example, to send a message as part
+of a chat just use the message <b>#send(Message)</b> of <i><b>Chat</b></i> or you can use
+the message <b>#send(Packet)</b> of <i><b>XMPPConnection</b></i>.</p>
+
+<b>Example</b><p>
+
+In this example we can see how to send a message with XHTML content as part of a chat. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a message to send</font>
+      Message msg = chat.createMessage();
+      <font color="#3f7f5f">// Obtain the XHTML text to send from somewhere</font>
+      String xhtmlBody = getXHTMLTextToSend();
+
+      <font color="#3f7f5f">// Add the XHTML text to the message</font>
+      XHTMLManager.addBody(msg, xhtmlBody);
+
+      <font color="#3f7f5f">// Send the message that contains the XHTML</font>
+      chat.sendMessage(msg);
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="xhtmlreceive">Receive an XHTML Message</a></div><p>
+
+<b>Description</b><p>
+
+It is also possible to obtain the XHTML content from a received message. Remember 
+that the specification defines that a message may contain several XHTML bodies 
+where each body should be for a different language.</p>
+
+<b>Usage</b><p>
+
+To get the XHTML bodies of a given message just send the message <b>#getBodies(Message)</b>
+ to the class <i><b>XHTMLManager</b></i>. The answer of this message will be an 
+ <i><b>Iterator</b></i> with the different XHTML bodies of the message or null if none.</p>
+
+<b>Example</b><p>
+
+In this example we can see how to create a PacketListener that obtains the XHTML bodies of any received message. 
+<blockquote>
+<pre>      <font color="#3f7f5f">// Create a listener for the chat and display any XHTML content</font>
+      PacketListener packetListener = new PacketListener() {
+          public void processPacket(Packet packet) {
+              Message message = (Message) packet;
+              <font color="#3f7f5f">// Obtain the XHTML bodies of the message</font>
+              Iterator it = XHTMLManager.getBodies(message);
+              if (it != null) {
+                 <font color="#3f7f5f">// Display the bodies on the console</font>
+                 while (it.hasNext()) {
+	             String body = (String) it.next();
+	             System.out.println(body);
+	         }
+	      }
+            };
+      chat.addMessageListener(packetListener);
+
+</pre>
+</blockquote>
+
+<hr>
+
+<div class="subheader"><a name="xhtmldiscover">Discover support for XHTML Messages</a></div><p>
+
+<b>Description</b><p>
+
+Before you start to send XHTML messages to a user you should discover if the user supports XHTML messages.
+There are two ways to achieve the discovery, explicitly and implicitly. Explicit is when you first try
+to discover if the user supports XHTML before sending any XHTML message. Implicit is when you send 
+XHTML messages without first discovering if the conversation partner's client supports XHTML and depenging on
+the answer (normal message or XHTML message) you find out if the user supports XHTML messages or not. This 
+section explains how to explicitly discover for XHTML support.</p>
+
+<b>Usage</b><p>
+
+In order to discover if a remote user supports XHTML messages send <b>#isServiceEnabled(XMPPConnection 
+connection, String userID)</b> to the class <i><b>XHTMLManager</b></i> where connection is the connection 
+to use to perform the service discovery and userID is the user to check (A fully qualified xmpp ID, 
+e.g. jdoe@example.com). This message will return true if the specified user handles XHTML messages.</p>
+
+<b>Example</b><p>
+
+In this example we can see how to discover if a remote user supports XHTML Messages. 
+<blockquote>
+<pre>      Message msg = chat.createMessage();
+      <font color="#3f7f5f">// Include a normal body in the message</font>
+      msg.setBody(getTextToSend());
+      <font color="#3f7f5f">// Check if the other user supports XHTML messages</font>
+      if (XHTMLManager.isServiceEnabled(connection, chat.getParticipant())) {
+          <font color="#3f7f5f">// Obtain the XHTML text to send from somewhere</font>
+          String xhtmlBody = getXHTMLTextToSend();
+
+          <font color="#3f7f5f">// Include an XHTML body in the message</font>
+          XHTMLManager.addBody(msg, xhtmlBody);
+      }
+
+      <font color="#3f7f5f">// Send the message</font>
+      chat.sendMessage(msg);
+</pre>
+</blockquote>
+</body>
+
+</html>
diff --git a/CopyOftrunk/documentation/gettingstarted.html b/CopyOftrunk/documentation/gettingstarted.html
new file mode 100644
index 000000000..127867885
--- /dev/null
+++ b/CopyOftrunk/documentation/gettingstarted.html
@@ -0,0 +1,109 @@
+<html>
+<head>
+	<title>Smack: Getting Started - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" /
+</head>
+
+<body>
+
+<div class="header">
+Getting Started With Smack
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+This document will introduce you to the Smack API and provide an overview of 
+important classes and concepts.
+</p>
+
+<p class="subheader">
+Requirements
+</p>
+
+The only requirement for Smack is JDK 1.2 or later<sup>
+<a style="text-decoration:none;" href="#ssenote">1</a></sup>. 
+An XML parser is embedded in the smack.jar file and no other third party 
+libraries are required.<p>
+
+<sup>1</sup> <font size="-1"><i>JDK 1.2 and 1.3 users that wish to use SSL connections must have the 
+<a href="http://java.sun.com/products/jsse/index-103.html">JSSE</a> library in their classpath.</i></font>
+
+<p class="subheader">
+Establishing a Connection
+</p>
+
+The <tt>XMPPConnection</tt> class is used to create a connection to an 
+XMPP server. To create an SSL connection, use the SSLXMPPConnection class.
+Below are code examples for making a connection:<p>
+
+<div class="code">
+<pre>
+<font color="gray"><i>// Create a connection to the jabber.org server.</i></font>
+XMPPConnection conn1 = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>);
+
+<font color="gray"><i>// Create a connection to the jabber.org server on a specific port.</i></font>
+XMPPConnection conn2 = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>, 5222);
+
+<font color="gray"><i>// Create an SSL connection to jabber.org.</i></font>
+XMPPConnection connection = <font color="navy"><b>new</b></font> SSLXMPPConnection(<font color="green">"jabber.org"</font>); 
+</pre></div>
+
+<p>Once you've created a connection, you should login using a username and password
+with the <tt>XMPPConnection.login(String username, String password)</tt> method.
+Once you've logged in, you can being chatting with other users by creating
+new <tt>Chat</tt> or <tt>GroupChat</tt> objects.
+
+<p class="subheader">
+Working with the Roster
+</p>
+The roster lets you keep track of the availability (presence) of other users. Users
+can be organized into groups such as "Friends" and "Co-workers", and then you
+discover whether each user is online or offline.<p> 
+
+Retrieve the roster using the <tt>XMPPConnection.getRoster()</tt> method. The roster
+class allows you to find all the roster entries, the groups they belong to, and the
+current presence status of each entry.
+
+<p class="subheader">
+Reading and Writing Packets
+</p>
+
+Each message to the XMPP server from a client is called a packet and is
+sent as XML. The <tt>org.jivesoftware.smack.packet</tt> package contains
+classes that encapsulate the three different basic packet types allowed by 
+XMPP (message, presence, and IQ). Classes such as <tt>Chat</tt> and <tt>GroupChat</tt>
+provide higher-level constructs that manage creating and sending packets
+automatically, but you can also create and send packets directly. Below
+is a code example for changing your presence to let people know you're unavailable
+and "out fishing":<p>
+
+<div class="code">
+<pre>
+<font color="gray"><i>// Create a new presence. Pass in false to indicate we're unavailable.</i></font>
+Presence presence = new Presence(Presence.Type.UNAVAILABLE);
+presence.setStatus(<font color="green">"Gone fishing"</font>);
+<font color="gray"><i>// Send the packet (assume we have a XMPPConnection instance called "con").</i></font>
+con.sendPacket(presence);
+</pre></div>
+<p>
+
+Smack provides two ways to read incoming packets: <tt>PacketListener</tt>, and 
+<tt>PacketCollector</tt>. Both use <tt>PacketFilter</tt> instances to determine 
+which packets should be processed. A packet listener is used for event style programming,
+while a packet collector has a result queue of packets that you can do
+polling and blocking operations on. So, a packet listener is useful when
+you want to take some action whenever a packet happens to come in, while a
+packet collector is useful when you want to wait for a specific packet
+to arrive. Packet collectors and listeners can be created using an
+XMPPConnection instance.
+
+
+<p><div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/images/debugwindow.gif b/CopyOftrunk/documentation/images/debugwindow.gif
new file mode 100644
index 000000000..e53c5dc33
Binary files /dev/null and b/CopyOftrunk/documentation/images/debugwindow.gif differ
diff --git a/CopyOftrunk/documentation/images/enhanceddebugger.png b/CopyOftrunk/documentation/images/enhanceddebugger.png
new file mode 100644
index 000000000..741a3e250
Binary files /dev/null and b/CopyOftrunk/documentation/images/enhanceddebugger.png differ
diff --git a/CopyOftrunk/documentation/images/roster.png b/CopyOftrunk/documentation/images/roster.png
new file mode 100644
index 000000000..5b5056fb7
Binary files /dev/null and b/CopyOftrunk/documentation/images/roster.png differ
diff --git a/CopyOftrunk/documentation/images/smacklogo.png b/CopyOftrunk/documentation/images/smacklogo.png
new file mode 100644
index 000000000..7ee139fbb
Binary files /dev/null and b/CopyOftrunk/documentation/images/smacklogo.png differ
diff --git a/CopyOftrunk/documentation/index.html b/CopyOftrunk/documentation/index.html
new file mode 100644
index 000000000..282a3f745
--- /dev/null
+++ b/CopyOftrunk/documentation/index.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+	<title>Smack Documentation - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<table border="0"><tr><td>
+<img src="images/smacklogo.png" width="100" height="100" alt="Smack" border="0" hspace="10">
+</td><td>
+<font size="4"><b>Smack Documentation</b></font>
+</div></td></tr></table>
+
+<p>
+<strong>Contents:</strong>
+</p>
+
+<ul>
+    <li><a href="overview.html">Overview</a>
+    <li><a href="gettingstarted.html">Getting Started Guide</a>
+	<li><a href="messaging.html">Messaging Basics</a>
+	<li><a href="roster.html">Roster and Presence</a>
+	<li><a href="processing.html">Processing Incoming Packets</a>
+	<li><a href="providers.html">Provider Architecture</a>
+    <li><a href="properties.html">Packet Properties</a>
+    <li><a href="debugging.html">Debugging with Smack</a>
+    <p>
+    <li><a href="extensions/index.html">Smack Extensions Manual</a>
+</ul>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/messaging.html b/CopyOftrunk/documentation/messaging.html
new file mode 100644
index 000000000..46dd5a8a1
--- /dev/null
+++ b/CopyOftrunk/documentation/messaging.html
@@ -0,0 +1,108 @@
+<html>
+<head>
+	<title>Smack: Chat - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Messaging using Chat and GroupChat
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+Sending messages back and forth is at the core of instant messaging. Two classes
+aid in sending and receiving messages:
+<ul>
+	<li> <tt>org.jivesoftware.smack.Chat</tt> -- used to send messages between two people.
+	<li> <tt>org.jivesoftware.smack.GroupChat</tt> -- used to join a chat room to send messages between many people.
+</ul>
+
+Both the Chat and GroupChat classes use the <tt>org.jivesoftware.smack.packet.Message</tt> packet
+class to send messages. In certain circumstances, you may wish to bypass the higher-level
+Chat and GroupChat classes to send and listen for messages directly.
+</p>
+
+<p class="subheader">
+Chat
+</p>
+
+A chat creates a new thread of messages (using a thread ID) between two users. The 
+following code snippet demonstrates how to create a new Chat with a user and then send
+them a text message:<p>
+
+<div class="code"><pre>
+<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font>
+Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>);
+newChat.sendMessage(<font color="green">"Howdy!"</font>);
+</pre></div><p>
+
+The <tt>Chat.sendMessage(String)</tt> method is a convenience method that creates a Message
+object, sets the body using the String parameter, then sends the message. In the case
+that you wish to set additional values on a Message before sending it, use the
+<tt>Chat.createMessage()</tt> and <tt>Chat.sendMessage(Message)</tt> methods, as in the
+following code snippet:<p>
+
+<div class="code"><pre>
+<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font>
+Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>);
+Message newMessage = newChat.createMessage();
+newMessage.setBody(<font color="green">"Howdy!"</font>);
+message.setProperty(<font color="green">"favoriteColor"</font>, <font color="green">"red"</font>);
+newChat.sendMessage(newMessage);
+</pre></div><p>
+
+The Chat object allows you to easily listen for replies from the other chat participant.
+The following code snippet is a parrot-bot -- it echoes back everything the other user types.<p>
+
+<div class="code"><pre>
+<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font>
+Chat newChat = connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>);
+newMessage.setBody(<font color="green">"Hi, I'm an annoying parrot-bot! Type something back to me."</font>);
+<b>while</b> (<b>true</b>) {
+    <font color="gray"><i>// Wait for the next message the user types to us.</i></font>
+    Message message = newChat.nextMessage();
+    <font color="gray"><i>// Send back the same text the other user sent us.</i></font>
+    newChat.sendMessage(message.getBody());
+}
+</pre></div><p>
+
+The code above uses the <tt>Chat.nextMessage()</tt> method to get the next message, which
+will wait indefinitely until another message comes in. There are other methods to wait
+a specific amount of time for a new message, or you can add a listener that will be notified
+every time a new message arrives.
+
+<p class="subheader">
+GroupChat
+</p>
+
+A group chat connects to a chat room on a server and allows you to send and receive messages
+from a group of people. Before you can send or receive messages, you must join the room using
+a nickname. The following code snippet connects to a chat room and sends a
+message.<p>
+
+<div class="code"><pre>
+<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font>
+GroupChat newGroupChat = connection.createGroupChat(<font color="green">"test@jivesoftware.com"</font>);
+<font color="gray"><i>// Join the group chat using the nickname "jsmith".</i></font>
+newGroupChat.join(<font color="green">"jsmith"</font>);
+<font color="gray"><i>// Send a message to all the other people in the chat room.</i></font>
+newGroupChat.sendMessage(<font color="green">"Howdy!"</font>);
+</pre></div><p>
+
+In general, sending and receiving messages in a group chat works very similarly to 
+the <tt>Chat</tt> class. Method are also provided to get the list of the other
+users in the room.<p>
+
+<br clear="all" /><br><br>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/overview.html b/CopyOftrunk/documentation/overview.html
new file mode 100644
index 000000000..4affaee85
--- /dev/null
+++ b/CopyOftrunk/documentation/overview.html
@@ -0,0 +1,72 @@
+<html>
+<head>
+	<title>Smack: Overview - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Smack Overview
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+
+Smack is a library for communicating with XMPP servers to perform
+instant messaging and chat.<p>
+
+<p class="subheader">
+Smack Key Advantages
+</p>
+
+<ul>
+	<li>Extremely simple to use, yet powerful API. Sending a text message to a user
+	    can be accomplished in three lines of code:
+
+<div class="code"><pre>
+XMPPConnection connection = <font color="navy"><b>new</b></font> XMPPConnection(<font color="green">"jabber.org"</font>);
+connection.login(<font color="green">"mtucker"</font>, <font color="green">"password"</font>);
+connection.createChat(<font color="green">"jsmith@jivesoftware.com"</font>).sendMessage(<font color="green">"Howdy!"</font>);
+</pre></div>
+
+
+	<li>Doesn't force you to code at the packet level, as other libraries do. Smack provides 
+		intelligent higher level constructs such as the <tt>Chat</tt> and <tt>GroupChat</tt> 
+		classes, which let you program more efficiently.
+
+	<li>Does not require that you're familiar with the XMPP XML format, or even that you're familiar with XML.
+
+	<li>Provides easy machine to machine communication. Smack lets you set any number of properties on
+		each message, including properties that are Java objects.
+
+	<li>Open Source under the Apache License, which means you can incorporate Smack into your commercial or
+		non-commercial applications.
+</ul>
+
+<p class="subheader">
+About XMPP
+</p>
+
+XMPP (eXtensible Messaging and Presence Protocol) is an open, XML based protocol
+making it's way through the IETF approval process under the guidance of the
+Jabber Software Foundation (<a href="http://www.jabber.org">http://www.jabber.org</a>).
+ 
+<p class="subheader">
+How To Use This Documentation
+</p>
+
+This documentation assumes that you're already familiar with the main features of XMPP
+instant messaging. It's also highly recommended that you open the Javadoc API guide and
+use that as a reference while reading through this documentation.
+ 
+<br clear="all" /><br><br>
+<div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/processing.html b/CopyOftrunk/documentation/processing.html
new file mode 100644
index 000000000..d8879aeb4
--- /dev/null
+++ b/CopyOftrunk/documentation/processing.html
@@ -0,0 +1,87 @@
+<html>
+<head>
+	<title>Smack: Processing Incoming Packets - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Processing Incoming Packets
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+
+Smack provides a flexible framework for processing incoming packets using two constructs:
+<ul>
+	<li><tt>org.jivesoftware.smack.PacketCollector</tt> -- a class that lets you 
+		synchronously wait for new packets.
+	<li><tt>org.jivesoftware.smack.PacketListener</tt> -- an interface for asynchronously 
+		notifying you of incoming packets. 
+</ul>	
+A packet listener is used for event style programming, while a packet collector has a 
+result queue of packets that you can do polling and blocking operations on. So, a packet 
+listener is useful when you want to take some action whenever a packet happens to come in, 
+while a packet collector is useful when you want to wait for a specific packet to come 
+arrive. Packet collectors and listeners can be created using an <tt>XMPPConnection</tt> instance.<p>
+	
+The <tt>org.jivesoftware.smack.filter.PacketFilter</tt> interface determines which
+specific packets will be delivered to a <tt>PacketCollector</tt> or <tt>PacketListener</tt>.
+Many pre-defined filters can be found in the <tt>org.jivesoftware.smack.filter</tt> package.
+
+<p>
+The following code snippet demonstrates registering both a packet collector and a packet
+listener:<p> 
+ 
+<div class="code"><pre>
+<font color="gray"><i>// Create a packet filter to listen for new messages from a particular</i></font>
+<font color="gray"><i>// user. We use an AndFilter to combine two other filters.</i></font>
+PacketFilter filter = new AndFilter(new PacketTypeFilter(<b>Message.class</b>), 
+        new FromContainsFilter(<font color="green">"mary@jivesoftware.com"</font>));
+<font color="gray"><i>// Assume we've created an XMPPConnection name "connection".</i></font>
+
+<font color="gray"><i>// First, register a packet collector using the filter we created.</i></font>
+PacketCollector myCollector = connection.createPacketCollector(filter);
+<font color="gray"><i>// Normally, you'd do something with the collector, like wait for new packets.</i></font>
+
+<font color="gray"><i>// Next, create a packet listener. We use an anonymous inner class for brevity.</i></font>
+PacketListener myListener = new PacketListener() {
+        <b>public</b> <b>void</b> processPacket(Packet packet) {
+            <font color="gray"><i>// Do something with the incoming packet here.</i></font>
+        }
+    };
+<font color="gray"><i>// Register the listener.</i></font>
+connection.addPacketListener(myListener, filter);
+</pre></div><p>
+
+<p class="subheader">
+Standard Packet Filters
+</p>
+
+A rich set of packet filters are included with Smack, or you can create your own filters by coding
+to the <tt>PacketFilter</tt> interface. The default set of filters includes:
+<ul>
+	<li> <tt>PacketTypeFilter</tt> -- filters for packets that are a particular Class type.
+	<li> <tt>PacketIDFilter</tt> -- filters for packets with a particular packet ID.
+	<li> <tt>ThreadFilter</tt> -- filters for message packets with a particular thread ID.
+	<li> <tt>ToContainsFilter</tt> -- filters for packets that are sent to a particular address.
+	<li> <tt>FromContainsFilter</tt> -- filters for packets that are sent to a particular address.
+	<li> <tt>PacketExtensionFilter</tt> -- filters for packets that have a particular packet extension.
+	<li> <tt>AndFilter</tt> -- implements the logical AND operation over two filters.
+	<li> <tt>OrFilter</tt> -- implements the logical OR operation over two filters.
+	<li> <tt>NotFilter</tt> -- implements the logical NOT operation on a filter.
+</ul>
+
+
+
+<br clear="all" /><br><br>
+<div class="footer">
+Copyright &copy; Jive Software 2002-2005
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/CopyOftrunk/documentation/properties.html b/CopyOftrunk/documentation/properties.html
new file mode 100644
index 000000000..8da6038bb
--- /dev/null
+++ b/CopyOftrunk/documentation/properties.html
@@ -0,0 +1,119 @@
+<html>
+<head>
+	<title>Smack: Packet Properties - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" /
+</head>
+
+<body>
+
+<div class="header">
+Packet Properties
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+Smack provides an easy mechanism for attaching arbitrary properties to packets. Each property
+has a String name, and a value that is a Java primitive (int, long, float, double, boolean) or
+any Serializable object (a Java object is Serializable when it implements the Serializable
+interface).
+</p>
+
+<p class="subheader">
+Using the API
+</p>
+
+<p>
+All major objects have property support, such as Message objects. The following code
+demonstrates how to set properties:
+</p>
+
+<div class="code"><pre>
+Message message = chat.createMessage();
+<font color="gray"></i>// Add a Color object as a property.</i></font>
+message.setProperty(<font color="blue">"favoriteColor"</font>, new Color(0, 0, 255));
+<font color="gray"></i>// Add an int as a property.</i></font>
+message.setProperty(<font color="blue">"favoriteNumber"</font>, 4);
+chat.sendMessage(message);
+</pre></div>
+
+<p>
+Getting those same properties would use the following code:
+</p>
+
+<div class="code"><pre>
+Message message = chat.nextMessage();
+<font color="gray"></i>// Get a Color object property.</i></font>
+Color favoriteColor = (Color)message.getProperty(<font color="blue">"favoriteColor"</font>);
+<font color="gray"></i>// Get an int property. Note that properties are always returned as
+// Objects, so we must cast the value to an Integer, then convert
+// it to an int.</i></font>
+int favoriteNumber = ((Integer)message.getProperty(<font color="blue">"favoriteNumber"</font>)).intValue();
+</pre></div>
+
+<p class="subheader">
+Objects as Properties
+</p>
+
+<p>
+Using objects as property values is a very powerful and easy way to exchange data. However,
+you should keep the following in mind:
+</p>
+
+<ul>
+ 	<li>Packet extensions are the more standard way to add extra data to XMPP stanzas. Using
+		properties may be more convenient in some cases, however, since Smack will do the
+		work of handling the XML.
+
+	<li>When you send a Java object as a property, only clients running Java will be able to 
+		interpret the data. So, consider using a series of primitive values to transfer data 
+		instead.
+
+	<li>Objects sent as property values must implement Serialiable. Additionally, both the sender
+		and receiver must have identical versions of the class, or a serialization exception
+		will occur when de-serializing the object.
+
+	<li>Serialized objects can potentially be quite large, which will use more bandwidth and
+		server resources.
+</ul>
+
+<p class="subheader">
+XML Format
+</p>
+
+<p>
+The current XML format used to send property data is not a standard, so will likely not be
+recognized by clients not using Smack. The XML looks like the following (comments added for
+clarity):
+</p>
+
+<div class="code"><pre>
+<font color="gray"><i>&lt;!-- All properties are in a x block. --&gt;</i></font> 
+&lt;properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties"&gt;
+    <font color="gray"><i>&lt;!-- First, a property named "prop1" that's an integer. --&gt;</i></font> 
+    &lt;property&gt;
+        &lt;name&gt;prop1&lt;/name&gt;
+        &lt;value type="integer"&gt;123&lt;/value&gt;
+    &lt;property&gt;
+    <font color="gray"><i>&lt;!-- Next, a Java object that's been serialized and then converted
+         from binary data to base-64 encoded text. --&gt;</i></font>  
+    &lt;property&gt;
+        &lt;name&gt;blah2&lt;/name&gt;
+        &lt;value type="java-object"&gt;adf612fna9nab&lt;/value&gt;
+    &lt;property&gt;
+&lt;/properties&gt; 
+</pre></div>
+
+<p>
+The currently supported types are: <tt>integer</tt>, <tt>long</tt>, <tt>float</tt>, 
+<tt>double</tt>, <tt>boolean</tt>, <tt>string</tt>, and <tt>java-object</tt>.
+</p>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2004
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/providers.html b/CopyOftrunk/documentation/providers.html
new file mode 100644
index 000000000..fb7e5e586
--- /dev/null
+++ b/CopyOftrunk/documentation/providers.html
@@ -0,0 +1,121 @@
+<html>
+<head>
+	<title>Smack: Provider Architecture - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Provider Architecture: Packet Extensions and Custom IQ's
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+
+The Smack provider architecture is a system for plugging in
+custom XML parsing of packet extensions and IQ packets. The 
+standard  <a href="extensions/index.html">Smack Extensions</a>
+are built using the provider architecture. Two types of
+providers exist:<ul>
+      <li><tt>IQProvider</tt> -- parses IQ requests into Java objects.
+      <li><tt>PacketExtension</tt> -- parses XML sub-documents attached to 
+      packets into PacketExtension instances.</ul>
+
+<p class="subheader">IQProvider</p>
+
+By default, Smack only knows how to process IQ packets with sub-packets that
+are in a few namespaces such as:<ul>
+      <li>jabber:iq:auth
+      <li>jabber:iq:roster
+      <li>jabber:iq:register</ul>
+
+Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
+mechanism is provided. IQ providers are registered programatically or by creating a
+smack.providers file in the META-INF directory of your JAR file. The file is an XML
+document that contains one or more iqProvider entries, as in the following example:
+
+<pre>
+ &lt;?xml version="1.0"?&gt;
+ &lt;smackProviders&gt;
+     &lt;iqProvider&gt;
+         &lt;elementName&gt;query&lt;/elementName&gt;
+         &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
+         &lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
+     &lt;/iqProvider&gt;
+ &lt;/smackProviders&gt;</pre>
+
+Each IQ provider is associated with an element name and a namespace. In the
+example above, the element name is <tt>query</tt> and the namespace is
+<tt>abber:iq:time</tt>. If multiple provider entries attempt to register to 
+handle the same namespace, the first entry loaded from the classpath will 
+take precedence. <p>
+
+The IQ provider class can either implement the IQProvider
+interface, or extend the IQ class. In the former case, each IQProvider is 
+responsible for parsing the raw XML stream to create an IQ instance. In 
+the latter case, bean introspection is used to try to automatically set 
+properties of the IQ instance using the values found in the IQ packet XML. 
+For example, an XMPP time packet resembles the following:
+
+<pre>
+&lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
+    &lt;query xmlns='jabber:iq:time'&gt;
+        &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
+        &lt;tz&gt;MDT&lt;/tz&gt;
+        &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
+    &lt;/query&gt;
+&lt;/iq&gt;</pre>
+
+In order for this packet to be automatically mapped to the Time object listed in the
+providers file above, it must have the methods setUtc(String), setTz(String), and
+setDisplay(String). The introspection service will automatically try to convert the String
+value from the XML into a boolean, int, long, float, double, or Class depending on the
+type the IQ instance expects.<p>
+
+<p class="subheader">PacketExtensionProvider</p>
+
+Packet extension providers provide a pluggable system for 
+packet extensions, which are child elements in a custom namespace 
+of IQ, message and presence packets.
+Each extension provider is registered with an element name and namespace 
+in the smack.providers file as in the following example:
+
+<pre>
+&lt;?xml version="1.0"?&gt;
+&lt;smackProviders&gt;
+    &lt;extensionProvider&gt;
+        &lt;elementName&gt;x&lt;/elementName&gt;
+        &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
+        &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
+    &lt;/extensionProvider&gt;
+&lt;/smackProviders&gt;</pre>
+
+If multiple provider entries attempt to register to handle the same element 
+name and namespace, the first entry loaded from the classpath will take 
+precedence.<p>
+
+Whenever a packet extension is found in a packet, parsing will 
+be passed to the correct provider. Each provider can either implement the 
+PacketExtensionProvider interface or be a standard Java Bean. In the 
+former case, each extension provider is responsible for parsing the raw 
+XML stream to contruct an object. In the latter case, bean introspection 
+is used to try to automatically set the properties of the class using 
+the values in the packet extension sub-element.<p>
+
+When an extension provider is not registered for an element name and 
+namespace combination, Smack will store all top-level elements of the 
+sub-packet in DefaultPacketExtension object and then attach it to the packet.
+      
+
+<br clear="all" /><br><br>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2004
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/roster.html b/CopyOftrunk/documentation/roster.html
new file mode 100644
index 000000000..025c84b54
--- /dev/null
+++ b/CopyOftrunk/documentation/roster.html
@@ -0,0 +1,125 @@
+<html>
+<head>
+	<title>Smack: Roster and Presence - Jive Software</title>
+	<link rel="stylesheet" type="text/css" href="style.css" />
+</head>
+
+<body>
+
+<div class="header">
+Roster and Presence
+</div>
+
+<div class="nav">
+&laquo; <a href="index.html">Table of Contents</a>
+</div>
+
+<p>
+
+The roster lets you keep track of the availability ("presence") of other users. 
+A roster also allows you to organize users into groups such as "Friends" and 
+"Co-workers". Other IM systems refer to the roster as the buddy list, contact list, 
+etc.<p>
+
+A <tt>Roster</tt> instance is obtained using the <tt>XMPPConnection.getRoster()</tt>
+method, but only after successfully logging into a server.
+
+<p class="subheader">Roster Entries</p>
+
+<p>
+Every user in a roster is represented by a RosterEntry, which consists of:<ul>
+    <li>An XMPP address (e.g. jsmith@example.com).
+    <li>A name you've assigned to the user (e.g. "Joe").
+    <li>The list of groups in the roster that the entry belongs to. If the roster
+        entry belongs to no groups, it's called an "unfiled entry".
+</ul>
+
+The following code snippet prints all entries in the roster:
+
+<pre>
+Roster roster = con.getRoster();
+<b>for</b> (Iterator i=roster.getEntries(); i.hasNext(); ) {
+    System.out.println(i.next());
+}
+</pre>
+
+Methods also exist to get individual entries, the list of unfiled entries, or to get one or
+all roster groups.
+
+<p class="subheader">Presence</p>
+
+<img src="images/roster.png" width="166" height="322" vspace="5" hspace="5" alt="Roster" border="0" align="right">
+
+<p>Every entry in the roster has presence associated with it. The 
+<tt>Roster.getPresence(String user)</tt> method will return a Presence object with
+the user's presence or <tt>null</tt> if the user is not online or you are not
+subscribed to the user's presence. <i>Note:</i> typically, presence
+subscription is always tied to the user being on the roster, but this is not
+true in all cases.</p>
+
+<p>A user either has a presence of online or offline. When a user is online, their
+presence may contain extended information such as what they are currently doing, whether
+they wish to be disturbed, etc. See the Presence class for further details.</p>
+
+<p class="subheader">Listening for Roster and Presence Changes</p>
+
+<p>The typical use of the roster class is to display a tree view of groups and entries
+along with the current presence value of each entry. As an example, see the image showing
+a Roster in the Exodus XMPP client to the right.</p>
+
+<p>The presence information will likely 
+change often, and it's also possible for the roster entries to change or be deleted. 
+To listen for changing roster and presence data, a RosterListener should be used. 
+The following code snippet registers a RosterListener with the Roster that prints
+any presence changes in the roster to standard out. A normal client would use
+similar code to update the roster UI with the changing information. 
+
+<br clear="all">
+
+<pre>
+final Roster roster = con.getRoster();
+roster.addRosterListener(new RosterListener() {
+    <b>public void</b> rosterModified() {
+        <font color="gray"><i>// Ignore event for this example.</i></font>
+    }
+
+    <b>public void</b> presenceChanged(String user) {
+        <font color="gray"><i>// If the presence is unavailable then "null" will be printed,
+        // which is fine for this example.</i></font>
+        System.out.println(<font color="green">"Presence changed: "</font> + roster.getPresence(user));
+    }
+});
+</pre> 
+
+<p class="subheader">Adding Entries to the Roster</p>
+
+<p>Rosters and presence use a permissions-based model where users must give permission before
+they are added to someone else's roster. This protects a user's privacy by
+making sure that only approved users are able to view their presence information.
+Therefore, when you add a new roster entry it will be in a pending state until
+the other user accepts your request.</p>
+
+If another user requests a presence subscription so they can add you to their roster,
+you must accept or reject that request. Smack handles presence subscription requests
+in one of three ways: <ul>
+
+    <li> Automatically accept all presence subscription requests.
+    <li> Automatically reject all presence subscription requests.
+    <li> Process presence subscription requests manually.
+</ul>
+
+The mode can be set using the <tt>Roster.setSubscriptionMode(int subscriptionMode)</tt>
+method. Simple clients normally use one of the automated subscription modes, while
+full-featured clients should manually process subscription requests and let the
+end-user accept or reject each request. If using the manual mode, a PacketListener 
+should be registered that listens for Presence packets that have a type of 
+<tt>Presence.Type.SUBSCRIBE</tt>. 
+
+<br clear="all" /><br><br>
+
+<div class="footer">
+Copyright &copy; Jive Software 2002-2004
+</div>
+
+</body>
+</html>
diff --git a/CopyOftrunk/documentation/style.css b/CopyOftrunk/documentation/style.css
new file mode 100644
index 000000000..082da56a1
--- /dev/null
+++ b/CopyOftrunk/documentation/style.css
@@ -0,0 +1,56 @@
+BODY {
+    font-size : 100%;
+    background-color : #fff;
+}
+BODY, TD, TH {
+    font-family : tahoma, arial, helvetica;
+    font-size : 0.8em;
+}
+PRE, TT, CODE {
+    font-family : courier new, monospaced;
+    font-size : 1.0em;
+}
+A:hover {
+    text-decoration : none;
+}
+LI {
+    padding-bottom : 4px;
+}
+.header {
+    font-size : 1.4em;
+    font-weight : bold;
+    width : 100%;
+    border-bottom : 1px #ccc solid;
+    padding-bottom : 2px;
+}
+.subheader {
+    font-weight : bold;
+}
+.footer {
+    font-size : 0.8em;
+    color : #999;
+    text-align : center;
+    width : 100%;
+    border-top : 1px #ccc solid;
+    padding-top : 2px;
+}
+.code {
+    border : 1px #ccc solid;
+    padding : 0em 1.0em 0em 1.0em;
+    margin : 4px 0px 4px 0px;
+}
+.nav, .nav A {
+    font-family : verdana;
+    font-size : 0.85em;
+    color : #600;
+    text-decoration : none;
+    font-weight : bold;
+}
+.nav {
+    width : 100%;
+    border-bottom : 1px #ccc solid;
+    padding : 3px 3px 5px 1px;
+}
+.nav A:hover {
+    text-decoration : underline;
+}
\ No newline at end of file
diff --git a/CopyOftrunk/sample/conf/WEB-INF/web.xml b/CopyOftrunk/sample/conf/WEB-INF/web.xml
new file mode 100644
index 000000000..1e3ac64bb
--- /dev/null
+++ b/CopyOftrunk/sample/conf/WEB-INF/web.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!-- Servlet 2.3 is required for Jive Forums 3 -->
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+    
+<web-app>
+    <display-name>Smack Client UI</display-name>
+    <description>Smack sample UI</description>
+
+    <!-- Welcome file list -->
+    <welcome-file-list>
+        <welcome-file>login.jsp</welcome-file>
+    </welcome-file-list>
+    
+</web-app>
diff --git a/CopyOftrunk/sample/web/addContact.jsp b/CopyOftrunk/sample/web/addContact.jsp
new file mode 100644
index 000000000..0874eec1b
--- /dev/null
+++ b/CopyOftrunk/sample/web/addContact.jsp
@@ -0,0 +1,138 @@
+<%--
+  - $RCSfile$
+  - $Revision$
+  - $Date$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<%@ page import="java.util.*,
+                org.jivesoftware.smack.*,
+                org.jivesoftware.smack.packet.*,
+                org.jivesoftware.smack.util.*"%>
+<%@ include file="global.jsp" %>
+<%
+    // If we don't have a valid connection then proceed to login
+    XMPPConnection conn = (XMPPConnection) session.getAttribute("connection");
+    if (conn == null || !conn.isConnected()) {
+        response.sendRedirect("login.jsp");
+        return;
+    }
+    Roster roster = conn.getRoster();
+
+    // Get parameters
+    String user = getParameter(request, "user");
+    String nickname = getParameter(request, "nickname");
+    String group1 = getParameter(request, "group1");
+    String group2 = getParameter(request, "group2");
+
+    // Create a new entry in the roster that belongs to a certain groups
+    if (user != null) {
+        roster.createEntry(user, nickname, new String[] {group1, group2});
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+%>
+<html>
+<head>
+<title>Add Contact</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+<link href="css/general.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+<table width="100%" border="0">
+  <tr> 
+    <td><span id="bigBlack">Add contact to roster</span></td>
+    <td align="right"><a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a></td>
+  </tr>
+  <tr> 
+    <td colspan="2">&nbsp;</td>
+  </tr>
+  <tr> 
+    <td colspan="2"><table width="100%" border="0">
+        <tr> 
+          <td width="20%">&nbsp;</td>
+          <td width="60%"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+              <tr bgcolor="#AAAAAA"> 
+                <td class=text id=bigWhite height=16 align="center"> <b>Contact 
+                  Information</b> </td>
+              </tr>
+              <tr> 
+                <td align="center"> <table width="100%" border="0">
+                    <form method="post" action="addContact.jsp">
+                      <tr> 
+                        <td class=text id=black height=16>Username:</td>
+                        <td><input type="text" name="user"> &nbsp;<span class=text id=black height=16>(e.g. 
+                          johndoe@jabber.org)</span></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Nickname:</td>
+                        <td><input type="text" name="nickname"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Group 1:</td>
+                        <td><input type="text" name="group1" value="<%= (group1!=null)?group1:"" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Group 2:</td>
+                        <td><input type="text" name="group2"></td>
+                      </tr>
+                      <tr> 
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</td>
+                      </tr>
+                      <tr> 
+                        <td colspan=2 align="center"> <input type="submit" value="Add Contact"></td>
+                      </tr>
+                    </form>
+                  </table></td>
+              </tr>
+            </table></td>
+          <td width="20%">&nbsp;</td>
+        </tr>
+      </table></td>
+  </tr>
+</table>
+</body>
+</html>
diff --git a/CopyOftrunk/sample/web/chat.jsp b/CopyOftrunk/sample/web/chat.jsp
new file mode 100644
index 000000000..db7ada888
--- /dev/null
+++ b/CopyOftrunk/sample/web/chat.jsp
@@ -0,0 +1,60 @@
+<%--
+  - $$RCSfile$$
+  - $$Revision$$
+  - $$Date$$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<html>
+<head>
+<title>Chat with contact</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+<link href="css/general.css" rel="stylesheet" type="text/css">
+</head>
+
+<body>
+Not implemented yet! <a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a>
+</body>
+</html>
diff --git a/CopyOftrunk/sample/web/css/general.css b/CopyOftrunk/sample/web/css/general.css
new file mode 100644
index 000000000..f25a4b5f0
--- /dev/null
+++ b/CopyOftrunk/sample/web/css/general.css
@@ -0,0 +1,36 @@
+BODY
+{
+	FONT-FAMILY: Verdana, Geneva, Arial, Helvetica, sans-serif;
+	FONT-SIZE: 10px;
+	margin-left: 0px;
+	margin-right: 0px;
+	margin-bottom: 0px;
+	margin-top: 0px;
+	background-color: #FFFFFF;
+}
+.text {
+ FONT-FAMILY: Verdana, Geneva, Arial, Helvetica, sans-serif;
+ FONT-SIZE: 10px;
+}
+input,textarea,select
+{
+ font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+}
+#black {
+ color: #000000;
+ font-weight: BOLD;
+}
+#white {
+ color: FFFFFF;
+ font-weight: BOLD;
+}
+#bigWhite {
+ color: FFFFFF;
+ font-weight: BOLD;
+ font-size: 14px;
+}
+#bigBlack {
+ font-weight: BOLD;
+ font-size: 18px;
+}
\ No newline at end of file
diff --git a/CopyOftrunk/sample/web/global.jsp b/CopyOftrunk/sample/web/global.jsp
new file mode 100644
index 000000000..202cc659c
--- /dev/null
+++ b/CopyOftrunk/sample/web/global.jsp
@@ -0,0 +1,66 @@
+<%--
+  - $$RCSfile$$
+  - $$Revision$$
+  - $$Date$$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<%!
+    public static String getParameter(HttpServletRequest request, String name)
+    {
+        String temp = request.getParameter(name);
+        if (temp != null) {
+            if (temp.equals("")) {
+                return null;
+            }
+            else {
+                return temp;
+            }
+        }
+        else {
+            return null;
+        }
+    }
+%>
\ No newline at end of file
diff --git a/CopyOftrunk/sample/web/images/address_book.png b/CopyOftrunk/sample/web/images/address_book.png
new file mode 100644
index 000000000..8519aeef1
Binary files /dev/null and b/CopyOftrunk/sample/web/images/address_book.png differ
diff --git a/CopyOftrunk/sample/web/images/businessman_add.png b/CopyOftrunk/sample/web/images/businessman_add.png
new file mode 100644
index 000000000..066cf368c
Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessman_add.png differ
diff --git a/CopyOftrunk/sample/web/images/businessman_delete.png b/CopyOftrunk/sample/web/images/businessman_delete.png
new file mode 100644
index 000000000..cd7403a74
Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessman_delete.png differ
diff --git a/CopyOftrunk/sample/web/images/businessmen.png b/CopyOftrunk/sample/web/images/businessmen.png
new file mode 100644
index 000000000..c66133959
Binary files /dev/null and b/CopyOftrunk/sample/web/images/businessmen.png differ
diff --git a/CopyOftrunk/sample/web/images/garbage.png b/CopyOftrunk/sample/web/images/garbage.png
new file mode 100644
index 000000000..deaacacae
Binary files /dev/null and b/CopyOftrunk/sample/web/images/garbage.png differ
diff --git a/CopyOftrunk/sample/web/images/messages.png b/CopyOftrunk/sample/web/images/messages.png
new file mode 100644
index 000000000..6dab998e8
Binary files /dev/null and b/CopyOftrunk/sample/web/images/messages.png differ
diff --git a/CopyOftrunk/sample/web/images/nav_right_blue.png b/CopyOftrunk/sample/web/images/nav_right_blue.png
new file mode 100644
index 000000000..1612579ec
Binary files /dev/null and b/CopyOftrunk/sample/web/images/nav_right_blue.png differ
diff --git a/CopyOftrunk/sample/web/images/plug_delete.png b/CopyOftrunk/sample/web/images/plug_delete.png
new file mode 100644
index 000000000..a3b9533fa
Binary files /dev/null and b/CopyOftrunk/sample/web/images/plug_delete.png differ
diff --git a/CopyOftrunk/sample/web/images/refresh.png b/CopyOftrunk/sample/web/images/refresh.png
new file mode 100644
index 000000000..aaf94aefe
Binary files /dev/null and b/CopyOftrunk/sample/web/images/refresh.png differ
diff --git a/CopyOftrunk/sample/web/index.jsp b/CopyOftrunk/sample/web/index.jsp
new file mode 100644
index 000000000..9aa774326
--- /dev/null
+++ b/CopyOftrunk/sample/web/index.jsp
@@ -0,0 +1,53 @@
+<%--
+  - $$RCSfile$$
+  - $$Revision$$
+  - $$Date$$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+
+<%  response.sendRedirect("login.jsp");
+    return;
+%>
\ No newline at end of file
diff --git a/CopyOftrunk/sample/web/login.jsp b/CopyOftrunk/sample/web/login.jsp
new file mode 100644
index 000000000..fdaa79aa3
--- /dev/null
+++ b/CopyOftrunk/sample/web/login.jsp
@@ -0,0 +1,190 @@
+<%--
+  - $RCSfile$
+  - $Revision$
+  - $Date$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<%@ page import="org.jivesoftware.smack.*"%>
+<%@ include file="global.jsp" %>
+<%
+    // If we already got a connection then proceed to view the roster
+    XMPPConnection conn = (XMPPConnection) session.getAttribute("connection");
+    if (conn != null && conn.isConnected()) {
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+
+    // Get parameters
+    String host        = getParameter(request, "host");
+    String port        = getParameter(request, "port");
+    String ssl         = getParameter(request, "ssl");
+    String debug       = getParameter(request, "debug");
+    String username    = getParameter(request, "username");
+    String password    = getParameter(request, "password");
+    String resource    = getParameter(request, "resource");
+    String error       = getParameter(request, "error");
+
+    // Try to connect to the server
+    if (error == null && host != null && port != null) {
+        XMPPConnection.DEBUG_ENABLED = "Yes".equals(debug);
+        try {
+            if ("No".equals(ssl)) {
+                conn = new XMPPConnection(host, Integer.parseInt(port));
+            }
+            else {
+                conn = new SSLXMPPConnection(host, Integer.parseInt(port));
+            }
+            // Add listener for messages (offline messages will be listen here)
+            
+            // Set the roster subscription mode to use 
+            
+            // Login to the server
+            conn.login(username, password, resource);
+            session.setAttribute("connection", conn);
+        }
+        catch (Exception e) {
+            error = e.getMessage();
+            // Replace any char : because otherwise the URL will get corrupted
+            error = error.replace(':', '-');
+            response.sendRedirect("login.jsp?host="+host+"&port="+port+"&ssl="+ssl+"&error="+error);
+            return;
+        }
+        // Redirect to the next page
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+%>
+<html>
+<head>
+<title>Login</title>
+<link href="css/general.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+<table width="100%" border="0">
+  <tr> 
+    <td><span id="bigBlack">Smack Demo Application</span></td>
+  </tr>
+  <tr> 
+    <td align="center"> 
+      <% if (error != null) { %>
+      <p><font color="#FF0000"><%= error %></font></p>
+      <%} else {%>
+      &nbsp; 
+      <%}%>
+    </td>
+  </tr>
+  <tr> 
+    <td><table width="100%" border="0">
+        <tr> 
+          <td width="20%">&nbsp;</td>
+          <td width="60%"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+              <tr bgcolor="#AAAAAA"> 
+                <td class=text id=bigWhite height=16 align="center"> <b>Login 
+                  Information</b> </td>
+              </tr>
+              <tr> 
+                <td> <table width=100% border=0>
+                    <form action="login.jsp" method="post">
+                      <tr> 
+                        <td class=text id=black height=16>Host:</td>
+                        <td><input type="text" name="host" size="30" maxlength="50" value="<%= (host!=null)?host:"" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Port:</td>
+                        <td><input type="text" name="port" size="5" maxlength="10" value="<%= (port!=null)?port:"5222" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Use SSL:</td>
+                        <td><select size="1" name="ssl">
+                            <option <%= ("No".equals(ssl))?"selected":""%>>No</option>
+                            <option <%= ("Yes".equals(ssl))?"selected":""%>>Yes</option>
+                          </select></td>
+                      </tr>
+                      <tr> 
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Debug Connection:</td>
+                        <td><select size="1" name="debug">
+                            <option <%= ("No".equals(debug))?"selected":""%>>No</option>
+                            <option <%= ("Yes".equals(debug))?"selected":""%>>Yes</option>
+                          </select></td>
+                      </tr>
+                      <tr> 
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Username:</td>
+                        <td><input type="text" name="username" size="30" maxlength="50" value="<%= (username!=null)?username:"" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Password:</td>
+                        <td><input type="password" name="password" size="30" maxlength="50" value="<%= (password!=null)?password:"" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td class=text id=black height=16>Resource:</td>
+                        <td><input type="text" name="resource" size="30" maxlength="50" value="<%= (resource!=null)?resource:"" %>"></td>
+                      </tr>
+                      <tr> 
+                        <td>&nbsp;</td>
+                        <td>&nbsp;</td>
+                      </tr>
+                      <tr align="center"> 
+                        <td colspan="2"> <input type="submit" value="Login"> </td>
+                      </tr>
+                    </form>
+                  </table></td>
+              </tr>
+            </table></td>
+          <td width="20%">&nbsp;</td>
+        </tr>
+      </table></td>
+  </tr>
+</table>
+</body>
+</html>
diff --git a/CopyOftrunk/sample/web/moveContact.jsp b/CopyOftrunk/sample/web/moveContact.jsp
new file mode 100644
index 000000000..1b8924e88
--- /dev/null
+++ b/CopyOftrunk/sample/web/moveContact.jsp
@@ -0,0 +1,208 @@
+<%--
+  - $RCSfile$
+  - $Revision$
+  - $Date$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<%@ page import="java.util.*,
+                org.jivesoftware.smack.*,
+                org.jivesoftware.smack.packet.*,
+                org.jivesoftware.smack.util.*"%>
+<%@ include file="global.jsp" %>
+<%
+    // If we don't have a valid connection then proceed to login
+    XMPPConnection conn = (XMPPConnection) session.getAttribute("connection");
+    if (conn == null || !conn.isConnected()) {
+        response.sendRedirect("login.jsp");
+        return;
+    }
+    Roster roster = conn.getRoster();
+
+    // Get parameters
+    String action = getParameter(request, "action");
+    String user = getParameter(request, "user");
+    String fromGroup = getParameter(request, "fromGroup");
+    String toGroup = getParameter(request, "toGroup");
+
+    // Move the entry from the existing group to a new group
+    if ("move".equals(action)) {
+        RosterEntry entry = roster.getEntry(user);
+        // Remove the entry from the existing group
+        RosterGroup rosterGroup = roster.getGroup(fromGroup);
+        rosterGroup.removeEntry(entry);
+        // Get the new group or create it if it doesn't exist
+        rosterGroup = roster.getGroup(toGroup);
+        if (rosterGroup == null) {
+            rosterGroup = roster.createGroup(toGroup);
+        }
+        // Add the new entry to the group
+        rosterGroup.addEntry(entry);
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+
+    // Add the entry to a new group
+    if ("add".equals(action)) {
+        RosterEntry entry = roster.getEntry(user);
+        // Get the new group or create it if it doesn't exist
+        RosterGroup rosterGroup = roster.getGroup(toGroup);
+        if (rosterGroup == null) {
+            rosterGroup = roster.createGroup(toGroup);
+        }
+        // Add the new entry to the group
+        rosterGroup.addEntry(entry);
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+
+    // Delete the entry from a group
+    if ("delete".equals(action)) {
+        RosterEntry entry = roster.getEntry(user);
+        RosterGroup rosterGroup = roster.getGroup(fromGroup);
+        rosterGroup.removeEntry(entry);
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+%>
+<html>
+<head>
+<title>Groups Management</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+<link href="css/general.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+<table width="100%" border="0">
+  <tr> 
+    <td><span id="bigBlack">Groups for entry: <%= user%></span></td>
+    <td align="right"><a href="viewRoster.jsp"><img src="images/address_book.png" alt="View roster" border="0"></a></td>
+  </tr>
+  <tr> 
+    <td colspan="2">&nbsp;</td>
+  </tr><tr><td colspan="2">
+  <table width="100%" border="0"><tr>
+    <td valign="top" width="48%"><table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+        <tr bgcolor="#AAAAAA"> 
+          <td class=text id=bigWhite height=16 align="center"> <b>Add to new group</b> 
+          </td>
+        </tr>
+        <tr> 
+          <td> <table width="100%" border="0">
+              <form method="post" action="moveContact.jsp">
+                <input type="hidden" name="action" value="add">
+                <input type="hidden" name="user" value="<%= user%>">
+                <tr> 
+                  <td width="40%" height=16 align="center" class=text>Group:</td>
+                  <td width="20%">&nbsp;</td>
+                  <td width="40%" align="center"><input type="text" name="toGroup"></td>
+                </tr>
+                <tr> 
+                  <td colspan=3 align="center"> <input type="submit" value="Add"></td>
+                </tr>
+              </form>
+            </table></td>
+        </tr>
+      </table></td>
+    <td width="4%">&nbsp;</td><td valign="top" width="48%">
+    <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+      <tr bgcolor="#AAAAAA"> 
+        <td colspan="3" class=text id=bigWhite height=16 align="center"> <b>Move 
+          to new group</b> </td>
+      </tr>
+      <tr vAlign=center align=middle bgcolor="#AAAAAA"> 
+        <td align="center" width="40%" class=text id=white height=16>From</td>
+        <td width="20%">&nbsp;</td>
+        <td align="center" width="40%" class=text id=white height=16>To</td>
+      </tr>
+      <% if (fromGroup != null) { %>
+      <form method="post" action="moveContact.jsp">
+        <input type="hidden" name="action" value="move">
+        <input type="hidden" name="user" value="<%= user%>">
+        <input type="hidden" name="fromGroup" value="<%= fromGroup%>">
+        <tr> 
+          <td colspan="3"> <table width="100%" border="0">
+              <tr> 
+                <td width="40%" align="center" valign="middle" class=text height=16><%=fromGroup%></td>
+                <td width="20%" align="center" valign="middle"><img src="images/nav_right_blue.png" alt="Move contact to group" border="0"></td>
+                <td width="40%" align="center" valign="middle"> <input type="text" name="toGroup"> 
+              </tr>
+              <tr> 
+                <td colspan="3" align="center"> <input type="submit" value="Move"> 
+              </tr>
+            </table></td>
+        </tr>
+      </form>
+      <%  } else { %>
+      <tr> 
+        <td colspan="3" align="center" class=text id=black height=16>Entry does 
+          not belong to a group (unfiled entry) </td>
+      </tr>
+      <%  } %>
+    </table></td></tr>
+    <tr> 
+      <td colspan="3">&nbsp;</td>
+    </tr>
+    <tr> 
+      <td colspan="3" align="center"> <table cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=50% align=center borderColorLight=#000000 border=1>
+          <tr bgcolor="#AAAAAA"> 
+            <td colspan="2" class=text id=bigWhite height=16 align="center"> <b>Remove 
+              from groups</b> </td>
+          </tr>
+          <% 
+                    RosterEntry entry = roster.getEntry(user);
+                    RosterGroup group;
+                    for (Iterator it=entry.getGroups(); it.hasNext();) { 
+                        group = (RosterGroup) it.next();%>
+          <tr> 
+            <td align="center" width="80%" class=text height=16><%=group.getName()%></td>
+            <td valign="middle" align="center" width="20%"><a href="moveContact.jsp?action=delete&user=<%=user%>&fromGroup=<%=group.getName()%>"><img src="images/garbage.png" alt="Remove contact from the group" border="0"></a></td>
+          </tr>
+          <% } %>
+        </table></td>
+    </tr>
+  </table></td></tr>
+</table>
+</body>
+</html>
diff --git a/CopyOftrunk/sample/web/viewRoster.jsp b/CopyOftrunk/sample/web/viewRoster.jsp
new file mode 100644
index 000000000..7816211a6
--- /dev/null
+++ b/CopyOftrunk/sample/web/viewRoster.jsp
@@ -0,0 +1,230 @@
+<%--
+  - $$RCSfile$$
+  - $$Revision$$
+  - $$Date$$
+  -
+  - Copyright (C) 2002-2003 Jive Software. All rights reserved.
+  - 
+  - The Jive Software License (based on Apache Software License, Version 1.1)
+  - Redistribution and use in source and binary forms, with or without
+  - modification, are permitted provided that the following conditions
+  - are met:
+  -
+  - 1. Redistributions of source code must retain the above copyright
+  -    notice, this list of conditions and the following disclaimer.
+  -
+  - 2. Redistributions in binary form must reproduce the above copyright
+  -    notice, this list of conditions and the following disclaimer in
+  -    the documentation and/or other materials provided with the
+  -    distribution.
+  -
+  - 3. The end-user documentation included with the redistribution,
+  -    if any, must include the following acknowledgment:
+  -       "This product includes software developed by
+  -        Jive Software (http://www.jivesoftware.com)."
+  -    Alternately, this acknowledgment may appear in the software itself,
+  -    if and wherever such third-party acknowledgments normally appear.
+  -
+  - 4. The names "Smack" and "Jive Software" must not be used to
+  -    endorse or promote products derived from this software without
+  -    prior written permission. For written permission, please
+  -    contact webmaster@jivesoftware.com.
+  -
+  - 5. Products derived from this software may not be called "Smack",
+  -    nor may "Smack" appear in their name, without prior written
+  -    permission of Jive Software.
+  -
+  - THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+  - WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+  - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+  - DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+  - ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+  - USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+  - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+  - OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+  - SUCH DAMAGE.
+ --%>
+<%@ page import="java.util.*,
+                org.jivesoftware.smack.*,
+                org.jivesoftware.smack.packet.*,
+                org.jivesoftware.smack.util.*"%>
+<%@ include file="global.jsp" %>
+<%
+    // If we don't have a valid connection then proceed to login
+    XMPPConnection conn = (XMPPConnection) session.getAttribute("connection");
+    if (conn == null || !conn.isConnected()) {
+        response.sendRedirect("login.jsp");
+        return;
+    }
+    Roster roster = conn.getRoster();
+
+    // Get parameters
+    String action = getParameter(request, "action");
+    String user = getParameter(request, "user");
+    String groupName = getParameter(request, "group");
+
+    // Remove the selected user from the roster (and all the groups)
+    if ("rosterDelete".equals(action)) {
+        RosterEntry entry = roster.getEntry(user);
+        roster.removeEntry(entry);
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+
+    // Remove the selected user from the selected group
+    if ("groupDelete".equals(action)) {
+        RosterEntry entry = roster.getEntry(user);
+        RosterGroup rosterGroup = roster.getGroup(groupName);
+        rosterGroup.removeEntry(entry);
+        response.sendRedirect("viewRoster.jsp");
+        return;
+    }
+
+    // Close the connection to the XMPP server
+    if ("close".equals(action)) {
+        conn.close();
+        session.invalidate();
+        response.sendRedirect("login.jsp");
+        return;
+    }
+%>
+<html>
+<head>
+<title>Viewing roster</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+<link href="css/general.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+<table width="100%" border="0">
+  <tr> 
+    <td width="50%"><span id="bigBlack"><%= StringUtils.parseName(conn.getUser())%>'s roster</span></td>
+    <td width="50%"><table width="100%" border="0">
+        <tr> 
+          <td>&nbsp;</td>
+          <td width="24"><a href="viewRoster.jsp"><img src="images/refresh.png" alt="Refresh roster" border="0"></a></td>
+          <td width="24"><a href="viewRoster.jsp?action=close"><img src="images/plug_delete.png" alt="Close connection" border="0"></a></td>
+        </tr>
+      </table></td>
+  </tr>
+  <tr> 
+    <td colspan="2"><table width="100%" border="0">
+        <tr> 
+          <td width="49%" valign="top"> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+              <tr bgcolor="#AAAAAA"> 
+                <td class=text id=bigWhite height=16 align="center"> <b>Roster entries</b> </td>
+              </tr>
+              <tr> 
+                <td> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+                    <% for (Iterator groups = roster.getGroups(); groups.hasNext();) {
+                    RosterGroup group = (RosterGroup)groups.next();%>
+                    <tr> 
+                      <td bgcolor="#AAAAAA" colspan="6" class=text id=white height=16>Group: 
+                        <%= group.getName()%></td>
+                    </tr>
+                    <TR vAlign=center align=middle bgcolor="#AAAAAA"> 
+                      <TD class=text id=white height=16>User</TD>
+                      <TD class=text id=white height=16>Name</TD>
+                      <TD class=text id=white height=16>Subscription</TD>
+                      <TD colspan="3" class=text id=white height=16><a href="addContact.jsp?group1=<%=group.getName()%>"><img src="images/businessman_add.png" alt="Add contact to group" border="0"></a></TD>
+                    </TR>
+                    <% for (Iterator it = group.getEntries(); it.hasNext();) { 
+                        RosterEntry entry = (RosterEntry)it.next();%>
+                    <TR vAlign=center align=middle bgColor=#ffffff> 
+                      <TD class=text height=16><%= entry.getUser()%></TD>
+                      <TD class=text height=16><%= entry.getName()%></TD>
+                      <TD class=text height=16><%= entry.getType()%></TD>
+                      <TD class=text height=16><a href="moveContact.jsp?user=<%=entry.getUser()%>&fromGroup=<%=group.getName()%>"><img src="images/businessmen.png" alt="Groups management" border="0"></a></TD>
+                      <TD class=text height=16><a href="viewRoster.jsp?action=groupDelete&user=<%=entry.getUser()%>&group=<%=group.getName()%>"><img src="images/businessman_delete.png" alt="Remove contact from the group" border="0"></a></TD>
+                      <TD class=text height=16><a href="viewRoster.jsp?action=rosterDelete&user=<%=entry.getUser()%>"><img src="images/garbage.png" alt="Remove contact from the roster" border="0"></a></TD>
+                    </TR>
+                    <% }%>
+                    <% }%>
+                    <tr> 
+                      <td bgcolor="#AAAAAA" colspan="6" class=text id=white height=16>Unfiled 
+                        entries</td>
+                    </tr>
+                    <TR vAlign=center align=middle bgcolor="#AAAAAA"> 
+                      <TD class=text id=white height=16>User</TD>
+                      <TD class=text id=white height=16>Name</TD>
+                      <TD class=text id=white height=16>Subscription</TD>
+                      <TD colspan="3" class=text id=white height=16><a href="addContact.jsp"><img src="images/businessman_add.png" alt="Add contact" border="0"></a></TD>
+                    </TR>
+                    <% for (Iterator it = roster.getUnfiledEntries(); it.hasNext();) { 
+                RosterEntry entry = (RosterEntry)it.next();%>
+                    <tr vAlign=center align=middle bgColor=#ffffff> 
+                      <td class=text height=16><%= entry.getUser()%></td>
+                      <td class=text height=16><%= entry.getName()%></td>
+                      <td class=text height=16><%= entry.getType()%></td>
+                      <TD class=text height=16><a href="moveContact.jsp?user=<%=entry.getUser()%>"><img src="images/businessmen.png" alt="Groups management" border="0"></a></td>
+                      <TD class=text height=16>&nbsp;</TD>
+                      <TD class=text height=16><a href="viewRoster.jsp?action=rosterDelete&user=<%=entry.getUser()%>"><img src="images/garbage.png" alt="Remove contact from roster" border="0"></a></td>
+                    </tr>
+                    <% }%>
+                  </table></td>
+              </tr>
+            </table></td>
+          <td width="2%" valign="top">&nbsp;</td>
+          <td width="49%" valign="top"> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+              <tr bgcolor="#AAAAAA"> 
+                <td class=text id=bigWhite height=16 align="center"> <b>Presences</b> 
+                </td>
+              </tr>
+              <tr> 
+                <td> <TABLE cellSpacing=0 borderColorDark=#E0E0E0 cellPadding=0 width=100% align=center borderColorLight=#000000 border=1>
+                    <tr> 
+                      <td bgcolor="#AAAAAA" colspan="5" class=text id=white height=16>Roster�s presences</td>
+                    </tr>
+                    <TR vAlign=center align=middle bgcolor="#AAAAAA"> 
+                      <TD class=text id=white height=16>User</TD>
+                      <TD class=text id=white height=16>Mode</TD>
+                      <TD class=text id=white height=16>Type</TD>
+                      <TD class=text id=white height=16>&nbsp;</TD>
+                    </TR>
+                    <% for (Iterator entries = roster.getEntries(); entries.hasNext();) {
+                    RosterEntry entry = (RosterEntry)entries.next();
+                    Iterator presences = roster.getPresences(entry.getUser());
+                    if (presences != null) {
+                        while (presences.hasNext()) {
+                            Presence presence = (Presence)presences.next(); %>
+                    <TR vAlign=center align=middle bgColor=#ffffff> 
+                      <TD class=text height=16><%= presence.getFrom()%></TD>
+                      <TD class=text height=16><%= presence.getMode()%></TD>
+                      <TD class=text height=16><%= presence.getType()%></TD>
+                      <TD class=text height=16><a href="chat.jsp?user=<%=presence.getFrom()%>"><img src="images/messages.png" alt="Chat" border="0"></a></a></TD>
+                    </TR>
+                    <% }%>
+                    <% }%>
+                    <% }%>
+                    <tr> 
+                      <td bgcolor="#AAAAAA" colspan="5" class=text id=white height=16>My other resources</td>
+                    </tr>
+                    <TR vAlign=center align=middle bgcolor="#AAAAAA"> 
+                      <TD class=text id=white height=16>User</TD>
+                      <TD class=text id=white height=16>Mode</TD>
+                      <TD class=text id=white height=16>Type</TD>
+                      <TD class=text id=white height=16>&nbsp;</TD>
+                    </TR>
+                    <%  // Show other presences of the current user
+                Iterator presences = roster.getPresences(conn.getUser());
+                if (presences != null) {
+                    while (presences.hasNext()) {
+                        Presence presence = (Presence)presences.next(); %>
+                    <tr vAlign=center align=middle bgColor=#ffffff> 
+                      <TD class=text height=16><%= presence.getFrom()%></TD>
+                      <TD class=text height=16><%= presence.getMode()%></TD>
+                      <TD class=text height=16><%= presence.getType()%></TD>
+                      <TD class=text height=16><a href="chat.jsp?user=<%=presence.getFrom()%>"><img src="images/messages.png" alt="Chat" border="0"></a></a></TD>
+                    </tr>
+                    <% }%>
+                    <% }%>
+                  </table></td>
+              </tr>
+            </table></tr>
+      </table></td>
+  </tr>
+</table>
+</body>
+</html>
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java b/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java
new file mode 100644
index 000000000..dbc0d6eaf
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/AccountManager.java
@@ -0,0 +1,298 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Registration;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.*;
+
+/**
+ * Allows creation and management of accounts on an XMPP server.
+ *
+ * @see XMPPConnection#getAccountManager()
+ * @author Matt Tucker
+ */
+public class AccountManager {
+
+    private XMPPConnection connection;
+    private Registration info = null;
+
+    /**
+     * Creates a new AccountManager instance.
+     *
+     * @param connection a connection to a XMPP server.
+     */
+    public AccountManager(XMPPConnection connection) {
+        this.connection = connection;
+    }
+
+    /**
+     * Returns true if the server supports creating new accounts. Many servers require
+     * that you not be currently authenticated when creating new accounts, so the safest
+     * behavior is to only create new accounts before having logged in to a server.
+     *
+     * @return true if the server support creating new accounts.
+     */
+    public boolean supportsAccountCreation() {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            return info.getType() != IQ.Type.ERROR;
+        }
+        catch (XMPPException xe) {
+            return false;
+        }
+    }
+
+    /**
+     * Returns an Iterator for the (String) names of the required account attributes.
+     * All attributes must be set when creating new accounts. The standard
+     * attributes are as follows: <ul>
+     *      <li>name -- the user's name.
+     *      <li>first -- the user's first name.
+     *      <li>last -- the user's last name.
+     *      <li>email -- the user's email address.
+     *      <li>city -- the user's city.
+     *      <li>state -- the user's state.
+     *      <li>zip -- the user's ZIP code.
+     *      <li>phone -- the user's phone number.
+     *      <li>url -- the user's website.
+     *      <li>date -- the date the registration took place.
+     *      <li>misc -- other miscellaneous information to associate with the account.
+     *      <li>text -- textual information to associate with the account.
+     *      <li>remove -- empty flag to remove account.
+     * </ul><p>
+     *
+     * Typically, servers require no attributes when creating new accounts, or just
+     * the user's email address.
+     *
+     * @return the required account attributes.
+     */
+    public Iterator getAccountAttributes() {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            Map attributes = info.getAttributes();
+            if (attributes != null) {
+                return attributes.keySet().iterator();
+            }
+        }
+        catch (XMPPException xe) { }
+        return Collections.EMPTY_LIST.iterator();
+    }
+
+    /**
+     * Returns the value of a given account attribute or <tt>null</tt> if the account
+     * attribute wasn't found.
+     *
+     * @param name the name of the account attribute to return its value.
+     * @return the value of the account attribute or <tt>null</tt> if an account
+     * attribute wasn't found for the requested name.
+     */
+    public String getAccountAttribute(String name) {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            return (String) info.getAttributes().get(name);
+        }
+        catch (XMPPException xe) { }
+        return null;
+    }
+
+    /**
+     * Returns the instructions for creating a new account, or <tt>null</tt> if there
+     * are no instructions. If present, instructions should be displayed to the end-user
+     * that will complete the registration process.
+     *
+     * @return the account creation instructions, or <tt>null</tt> if there are none.
+     */
+    public String getAccountInstructions() {
+        try {
+            if (info == null) {
+                getRegistrationInfo();
+            }
+            return info.getInstructions();
+        }
+        catch (XMPPException xe) {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a new account using the specified username and password. The server may
+     * require a number of extra account attributes such as an email address and phone
+     * number. In that case, Smack will attempt to automatically set all required
+     * attributes with blank values, which may or may not be accepted by the server.
+     * Therefore, it's recommended to check the required account attributes and to let
+     * the end-user populate them with real values instead.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @throws XMPPException if an error occurs creating the account.
+     */
+    public void createAccount(String username, String password) throws XMPPException {
+        if (!supportsAccountCreation()) {
+            throw new XMPPException("Server does not support account creation.");
+        }
+        // Create a map for all the required attributes, but give them blank values.
+        Map attributes = new HashMap();
+        for (Iterator i=getAccountAttributes(); i.hasNext(); ) {
+            String attributeName = (String)i.next();
+            attributes.put(attributeName, "");
+        }
+        createAccount(username, password, attributes);
+    }
+
+    /**
+     * Creates a new account using the specified username, password and account attributes.
+     * The attributes Map must contain only String name/value pairs and must also have values
+     * for all required attributes.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @param attributes the account attributes.
+     * @throws XMPPException if an error occurs creating the account.
+     * @see #getAccountAttributes()
+     */
+    public void createAccount(String username, String password, Map attributes)
+            throws XMPPException
+    {
+        if (!supportsAccountCreation()) {
+            throw new XMPPException("Server does not support account creation.");
+        }
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getHost());
+        attributes.put("username",username);
+        attributes.put("password",password);
+        reg.setAttributes(attributes);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Changes the password of the currently logged-in account. This operation can only
+     * be performed after a successful login operation has been completed. Not all servers
+     * support changing passwords; an XMPPException will be thrown when that is the case.
+     *
+     * @throws IllegalStateException if not currently logged-in to the server.
+     * @throws XMPPException if an error occurs when changing the password.
+     */
+    public void changePassword(String newPassword) throws XMPPException {
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getHost());
+        HashMap map = new HashMap();
+        map.put("username",StringUtils.parseName(connection.getUser()));
+        map.put("password",newPassword);
+        reg.setAttributes(map);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Deletes the currently logged-in account from the server. This operation can only
+     * be performed after a successful login operation has been completed. Not all servers
+     * support deleting accounts; an XMPPException will be thrown when that is the case.
+     *
+     * @throws IllegalStateException if not currently logged-in to the server.
+     * @throws XMPPException if an error occurs when deleting the account.
+     */
+    public void deleteAccount() throws XMPPException {
+        if (!connection.isAuthenticated()) {
+            throw new IllegalStateException("Must be logged in to delete a account.");
+        }
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(connection.getHost());
+        Map attributes = new HashMap();
+        // To delete an account, we add a single attribute, "remove", that is blank.
+        attributes.put("remove", "");
+        reg.setAttributes(attributes);
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Gets the account registration info from the server.
+     *
+     * @throws XMPPException if an error occurs.
+     */
+    private synchronized void getRegistrationInfo() throws XMPPException {
+        Registration reg = new Registration();
+        reg.setTo(connection.getHost());
+        PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+        else {
+            info = (Registration)result;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/Chat.java b/CopyOftrunk/source/org/jivesoftware/smack/Chat.java
new file mode 100644
index 000000000..c29832dd9
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/Chat.java
@@ -0,0 +1,266 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.filter.*;
+
+/**
+ * A chat is a series of messages sent between two users. Each chat can have
+ * a unique thread ID, which is used to track which messages are part of a particular
+ * conversation.<p>
+ *
+ * In some situations, it is better to have all messages from the other user delivered
+ * to a Chat rather than just the messages that have a particular thread ID. To
+ * enable this behavior, call {@link #setFilteredOnThreadID(boolean)} with
+ * <tt>false</tt> as the parameter.
+ *
+ * @see XMPPConnection#createChat(String)
+ * @author Matt Tucker
+ */
+public class Chat {
+
+    /**
+     * A prefix helps to make sure that ID's are unique across mutliple instances.
+     */
+    private static String prefix = StringUtils.randomString(5);
+    
+    /**
+     * True if only messages that have a matching threadID will be delivered to a Chat. When
+     * false, any message from the other participant will be delivered to a Chat.
+     */
+    private static boolean filteredOnThreadID = true;
+    
+    /**
+     * Keeps track of the current increment, which is appended to the prefix to
+     * forum a unique ID.
+     */
+    private static long id = 0;
+
+    /**
+     * Returns the next unique id. Each id made up of a short alphanumeric
+     * prefix along with a unique numeric value.
+     *
+     * @return the next id.
+     */
+    private static synchronized String nextID() {
+        return prefix + Long.toString(id++);
+    }
+
+    private XMPPConnection connection;
+    private String threadID;
+    private String participant;
+    private PacketFilter messageFilter;
+    private PacketCollector messageCollector;
+
+    /**
+     * Creates a new chat with the specified user.
+     *
+     * @param connection the connection the chat will use.
+     * @param participant the user to chat with.
+     */
+    public Chat(XMPPConnection connection, String participant) {
+        // Automatically assign the next chat ID.
+        this(connection, participant, nextID());
+        // If not filtering on thread ID, force the thread ID for this Chat to be null.
+        if (!filteredOnThreadID) {
+            this.threadID = null;
+        }
+    }
+
+    /**
+     * Creates a new chat with the specified user and thread ID.
+     *
+     * @param connection the connection the chat will use.
+     * @param participant the user to chat with.
+     * @param threadID the thread ID to use.
+     */
+    public Chat(XMPPConnection connection, String participant, String threadID) {
+        this.connection = connection;
+        this.participant = participant;
+        this.threadID = threadID;
+
+        if (filteredOnThreadID) {
+            // Filter the messages whose thread equals Chat's id
+            messageFilter = new ThreadFilter(threadID);
+        }
+        else {
+            // Filter the messages of type "chat" and sender equals Chat's participant
+            messageFilter =
+                new OrFilter(
+                    new AndFilter(
+                        new MessageTypeFilter(Message.Type.CHAT),
+                        new FromContainsFilter(participant)),
+                    new ThreadFilter(threadID));
+        }
+        messageCollector = connection.createPacketCollector(messageFilter);
+    }
+
+    /**
+     * Returns true if only messages that have a matching threadID will be delivered to Chat
+     * instances. When false, any message from the other participant will be delivered to Chat instances.
+     *
+     * @return true if messages delivered to Chat instances are filtered on thread ID.
+     */
+    public static boolean isFilteredOnThreadID() {
+        return filteredOnThreadID;
+    }
+
+    /**
+     * Sets whether only messages that have a matching threadID will be delivered to Chat instances.
+     * When false, any message from the other participant will be delivered to a Chat instances.
+     *
+     * @param value true if messages delivered to Chat instances are filtered on thread ID.
+     */
+    public static void setFilteredOnThreadID(boolean value) {
+        filteredOnThreadID = value;
+    }
+
+    /**
+     * Returns the thread id associated with this chat, which corresponds to the
+     * <tt>thread</tt> field of XMPP messages. This method may return <tt>null</tt>
+     * if there is no thread ID is associated with this Chat.
+     *
+     * @return the thread ID of this chat.
+     */
+    public String getThreadID() {
+        return threadID;
+    }
+
+    /**
+     * Returns the name of the user the chat is with.
+     *
+     * @return the name of the user the chat is occuring with.
+     */
+    public String getParticipant() {
+        return participant;
+    }
+
+    /**
+     * Sends the specified text as a message to the other chat participant.
+     * This is a convenience method for:
+     *
+     * <pre>
+     *     Message message = chat.createMessage();
+     *     message.setBody(messageText);
+     *     chat.sendMessage(message);
+     * </pre>
+     *
+     * @param text the text to send.
+     * @throws XMPPException if sending the message fails.
+     */
+    public void sendMessage(String text) throws XMPPException {
+        Message message = createMessage();
+        message.setBody(text);
+        connection.sendPacket(message);
+    }
+
+    /**
+     * Creates a new Message to the chat participant. The message returned
+     * will have its thread property set with this chat ID.
+     *
+     * @return a new message addressed to the chat participant and
+     *      using the correct thread value.
+     * @see #sendMessage(Message)
+     */
+    public Message createMessage() {
+        Message message = new Message(participant, Message.Type.CHAT);
+        message.setThread(threadID);
+        return message;
+    }
+
+    /**
+     * Sends a message to the other chat participant. The thread ID, recipient,
+     * and message type of the message will automatically set to those of this chat
+     * in case the Message was not created using the {@link #createMessage() createMessage}
+     * method.
+     *
+     * @param message the message to send.
+     * @throws XMPPException if an error occurs sending the message.
+     */
+    public void sendMessage(Message message) throws XMPPException {
+        // Force the recipient, message type, and thread ID since the user elected
+        // to send the message through this chat object.
+        message.setTo(participant);
+        message.setType(Message.Type.CHAT);
+        message.setThread(threadID);
+        connection.sendPacket(message);
+    }
+
+    /**
+     * Polls for and returns the next message, or <tt>null</tt> if there isn't
+     * a message immediately available. This method provides significantly different
+     * functionalty than the {@link #nextMessage()} method since it's non-blocking.
+     * In other words, the method call will always return immediately, whereas the
+     * nextMessage method will return only when a message is available (or after
+     * a specific timeout).
+     *
+     * @return the next message if one is immediately available and
+     *      <tt>null</tt> otherwise.
+     */
+    public Message pollMessage() {
+        return (Message)messageCollector.pollResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a message is available.
+     *
+     * @return the next message.
+     */
+    public Message nextMessage() {
+        return (Message)messageCollector.nextResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a packet is available or the <tt>timeout</tt> has elapased.
+     * If the timeout elapses without a result, <tt>null</tt> will be returned.
+     *
+     * @param timeout the maximum amount of time to wait for the next message.
+     * @return the next message, or <tt>null</tt> if the timeout elapses without a
+     *      message becoming available.
+     */
+    public Message nextMessage(long timeout) {
+        return (Message)messageCollector.nextResult(timeout);
+    }
+
+    /**
+     * Adds a packet listener that will be notified of any new messages in the
+     * chat.
+     *
+     * @param listener a packet listener.
+     */
+    public void addMessageListener(PacketListener listener) {
+        connection.addPacketListener(listener, messageFilter);
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        try {
+            if (messageCollector != null) {
+                messageCollector.cancel();
+            }
+        }
+        catch (Exception e) {}
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java
new file mode 100644
index 000000000..316615bfc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionEstablishedListener.java
@@ -0,0 +1,41 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+/**
+ * Interface that allows for implementing classes to listen for connection established
+ * events. Listeners are registered with the XMPPConnection class.
+ *
+ * @see XMPPConnection#addConnectionListener
+ * @see XMPPConnection#removeConnectionListener
+ * 
+ * @author Gaston Dombiak
+ */
+public interface ConnectionEstablishedListener {
+
+    /**
+     * Notification that a new connection has been established.
+     * 
+     * @param connection the new established connection 
+     */
+    public void connectionEstablished(XMPPConnection connection);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java
new file mode 100644
index 000000000..845cba884
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/ConnectionListener.java
@@ -0,0 +1,45 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+/**
+ * Interface that allows for implementing classes to listen for connection closing
+ * events. Listeners are reigstered with XMPPConnection objects.
+ *
+ * @see XMPPConnection#addConnectionListener
+ * @see XMPPConnection#removeConnectionListener
+ * 
+ * @author Matt Tucker
+ */
+public interface ConnectionListener {
+
+    /**
+     * Notification that the connection was closed normally.
+     */
+    public void connectionClosed();
+
+    /**
+     * Notification that the connection was closed due to an exception.
+     *
+     * @param e the exception.
+     */
+    public void connectionClosedOnError(Exception e);
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java b/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java
new file mode 100644
index 000000000..668ed8934
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/GroupChat.java
@@ -0,0 +1,353 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.filter.*;
+
+import java.util.*;
+
+/**
+ * A GroupChat is a conversation that takes place among many users in a virtual
+ * room. When joining a group chat, you specify a nickname, which is the identity
+ * that other chat room users see.
+ *
+ * @see XMPPConnection#createGroupChat(String)
+ * @author Matt Tucker
+ */
+public class GroupChat {
+
+    private XMPPConnection connection;
+    private String room;
+    private String nickname = null;
+    private boolean joined = false;
+    private List participants = new ArrayList();
+    private List connectionListeners = new ArrayList();
+
+    private PacketFilter presenceFilter;
+    private PacketFilter messageFilter;
+    private PacketCollector messageCollector;
+
+    /**
+     * Creates a new group chat with the specified connection and room name. Note: no
+     * information is sent to or received from the server until you attempt to
+     * {@link #join(String) join} the chat room. On some server implementations,
+     * the room will not be created until the first person joins it.<p>
+     *
+     *  Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
+     * for the XMPP server example.com). You must ensure that the room address you're
+     * trying to connect to includes the proper chat sub-domain.
+     *
+     * @param connection the XMPP connection.
+     * @param room the name of the room in the form "roomName@service", where
+     *      "service" is the hostname at which the multi-user chat
+     *      service is running.
+     */
+    public GroupChat(XMPPConnection connection, String room) {
+        this.connection = connection;
+        this.room = room;
+        // Create a collector for all incoming messages.
+        messageFilter = new AndFilter(new FromContainsFilter(room),
+                new PacketTypeFilter(Message.class));
+        messageFilter = new AndFilter(messageFilter, new PacketFilter() {
+            public boolean accept(Packet packet) {
+                Message msg = (Message)packet;
+                return msg.getType() == Message.Type.GROUP_CHAT;
+            }
+        });
+        messageCollector = connection.createPacketCollector(messageFilter);
+        // Create a listener for all presence updates.
+        presenceFilter = new AndFilter(new FromContainsFilter(room),
+                new PacketTypeFilter(Presence.class));
+        connection.addPacketListener(new PacketListener() {
+            public void processPacket(Packet packet) {
+                Presence presence = (Presence)packet;
+                String from = presence.getFrom();
+                if (presence.getType() == Presence.Type.AVAILABLE) {
+                    synchronized (participants) {
+                        if (!participants.contains(from)) {
+                            participants.add(from);
+                        }
+                    }
+                }
+                else if (presence.getType() == Presence.Type.UNAVAILABLE) {
+                    synchronized (participants) {
+                        participants.remove(from);
+                    }
+                }
+            }
+        }, presenceFilter);
+    }
+
+    /**
+     * Returns the name of the room this GroupChat object represents.
+     *
+     * @return the groupchat room name.
+     */
+    public String getRoom() {
+        return room;
+    }
+
+    /**
+     * Joins the chat room using the specified nickname. If already joined
+     * using another nickname, this method will first leave the room and then
+     * re-join using the new nickname. The default timeout of 5 seconds for a reply
+     * from the group chat server that the join succeeded will be used.
+     *
+     * @param nickname the nickname to use.
+     * @throws XMPPException if an error occurs joining the room. In particular, a
+     *      409 error can occur if someone is already in the group chat with the same
+     *      nickname.
+     */
+    public synchronized void join(String nickname) throws XMPPException {
+        join(nickname, SmackConfiguration.getPacketReplyTimeout());
+    }
+
+    /**
+     * Joins the chat room using the specified nickname. If already joined as
+     * another nickname, will leave as that name first before joining under the new
+     * name.
+     *
+     * @param nickname the nickname to use.
+     * @param timeout the number of milleseconds to wait for a reply from the
+     *      group chat that joining the room succeeded.
+     * @throws XMPPException if an error occurs joining the room. In particular, a
+     *      409 error can occur if someone is already in the group chat with the same
+     *      nickname.
+     */
+    public synchronized void join(String nickname, long timeout) throws XMPPException {
+        if (nickname == null || nickname.equals("")) {
+            throw new IllegalArgumentException("Nickname must not be null or blank.");
+        }
+        // If we've already joined the room, leave it before joining under a new
+        // nickname.
+        if (joined) {
+            leave();
+        }
+        // We join a room by sending a presence packet where the "to"
+        // field is in the form "roomName@service/nickname"
+        Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
+        joinPresence.setTo(room + "/" + nickname);
+        // Wait for a presence packet back from the server.
+        PacketFilter responseFilter = new AndFilter(
+                new FromContainsFilter(room + "/" + nickname),
+                new PacketTypeFilter(Presence.class));
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send join packet.
+        connection.sendPacket(joinPresence);
+        // Wait up to a certain number of seconds for a reply.
+        Presence presence = (Presence)response.nextResult(timeout);
+        response.cancel();
+        if (presence == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (presence.getError() != null) {
+            throw new XMPPException(presence.getError());
+        }
+        this.nickname = nickname;
+        joined = true;
+    }
+
+    /**
+     * Returns true if currently in the group chat (after calling the {@link
+     * #join(String)} method.
+     *
+     * @return true if currently in the group chat room.
+     */
+    public boolean isJoined() {
+        return joined;
+    }
+
+    /**
+     * Leave the chat room.
+     */
+    public synchronized void leave() {
+        // If not joined already, do nothing.
+        if (!joined) {
+            return;
+        }
+        // We leave a room by sending a presence packet where the "to"
+        // field is in the form "roomName@service/nickname"
+        Presence leavePresence = new Presence(Presence.Type.UNAVAILABLE);
+        leavePresence.setTo(room + "/" + nickname);
+        connection.sendPacket(leavePresence);
+        // Reset participant information.
+        participants = new ArrayList();
+        nickname = null;
+        joined = false;
+    }
+
+    /**
+     * Returns the nickname that was used to join the room, or <tt>null</tt> if not
+     * currently joined.
+     *
+     * @return the nickname currently being used.
+     */
+    public String getNickname() {
+        return nickname;
+    }
+
+    /**
+     * Returns the number of participants in the group chat.<p>
+     *
+     * Note: this value will only be accurate after joining the group chat, and
+     * may fluctuate over time. If you query this value directly after joining the
+     * group chat it may not be accurate, as it takes a certain amount of time for
+     * the server to send all presence packets to this client.
+     *
+     * @return the number of participants in the group chat.
+     */
+    public int getParticipantCount() {
+        synchronized (participants) {
+            return participants.size();
+        }
+    }
+
+    /**
+     * Returns an Iterator (of Strings) for the list of fully qualified participants
+     * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser".
+     * Typically, a client would only display the nickname of the participant. To
+     * get the nickname from the fully qualified name, use the
+     * {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method.
+     * Note: this value will only be accurate after joining the group chat, and may
+     * fluctuate over time.
+     *
+     * @return an Iterator for the participants in the group chat.
+     */
+    public Iterator getParticipants() {
+        synchronized (participants) {
+            return Collections.unmodifiableList(new ArrayList(participants)).iterator();
+        }
+    }
+
+    /**
+     * Adds a packet listener that will be notified of any new Presence packets
+     * sent to the group chat. Using a listener is a suitable way to know when the list
+     * of participants should be re-loaded due to any changes.
+     *
+     * @param listener a packet listener that will be notified of any presence packets
+     *      sent to the group chat.
+     */
+    public void addParticipantListener(PacketListener listener) {
+        connection.addPacketListener(listener, presenceFilter);
+        connectionListeners.add(listener);
+    }
+
+    /**
+     * Sends a message to the chat room.
+     *
+     * @param text the text of the message to send.
+     * @throws XMPPException if sending the message fails.
+     */
+    public void sendMessage(String text) throws XMPPException {
+        Message message = new Message(room, Message.Type.GROUP_CHAT);
+        message.setBody(text);
+        connection.sendPacket(message);
+    }
+
+    /**
+     * Creates a new Message to send to the chat room.
+     *
+     * @return a new Message addressed to the chat room.
+     */
+    public Message createMessage() {
+        return new Message(room, Message.Type.GROUP_CHAT);
+    }
+
+    /**
+     * Sends a Message to the chat room.
+     *
+     * @param message the message.
+     * @throws XMPPException if sending the message fails.
+     */
+    public void sendMessage(Message message) throws XMPPException {
+        connection.sendPacket(message);
+    }
+
+     /**
+     * Polls for and returns the next message, or <tt>null</tt> if there isn't
+     * a message immediately available. This method provides significantly different
+     * functionalty than the {@link #nextMessage()} method since it's non-blocking.
+     * In other words, the method call will always return immediately, whereas the
+     * nextMessage method will return only when a message is available (or after
+     * a specific timeout).
+     *
+     * @return the next message if one is immediately available and
+     *      <tt>null</tt> otherwise.
+     */
+    public Message pollMessage() {
+        return (Message)messageCollector.pollResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a message is available.
+     *
+     * @return the next message.
+     */
+    public Message nextMessage() {
+        return (Message)messageCollector.nextResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a packet is available or the <tt>timeout</tt> has elapased.
+     * If the timeout elapses without a result, <tt>null</tt> will be returned.
+     *
+     * @param timeout the maximum amount of time to wait for the next message.
+     * @return the next message, or <tt>null</tt> if the timeout elapses without a
+     *      message becoming available.
+     */
+    public Message nextMessage(long timeout) {
+        return (Message)messageCollector.nextResult(timeout);
+    }
+
+    /**
+     * Adds a packet listener that will be notified of any new messages in the
+     * group chat. Only "group chat" messages addressed to this group chat will
+     * be delivered to the listener. If you wish to listen for other packets
+     * that may be associated with this group chat, you should register a
+     * PacketListener directly with the XMPPConnection with the appropriate
+     * PacketListener.
+     *
+     * @param listener a packet listener.
+     */
+    public void addMessageListener(PacketListener listener) {
+        connection.addPacketListener(listener, messageFilter);
+        connectionListeners.add(listener);
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        try {
+            if (messageCollector != null) {
+                messageCollector.cancel();
+            }
+            // Remove all the PacketListeners added to the connection by this GroupChat
+            for (Iterator it=connectionListeners.iterator(); it.hasNext();) {
+                connection.removePacketListener((PacketListener) it.next());
+            }
+        }
+        catch (Exception e) {}
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java
new file mode 100644
index 000000000..75fba96cf
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketCollector.java
@@ -0,0 +1,184 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.filter.PacketFilter;
+
+import java.util.LinkedList;
+
+/**
+ * Provides a mechanism to collect packets into a result queue that pass a
+ * specified filter. The collector lets you perform blocking and polling
+ * operations on the result queue. So, a PacketCollector is more suitable to
+ * use than a {@link PacketListener} when you need to wait for a specific
+ * result.<p>
+ *
+ * Each packet collector will queue up to 2^16 packets for processing before
+ * older packets are automatically dropped.
+ *
+ * @see XMPPConnection#createPacketCollector(PacketFilter)
+ * @author Matt Tucker
+ */
+public class PacketCollector {
+
+    /**
+     * Max number of packets that any one collector can hold. After the max is
+     * reached, older packets will be automatically dropped from the queue as
+     * new packets are added.
+     */
+    private static final int MAX_PACKETS = 65536;
+
+    private PacketFilter packetFilter;
+    private LinkedList resultQueue;
+    private PacketReader packetReader;
+    private boolean cancelled = false;
+
+    /**
+     * Creates a new packet collector. If the packet filter is <tt>null</tt>, then
+     * all packets will match this collector.
+     *
+     * @param packetReader the packetReader the collector is tied to.
+     * @param packetFilter determines which packets will be returned by this collector.
+     */
+    protected PacketCollector(PacketReader packetReader, PacketFilter packetFilter) {
+        this.packetReader = packetReader;
+        this.packetFilter = packetFilter;
+        this.resultQueue = new LinkedList();
+         // Add the collector to the packet reader's list of active collector.
+        synchronized (packetReader.collectors) {
+            packetReader.collectors.add(this);
+        }
+    }
+
+    /**
+     * Explicitly cancels the packet collector so that no more results are
+     * queued up. Once a packet collector has been cancelled, it cannot be
+     * re-enabled. Instead, a new packet collector must be created.
+     */
+    public void cancel() {
+        // If the packet collector has already been cancelled, do nothing.
+        if (cancelled) {
+            return;
+        }
+        else {
+            cancelled = true;
+            // Remove object from collectors list by setting the value in the
+            // list at the correct index to null. The collector thread will
+            // automatically remove the actual list entry when it can.
+            synchronized (packetReader.collectors) {
+                int index = packetReader.collectors.indexOf(this);
+                packetReader.collectors.set(index, null);
+            }
+        }
+    }
+
+    /**
+     * Returns the packet filter associated with this packet collector. The packet
+     * filter is used to determine what packets are queued as results.
+     *
+     * @return the packet filter.
+     */
+    public PacketFilter getPacketFilter() {
+        return packetFilter;
+    }
+
+    /**
+     * Polls to see if a packet is currently available and returns it, or
+     * immediately returns <tt>null</tt> if no packets are currently in the
+     * result queue.
+     *
+     * @return the next packet result, or <tt>null</tt> if there are no more
+     *      results.
+     */
+    public synchronized Packet pollResult() {
+        if (resultQueue.isEmpty()) {
+            return null;
+        }
+        else {
+            return (Packet)resultQueue.removeLast();
+        }
+    }
+
+    /**
+     * Returns the next available packet. The method call will block (not return)
+     * until a packet is available.
+     *
+     * @return the next available packet.
+     */
+    public synchronized Packet nextResult() {
+        // Wait indefinitely until there is a result to return.
+        while (resultQueue.isEmpty()) {
+            try {
+                wait();
+            }
+            catch (InterruptedException ie) { }
+        }
+        return (Packet)resultQueue.removeLast();
+    }
+
+    /**
+     * Returns the next available packet. The method call will block (not return)
+     * until a packet is available or the <tt>timeout</tt> has elapased. If the
+     * timeout elapses without a result, <tt>null</tt> will be returned.
+     *
+     * @param timeout the amount of time to wait for the next packet (in milleseconds).
+     * @return the next available packet.
+     */
+    public synchronized Packet nextResult(long timeout) {
+        // Wait up to the specified amount of time for a result.
+        if (resultQueue.isEmpty()) {
+            try {
+                wait(timeout);
+            }
+            catch (InterruptedException ie) { }
+        }
+        // If still no result, return null.
+        if (resultQueue.isEmpty()) {
+            return null;
+        }
+        else {
+            return (Packet)resultQueue.removeLast();
+        }
+    }
+
+    /**
+     * Processes a packet to see if it meets the criteria for this packet collector.
+     * If so, the packet is added to the result queue.
+     *
+     * @param packet the packet to process.
+     */
+    protected synchronized void processPacket(Packet packet) {
+        if (packet == null) {
+            return;
+        }
+        if (packetFilter == null || packetFilter.accept(packet)) {
+            // If the max number of packets has been reached, remove the oldest one.
+            if (resultQueue.size() == MAX_PACKETS) {
+                resultQueue.removeLast();
+            }
+            // Add the new packet.
+            resultQueue.addFirst(packet);
+            // Notify waiting threads a result is available.
+            notifyAll();
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java
new file mode 100644
index 000000000..77c6deabe
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketListener.java
@@ -0,0 +1,48 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Provides a mechanism to listen for packets that pass a specified filter.
+ * This allows event-style programming -- every time a new packet is found,
+ * the {@link #processPacket(Packet)} method will be called. This is the
+ * opposite approach to the functionality provided by a {@link PacketCollector}
+ * which lets you block while waiting for results.
+ *
+ * @see XMPPConnection#addPacketListener(PacketListener, org.jivesoftware.smack.filter.PacketFilter)
+ * @author Matt Tucker
+ */
+public interface PacketListener {
+
+    /**
+     * Process the next packet sent to this packet listener.<p>
+     *
+     * A single thread is responsible for invoking all listeners, so
+     * it's very important that implementations of this method not block
+     * for any extended period of time.
+     *
+     * @param packet the packet to process.
+     */
+    public void processPacket(Packet packet);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java
new file mode 100644
index 000000000..dfd2cf598
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketReader.java
@@ -0,0 +1,593 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.xmlpull.v1.*;
+import org.xmlpull.mxp1.MXParser;
+
+import java.util.*;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.util.*;
+import org.jivesoftware.smack.provider.*;
+
+/**
+ * Listens for XML traffic from the XMPP server and parses it into packet objects.
+ * The packet reader also manages all packet listeners and collectors.<p>
+ *
+ * @see PacketCollector
+ * @see PacketListener
+ * @author Matt Tucker
+ */
+class PacketReader {
+
+    private Thread readerThread;
+    private Thread listenerThread;
+
+    private XMPPConnection connection;
+    private XmlPullParser parser;
+    private boolean done = false;
+    protected List collectors = new ArrayList();
+    private List listeners = new ArrayList();
+    protected List connectionListeners = new ArrayList();
+
+    private String connectionID = null;
+    private Object connectionIDLock = new Object();
+
+    protected PacketReader(XMPPConnection connection) {
+        this.connection = connection;
+
+        readerThread = new Thread() {
+            public void run() {
+                parsePackets();
+            }
+        };
+        readerThread.setName("Smack Packet Reader");
+        readerThread.setDaemon(true);
+
+        listenerThread = new Thread() {
+            public void run() {
+                try {
+                    processListeners();
+                }
+                catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        };
+        listenerThread.setName("Smack Listener Processor");
+        listenerThread.setDaemon(true);
+
+        try {
+            parser = new MXParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            parser.setInput(connection.reader);
+        }
+        catch (XmlPullParserException xppe) {
+            xppe.printStackTrace();
+        }
+    }
+
+    /**
+     * Creates a new packet collector for this reader. A packet filter determines
+     * which packets will be accumulated by the collector.
+     *
+     * @param packetFilter the packet filter to use.
+     * @return a new packet collector.
+     */
+    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
+        PacketCollector packetCollector = new PacketCollector(this, packetFilter);
+        return packetCollector;
+    }
+
+    /**
+     * Registers a packet listener with this reader. A packet filter determines
+     * which packets will be delivered to the listener.
+     *
+     * @param packetListener the packet listener to notify of new packets.
+     * @param packetFilter the packet filter to use.
+     */
+    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
+        ListenerWrapper wrapper = new ListenerWrapper(this, packetListener,
+                packetFilter);
+        synchronized (listeners) {
+            listeners.add(wrapper);
+        }
+    }
+
+    /**
+     * Removes a packet listener.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketListener(PacketListener packetListener) {
+        synchronized (listeners) {
+            for (int i=0; i<listeners.size(); i++) {
+                ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
+                if (wrapper != null && wrapper.packetListener.equals(packetListener)) {
+                    listeners.set(i, null);
+                }
+            }
+        }
+    }
+
+    /**
+     * Starts the packet reader thread and returns once a connection to the server
+     * has been established. A connection will be attempted for a maximum of five
+     * seconds. An XMPPException will be thrown if the connection fails.
+     *
+     * @throws XMPPException if the server fails to send an opening stream back
+     *      for more than five seconds.
+     */
+    public void startup() throws XMPPException {
+        readerThread.start();
+        listenerThread.start();
+        // Wait for stream tag before returing. We'll wait a couple of seconds before
+        // giving up and throwing an error.
+        try {
+            synchronized(connectionIDLock) {
+                if (connectionID == null) {
+                    // A waiting thread may be woken up before the wait time or a notify
+                    // (although this is a rare thing). Therefore, we continue waiting
+                    // until either a connectionID has been set (and hence a notify was
+                    // made) or the total wait time has elapsed.
+                    long waitTime = SmackConfiguration.getPacketReplyTimeout();
+                    long start = System.currentTimeMillis();
+                    while (connectionID == null && !done) {
+                        if (waitTime <= 0) {
+                            break;
+                        }
+                        connectionIDLock.wait(waitTime);
+                        long now = System.currentTimeMillis();
+                        waitTime -= now - start;
+                        start = now;
+                    }
+                }
+            }
+        }
+        catch (InterruptedException ie) { }
+        if (connectionID == null) {
+            throw new XMPPException("Connection failed. No response from server.");
+        }
+        else {
+            connection.connectionID = connectionID;
+        }
+    }
+
+    /**
+     * Shuts the packet reader down.
+     */
+    public void shutdown() {
+        // Notify connection listeners of the connection closing if done hasn't already been set.
+        if (!done) {
+            ArrayList listenersCopy;
+            synchronized (connectionListeners) {
+                // Make a copy since it's possible that a listener will be removed from the list
+                listenersCopy = new ArrayList(connectionListeners);
+                for (Iterator i=listenersCopy.iterator(); i.hasNext(); ) {
+                    ConnectionListener listener = (ConnectionListener)i.next();
+                    listener.connectionClosed();
+                }
+            }
+        }
+        done = true;
+    }
+
+    /**
+     * Sends out a notification that there was an error with the connection
+     * and closes the connection.
+     *
+     * @param e the exception that causes the connection close event.
+     */
+    void notifyConnectionError(Exception e) {
+        done = true;
+        connection.close();
+        // Print the stack trace to help catch the problem
+        e.printStackTrace();
+        // Notify connection listeners of the error.
+        ArrayList listenersCopy;
+        synchronized (connectionListeners) {
+            // Make a copy since it's possible that a listener will be removed from the list
+            listenersCopy = new ArrayList(connectionListeners);
+            for (Iterator i=listenersCopy.iterator(); i.hasNext(); ) {
+                ConnectionListener listener = (ConnectionListener)i.next();
+                listener.connectionClosedOnError(e);
+            }
+        }
+    }
+
+    /**
+     * Process listeners.
+     */
+    private void processListeners() {
+        boolean processedPacket = false;
+        while (!done) {
+            synchronized (listeners) {
+                if (listeners.size() > 0) {
+                    for (int i=listeners.size()-1; i>=0; i--) {
+                        if (listeners.get(i) == null) {
+                            listeners.remove(i);
+                        }
+                    }
+                }
+            }
+            processedPacket = false;
+            int size = listeners.size();
+            for (int i=0; i<size; i++) {
+                ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
+                if (wrapper != null) {
+                    processedPacket = processedPacket || wrapper.notifyListener();
+                }
+            }
+            if (!processedPacket) {
+                try {
+                    Thread.sleep(100);
+                }
+                catch (InterruptedException ie) { }
+            }
+        }
+    }
+
+    /**
+     * Parse top-level packets in order to process them further.
+     */
+    private void parsePackets() {
+        try {
+            int eventType = parser.getEventType();
+            do {
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals("message")) {
+                        processPacket(PacketParserUtils.parseMessage(parser));
+                    }
+                    else if (parser.getName().equals("iq")) {
+                        processPacket(parseIQ(parser));
+                    }
+                    else if (parser.getName().equals("presence")) {
+                        processPacket(PacketParserUtils.parsePresence(parser));
+                    }
+                    // We found an opening stream. Record information about it, then notify
+                    // the connectionID lock so that the packet reader startup can finish.
+                    else if (parser.getName().equals("stream")) {
+                        // Ensure the correct jabber:client namespace is being used.
+                        if ("jabber:client".equals(parser.getNamespace(null))) {
+                            // Get the connection id.
+                            for (int i=0; i<parser.getAttributeCount(); i++) {
+                                if (parser.getAttributeName(i).equals("id")) {
+                                    // Save the connectionID and notify that we've gotten it.
+                                    synchronized(connectionIDLock) {
+                                        connectionID = parser.getAttributeValue(i);
+                                        connectionIDLock.notifyAll();
+                                    }
+                                }
+                                else if (parser.getAttributeName(i).equals("from")) {
+                                    // Use the server name that the server says that it is.
+                                    connection.host = parser.getAttributeValue(i);
+                                }
+                            }
+                        }
+                    }
+                }
+                else if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("stream")) {
+                        // Close the connection.
+                        connection.close();
+                    }
+                }
+                eventType = parser.next();
+            } while (!done && eventType != XmlPullParser.END_DOCUMENT);
+        }
+        catch (Exception e) {
+            if (!done) {
+                // Close the connection and notify connection listeners of the
+                // error.
+                notifyConnectionError(e);
+            }
+        }
+    }
+
+    /**
+     * Processes a packet after it's been fully parsed by looping through the installed
+     * packet collectors and listeners and letting them examine the packet to see if
+     * they are a match with the filter.
+     *
+     * @param packet the packet to process.
+     */
+    private void processPacket(Packet packet) {
+        if (packet == null) {
+            return;
+        }
+
+        // Remove all null values from the collectors list.
+        synchronized (collectors) {
+            for (int i=collectors.size()-1; i>=0; i--) {
+                    if (collectors.get(i) == null) {
+                        collectors.remove(i);
+                    }
+                }
+        }
+
+        // Loop through all collectors and notify the appropriate ones.
+        int size = collectors.size();
+        for (int i=0; i<size; i++) {
+            PacketCollector collector = (PacketCollector)collectors.get(i);
+            if (collector != null) {
+                // Have the collector process the packet to see if it wants to handle it.
+                collector.processPacket(packet);
+            }
+        }
+    }
+
+    /**
+     * Parses an IQ packet.
+     *
+     * @param parser the XML parser, positioned at the start of an IQ packet.
+     * @return an IQ object.
+     * @throws Exception if an exception occurs while parsing the packet.
+     */
+    private IQ parseIQ(XmlPullParser parser) throws Exception {
+        IQ iqPacket = null;
+
+        String id = parser.getAttributeValue("", "id");
+        String to = parser.getAttributeValue("", "to");
+        String from = parser.getAttributeValue("", "from");
+        IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
+        XMPPError error = null;
+
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+
+            if (eventType == XmlPullParser.START_TAG) {
+                String elementName = parser.getName();
+                String namespace = parser.getNamespace();
+                if (elementName.equals("error")) {
+                    error = PacketParserUtils.parseError(parser);
+                }
+                else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) {
+                    iqPacket = parseAuthentication(parser);
+                }
+                else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) {
+                    iqPacket = parseRoster(parser);
+                }
+                else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) {
+                    iqPacket = parseRegistration(parser);
+                }
+                // Otherwise, see if there is a registered provider for
+                // this element name and namespace.
+                else {
+                    Object provider = ProviderManager.getIQProvider(elementName, namespace);
+                    if (provider != null) {
+                        if (provider instanceof IQProvider) {
+                            iqPacket = ((IQProvider)provider).parseIQ(parser);
+                        }
+                        else if (provider instanceof Class) {
+                            iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName,
+                                    (Class)provider, parser);
+                        }
+                    }
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("iq")) {
+                    done = true;
+                }
+            }
+        }
+        // Decide what to do when an IQ packet was not understood
+        if (iqPacket == null) {
+            if (IQ.Type.GET == type || IQ.Type.SET == type ) {
+                // If the IQ stanza is of type "get" or "set" containing a child element
+                // qualified by a namespace it does not understand, then answer an IQ of
+                // type "error" with code 501 ("feature-not-implemented")
+                iqPacket = new IQ() {
+                    public String getChildElementXML() {
+                        return null;
+                    }
+                };
+                iqPacket.setPacketID(id);
+                iqPacket.setTo(from);
+                iqPacket.setFrom(to);
+                iqPacket.setType(IQ.Type.ERROR);
+                iqPacket.setError(new XMPPError(501, "feature-not-implemented"));
+                connection.sendPacket(iqPacket);
+                return null;
+            }
+            else {
+                // If an IQ packet wasn't created above, create an empty IQ packet.
+                iqPacket = new IQ() {
+                    public String getChildElementXML() {
+                        return null;
+                    }
+                };
+            }
+        }
+
+        // Set basic values on the iq packet.
+        iqPacket.setPacketID(id);
+        iqPacket.setTo(to);
+        iqPacket.setFrom(from);
+        iqPacket.setType(type);
+        iqPacket.setError(error);
+
+        return iqPacket;
+    }
+
+    private Authentication parseAuthentication(XmlPullParser parser) throws Exception {
+        Authentication authentication = new Authentication();
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("username")) {
+                    authentication.setUsername(parser.nextText());
+                }
+                else if (parser.getName().equals("password")) {
+                    authentication.setPassword(parser.nextText());
+                }
+                else if (parser.getName().equals("digest")) {
+                    authentication.setDigest(parser.nextText());
+                }
+                else if (parser.getName().equals("resource")) {
+                    authentication.setResource(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+        return authentication;
+    }
+
+    private RosterPacket parseRoster(XmlPullParser parser) throws Exception {
+        RosterPacket roster = new RosterPacket();
+        boolean done = false;
+        RosterPacket.Item item = null;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    String jid = parser.getAttributeValue("", "jid");
+                    String name = parser.getAttributeValue("", "name");
+                    // Create packet.
+                    item = new RosterPacket.Item(jid, name);
+                    // Set status.
+                    String ask = parser.getAttributeValue("", "ask");
+                    RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask);
+                    item.setItemStatus(status);
+                    // Set type.
+                    String subscription = parser.getAttributeValue("", "subscription");
+                    RosterPacket.ItemType type = RosterPacket.ItemType.fromString(subscription);
+                    item.setItemType(type);
+                }
+                if (parser.getName().equals("group")) {
+                    String groupName = parser.nextText();
+                    item.addGroupName(groupName);
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    roster.addRosterItem(item);
+                }
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+        return roster;
+    }
+
+     private Registration parseRegistration(XmlPullParser parser) throws Exception {
+        Registration registration = new Registration();
+        Map fields = null;
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                // Any element that's in the jabber:iq:register namespace,
+                // attempt to parse it if it's in the form <name>value</name>.
+                if (parser.getNamespace().equals("jabber:iq:register")) {
+                    String name = parser.getName();
+                    String value = "";
+                    if (fields == null) {
+                        fields = new HashMap();
+                    }
+
+                    if (parser.next() == XmlPullParser.TEXT) {
+                        value = parser.getText();
+                    }
+                    // Ignore instructions, but anything else should be added to the map.
+                    if (!name.equals("instructions")) {
+                        fields.put(name, value);
+                    }
+                    else {
+                        registration.setInstructions(value);
+                    }
+}
+                // Otherwise, it must be a packet extension.
+                else {
+                    registration.addExtension(
+                        PacketParserUtils.parsePacketExtension(
+                            parser.getName(),
+                            parser.getNamespace(),
+                            parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+        registration.setAttributes(fields);
+        return registration;
+    }
+
+    /**
+     * A wrapper class to associate a packet collector with a listener.
+     */
+    private static class ListenerWrapper {
+
+        private PacketListener packetListener;
+        private PacketCollector packetCollector;
+
+        public ListenerWrapper(PacketReader packetReader, PacketListener packetListener,
+                PacketFilter packetFilter)
+        {
+            this.packetListener = packetListener;
+            this.packetCollector = new PacketCollector(packetReader, packetFilter);
+        }
+
+        public boolean equals(Object object) {
+            if (object == null) {
+                return false;
+            }
+            if (object instanceof ListenerWrapper) {
+                return ((ListenerWrapper)object).packetListener.equals(this.packetListener);
+            }
+            else if (object instanceof PacketListener) {
+                return object.equals(this.packetListener);
+            }
+            return false;
+        }
+
+        public boolean notifyListener() {
+            Packet packet = packetCollector.pollResult();
+            if (packet != null) {
+                packetListener.processPacket(packet);
+                return true;
+            }
+            else {
+                return false;
+            }
+        }
+
+        public void cancel() {
+            packetCollector.cancel();
+            packetCollector = null;
+            packetListener = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java b/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java
new file mode 100644
index 000000000..679cbc448
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/PacketWriter.java
@@ -0,0 +1,340 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import java.util.*;
+import java.io.*;
+
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Writes packets to a XMPP server.
+ *
+ * @author Matt Tucker
+ */
+class PacketWriter {
+
+    private Thread writerThread;
+    private Writer writer;
+    private XMPPConnection connection;
+    private LinkedList queue;
+    private boolean done = false;
+    
+    private List listeners = new ArrayList();
+    private boolean listenersDeleted = false;
+    private Thread listenerThread;
+    private LinkedList sentPackets = new LinkedList();
+
+    /**
+     * Creates a new packet writer with the specified connection.
+     *
+     * @param connection the connection.
+     */
+    protected PacketWriter(XMPPConnection connection) {
+        this.connection = connection;
+        this.writer = connection.writer;
+        this.queue = new LinkedList();
+
+        writerThread = new Thread() {
+            public void run() {
+                writePackets();
+            }
+        };
+        writerThread.setName("Smack Packet Writer");
+        writerThread.setDaemon(true);
+
+        listenerThread = new Thread() {
+            public void run() {
+                processListeners();
+            }
+        };
+        listenerThread.setName("Smack Writer Listener Processor");
+        listenerThread.setDaemon(true);
+
+        // Schedule a keep-alive task to run if the feature is enabled. will write
+        // out a space character each time it runs to keep the TCP/IP connection open.
+        int keepAliveInterval = SmackConfiguration.getKeepAliveInterval();
+        if (keepAliveInterval > 0) {
+            Thread keepAliveThread = new Thread(new KeepAliveTask(keepAliveInterval));
+            keepAliveThread.setDaemon(true);
+            keepAliveThread.start();
+        }
+    }
+
+    /**
+     * Sends the specified packet to the server.
+     *
+     * @param packet the packet to send.
+     */
+    public void sendPacket(Packet packet) {
+        if (!done) {
+            synchronized(queue) {
+                queue.addFirst(packet);
+                queue.notifyAll();
+            }
+            // Add the sent packet to the list of sent packets. The
+            // PacketWriterListeners will be notified of the new packet.
+            synchronized(sentPackets) {
+                sentPackets.addFirst(packet);
+                sentPackets.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Registers a packet listener with this writer. The listener will be
+     * notified of every packet that this writer sends. A packet filter determines
+     * which packets will be delivered to the listener.
+     *
+     * @param packetListener the packet listener to notify of sent packets.
+     * @param packetFilter the packet filter to use.
+     */
+    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
+        synchronized (listeners) {
+            listeners.add(new ListenerWrapper(packetListener, packetFilter));
+        }
+    }
+
+    /**
+     * Removes a packet listener.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketListener(PacketListener packetListener) {
+        synchronized (listeners) {
+            for (int i=0; i<listeners.size(); i++) {
+                ListenerWrapper wrapper = (ListenerWrapper)listeners.get(i);
+                if (wrapper != null && wrapper.packetListener.equals(packetListener)) {
+                    listeners.set(i, null);
+                    // Set the flag to indicate that the listener list needs
+                    // to be cleaned up.
+                    listenersDeleted = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the number of registered packet listeners.
+     *
+     * @return the count of packet listeners.
+     */
+    public int getPacketListenerCount() {
+        synchronized (listeners) {
+            return listeners.size();
+        }
+    }
+
+    /**
+     * Starts the packet writer thread and opens a connection to the server. The
+     * packet writer will continue writing packets until {@link #shutdown} or an
+     * error occurs.
+     */
+    public void startup() {
+        writerThread.start();
+        listenerThread.start();
+    }
+
+    /**
+     * Shuts down the packet writer. Once this method has been called, no further
+     * packets will be written to the server.
+     */
+    public void shutdown() {
+        done = true;
+    }
+
+    /**
+     * Returns the next available packet from the queue for writing.
+     *
+     * @return the next packet for writing.
+     */
+    private Packet nextPacket() {
+        synchronized(queue) {
+            while (!done && queue.size() == 0) {
+                try {
+                    queue.wait(2000);
+                }
+                catch (InterruptedException ie) { }
+            }
+            if (queue.size() > 0) {
+                return (Packet)queue.removeLast();
+            }
+            else {
+                return null;
+            }
+        }
+    }
+
+    private void writePackets() {
+        try {
+            // Open the stream.
+            StringBuffer stream = new StringBuffer();
+            stream.append("<stream:stream");
+            stream.append(" to=\"" + connection.getHost() + "\"");
+            stream.append(" xmlns=\"jabber:client\"");
+            stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\">");
+            writer.write(stream.toString());
+            writer.flush();
+            stream = null;
+            // Write out packets from the queue.
+            while (!done) {
+                Packet packet = nextPacket();
+                if (packet != null) {
+                    synchronized (writer) {
+                        writer.write(packet.toXML());
+                        writer.flush();
+                    }
+                }
+            }
+            // Close the stream.
+            try {
+                writer.write("</stream:stream>");
+                writer.flush();
+            }
+            catch (Exception e) { }
+            finally {
+                try {
+                    writer.close();
+                }
+                catch (Exception e) { }
+            }
+        }
+        catch (IOException ioe){
+            if (!done) {
+                done = true;
+                connection.packetReader.notifyConnectionError(ioe);
+            }
+        }
+    }
+
+    /**
+     * Process listeners.
+     */
+    private void processListeners() {
+        while (!done) {
+            Packet sentPacket;
+            // Wait until a new packet has been sent
+            synchronized (sentPackets) {
+                while (!done && sentPackets.size() == 0) {
+                    try {
+                        sentPackets.wait(2000);
+                    }
+                    catch (InterruptedException ie) { }
+                }
+                if (sentPackets.size() > 0) {
+                    sentPacket = (Packet)sentPackets.removeLast();
+                }
+                else {
+                    sentPacket = null;
+                }
+            }
+            if (sentPacket != null) {
+                // Clean up null entries in the listeners list if the flag is set. List
+                // removes are done seperately so that the main notification process doesn't
+                // need to synchronize on the list.
+                synchronized (listeners) {
+                    if (listenersDeleted) {
+                        for (int i=listeners.size()-1; i>=0; i--) {
+                            if (listeners.get(i) == null) {
+                                listeners.remove(i);
+                            }
+                        }
+                        listenersDeleted = false;
+                    }
+                }
+                // Notify the listeners of the new sent packet
+                int size = listeners.size();
+                for (int i=0; i<size; i++) {
+                    ListenerWrapper listenerWrapper = (ListenerWrapper)listeners.get(i);
+                    if (listenerWrapper != null) {
+                        listenerWrapper.notifyListener(sentPacket);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * A wrapper class to associate a packet filter with a listener.
+     */
+    private static class ListenerWrapper {
+
+        private PacketListener packetListener;
+        private PacketFilter packetFilter;
+
+        public ListenerWrapper(PacketListener packetListener,
+                PacketFilter packetFilter)
+        {
+            this.packetListener = packetListener;
+            this.packetFilter = packetFilter;
+        }
+
+        public boolean equals(Object object) {
+            if (object == null) {
+                return false;
+            }
+            if (object instanceof ListenerWrapper) {
+                return ((ListenerWrapper)object).packetListener.equals(this.packetListener);
+            }
+            else if (object instanceof PacketListener) {
+                return object.equals(this.packetListener);
+            }
+            return false;
+        }
+
+        public void notifyListener(Packet packet) {
+            if (packetFilter == null || packetFilter.accept(packet)) {
+                packetListener.processPacket(packet);
+            }
+        }
+    }
+
+    /**
+     * A TimerTask that keeps connections to the server alive by sending a space
+     * character on an interval.
+     */
+    private class KeepAliveTask implements Runnable {
+
+        private int delay;
+
+        public KeepAliveTask(int delay) {
+            this.delay = delay;
+        }
+
+        public void run() {
+            while (!done) {
+                synchronized (writer) {
+                    try {
+                        writer.write(" ");
+                        writer.flush();
+                    }
+                    catch (Exception e) { }
+                }
+                try {
+                    // Sleep until we should write the next keep-alive.
+                    Thread.sleep(delay);
+                }
+                catch (InterruptedException ie) { }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/Roster.java b/CopyOftrunk/source/org/jivesoftware/smack/Roster.java
new file mode 100644
index 000000000..d53570353
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/Roster.java
@@ -0,0 +1,780 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.*;
+
+/**
+ * Represents a user's roster, which is the collection of users a person receives
+ * presence updates for. Roster items are categorized into groups for easier management.<p>
+ *
+ * Others users may attempt to subscribe to this user using a subscription request. Three
+ * modes are supported for handling these requests: <ul>
+ *      <li> SUBSCRIPTION_ACCEPT_ALL -- accept all subscription requests.
+ *      <li> SUBSCRIPTION_REJECT_ALL -- reject all subscription requests.
+ *      <li> SUBSCRIPTION_MANUAL -- manually process all subscription requests. </ul>
+ *
+ * @see XMPPConnection#getRoster()
+ * @author Matt Tucker
+ */
+public class Roster {
+
+    /**
+     * Automatically accept all subscription requests. This is the default mode
+     * and is suitable for simple client. More complex client will likely wish to
+     * handle subscription requests manually.
+     */
+    public static final int SUBSCRIPTION_ACCEPT_ALL = 0;
+
+    /**
+     * Automatically reject all subscription requests.
+     */
+    public static final int SUBSCRIPTION_REJECT_ALL = 1;
+
+    /**
+     * Subscription requests are ignored, which means they must be manually
+     * processed by registering a listener for presence packets and then looking
+     * for any presence requests that have the type Presence.Type.SUBSCRIBE.
+     */
+    public static final int SUBSCRIPTION_MANUAL = 2;
+
+    /**
+     * The default subscription processing mode to use when a Roster is created. By default 
+     * all subscription requests are automatically accepted. 
+     */
+    private static int defaultSubscriptionMode = SUBSCRIPTION_ACCEPT_ALL;
+
+    private XMPPConnection connection;
+    private Map groups;
+    private List entries;
+    private List unfiledEntries;
+    private List rosterListeners;
+    private Map presenceMap;
+    // The roster is marked as initialized when at least a single roster packet
+    // has been recieved and processed.
+    boolean rosterInitialized = false;
+
+    private int subscriptionMode = getDefaultSubscriptionMode();
+
+    /**
+     * Returns the default subscription processing mode to use when a new Roster is created. The 
+     * subscription processing mode dictates what action Smack will take when subscription 
+     * requests from other users are made. The default subscription mode 
+     * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
+     * 
+     * @return the default subscription mode to use for new Rosters
+     */
+    public static int getDefaultSubscriptionMode() {
+        return defaultSubscriptionMode;
+    }
+
+    /**
+     * Sets the default subscription processing mode to use when a new Roster is created. The 
+     * subscription processing mode dictates what action Smack will take when subscription 
+     * requests from other users are made. The default subscription mode 
+     * is {@link #SUBSCRIPTION_ACCEPT_ALL}.
+     *
+     * @param subscriptionMode the default subscription mode to use for new Rosters.
+     */
+    public static void setDefaultSubscriptionMode(int subscriptionMode) {
+        defaultSubscriptionMode = subscriptionMode;
+    }
+
+    /**
+     * Creates a new roster.
+     *
+     * @param connection an XMPP connection.
+     */
+    Roster(final XMPPConnection connection) {
+        this.connection = connection;
+        groups = new Hashtable();
+        unfiledEntries = new ArrayList();
+        entries = new ArrayList();
+        rosterListeners = new ArrayList();
+        presenceMap = new HashMap();
+        // Listen for any roster packets.
+        PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class);
+        connection.addPacketListener(new RosterPacketListener(), rosterFilter);
+        // Listen for any presence packets.
+        PacketFilter presenceFilter = new PacketTypeFilter(Presence.class);
+        connection.addPacketListener(new PresencePacketListener(), presenceFilter);
+    }
+
+    /**
+     * Returns the subscription processing mode, which dictates what action
+     * Smack will take when subscription requests from other users are made.
+     * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
+     *
+     * If using the manual mode, a PacketListener should be registered that
+     * listens for Presence packets that have a type of
+     * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
+     *
+     * @return the subscription mode.
+     */
+    public int getSubscriptionMode() {
+        return subscriptionMode;
+    }
+
+    /**
+     * Sets the subscription processing mode, which dictates what action
+     * Smack will take when subscription requests from other users are made.
+     * The default subscription mode is {@link #SUBSCRIPTION_ACCEPT_ALL}.<p>
+     *
+     * If using the manual mode, a PacketListener should be registered that
+     * listens for Presence packets that have a type of
+     * {@link org.jivesoftware.smack.packet.Presence.Type#SUBSCRIBE}.
+     *
+     * @param subscriptionMode the subscription mode.
+     */
+    public void setSubscriptionMode(int subscriptionMode) {
+        if (subscriptionMode != SUBSCRIPTION_ACCEPT_ALL &&
+                subscriptionMode != SUBSCRIPTION_REJECT_ALL &&
+                subscriptionMode != SUBSCRIPTION_MANUAL)
+        {
+            throw new IllegalArgumentException("Invalid mode.");
+        }
+        this.subscriptionMode = subscriptionMode;
+    }
+
+    /**
+     * Reloads the entire roster from the server. This is an asynchronous operation,
+     * which means the method will return immediately, and the roster will be
+     * reloaded at a later point when the server responds to the reload request.
+     */
+    public void reload() {
+        connection.sendPacket(new RosterPacket());
+    }
+
+    /**
+     * Adds a listener to this roster. The listener will be fired anytime one or more
+     * changes to the roster are pushed from the server.
+     *
+     * @param rosterListener a roster listener.
+     */
+    public void addRosterListener(RosterListener rosterListener) {
+        synchronized (rosterListeners) {
+            if (!rosterListeners.contains(rosterListener)) {
+                rosterListeners.add(rosterListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from this roster. The listener will be fired anytime one or more
+     * changes to the roster are pushed from the server.
+     *
+     * @param rosterListener a roster listener.
+     */
+    public void removeRosterListener(RosterListener rosterListener) {
+        synchronized (rosterListeners) {
+            rosterListeners.remove(rosterListener);
+        }
+    }
+
+    /**
+     * Creates a new group.<p>
+     *
+     * Note: you must add at least one entry to the group for the group to be kept
+     * after a logout/login. This is due to the way that XMPP stores group information.
+     *
+     * @param name the name of the group.
+     * @return a new group.
+     */
+    public RosterGroup createGroup(String name) {
+        synchronized (groups) {
+            if (groups.containsKey(name)) {
+                throw new IllegalArgumentException("Group with name " + name + " alread exists.");
+            }
+            RosterGroup group = new RosterGroup(name, connection);
+            groups.put(name, group);
+            return group;
+        }
+    }
+
+    /**
+     * Creates a new roster entry and presence subscription. The server will asynchronously
+     * update the roster with the subscription status.
+     *
+     * @param user the user. (e.g. johndoe@jabber.org)
+     * @param name the nickname of the user.
+     * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
+     *      the roster entry won't belong to a group.
+     */
+    public void createEntry(String user, String name, String [] groups) throws XMPPException {
+        // Create and send roster entry creation packet.
+        RosterPacket rosterPacket = new RosterPacket();
+        rosterPacket.setType(IQ.Type.SET);
+        RosterPacket.Item item = new RosterPacket.Item(user, name);
+        if (groups != null) {
+            for (int i=0; i<groups.length; i++) {
+                if (groups[i] != null) {
+                    item.addGroupName(groups[i]);
+                }
+            }
+        }
+        rosterPacket.addRosterItem(item);
+        // Wait up to a certain number of seconds for a reply from the server.
+        PacketCollector collector = connection.createPacketCollector(
+                new PacketIDFilter(rosterPacket.getPacketID()));
+        connection.sendPacket(rosterPacket);
+        IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        collector.cancel();
+        if (response == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        // If the server replied with an error, throw an exception.
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+
+        // Create a presence subscription packet and send.
+        Presence presencePacket = new Presence(Presence.Type.SUBSCRIBE);
+        presencePacket.setTo(user);
+        connection.sendPacket(presencePacket);
+    }
+
+    /**
+     * Removes a roster entry from the roster. The roster entry will also be removed from the 
+     * unfiled entries or from any roster group where it could belong and will no longer be part
+     * of the roster. Note that this is an asynchronous call -- Smack must wait for the server
+     * to send an updated subscription status.
+     *
+     * @param entry a roster entry.
+     */
+    public void removeEntry(RosterEntry entry) throws XMPPException {
+        // Only remove the entry if it's in the entry list.
+        // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
+        synchronized (entries) {
+            if (!entries.contains(entry)) {
+                return;
+            }
+        }
+        RosterPacket packet = new RosterPacket();
+        packet.setType(IQ.Type.SET);
+        RosterPacket.Item item = RosterEntry.toRosterItem(entry);
+        // Set the item type as REMOVE so that the server will delete the entry
+        item.setItemType(RosterPacket.ItemType.REMOVE);
+        packet.addRosterItem(item);
+        PacketCollector collector = connection.createPacketCollector(
+        new PacketIDFilter(packet.getPacketID()));
+        connection.sendPacket(packet);
+        IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        collector.cancel();
+        if (response == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        // If the server replied with an error, throw an exception.
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+        else {
+            
+        }
+    }
+
+    /**
+     * Returns a count of the entries in the roster.
+     *
+     * @return the number of entries in the roster.
+     */
+    public int getEntryCount() {
+        HashMap entryMap = new HashMap();
+        // Loop through all roster groups.
+        for (Iterator groups = getGroups(); groups.hasNext(); ) {
+            RosterGroup rosterGroup = (RosterGroup) groups.next();
+            for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
+                entryMap.put(entries.next(), "");
+            }
+        }
+        synchronized (unfiledEntries) {
+            return entryMap.size() + unfiledEntries.size();
+        }
+    }
+
+    /**
+     * Returns all entries in the roster, including entries that don't belong to
+     * any groups.
+     *
+     * @return all entries in the roster.
+     */
+    public Iterator getEntries() {
+        ArrayList allEntries = new ArrayList();
+        // Loop through all roster groups and add their entries to the answer
+        for (Iterator groups = getGroups(); groups.hasNext(); ) {
+            RosterGroup rosterGroup = (RosterGroup) groups.next();
+            for (Iterator entries = rosterGroup.getEntries(); entries.hasNext(); ) {
+                RosterEntry entry = (RosterEntry)entries.next();
+                if (!allEntries.contains(entry)) {
+                    allEntries.add(entry);
+                }
+            }
+        }
+        // Add the roster unfiled entries to the answer
+        synchronized (unfiledEntries) {
+            allEntries.addAll(unfiledEntries);
+        }
+        return allEntries.iterator();
+    }
+
+    /**
+     * Returns a count of the unfiled entries in the roster. An unfiled entry is
+     * an entry that doesn't belong to any groups.
+     *
+     * @return the number of unfiled entries in the roster.
+     */
+    public int getUnfiledEntryCount() {
+        synchronized (unfiledEntries) {
+            return unfiledEntries.size();
+        }
+    }
+
+    /**
+     * Returns an Iterator for the unfiled roster entries. An unfiled entry is
+     * an entry that doesn't belong to any groups.
+     *
+     * @return an iterator the unfiled roster entries.
+     */
+    public Iterator getUnfiledEntries() {
+        synchronized (unfiledEntries) {
+            return Collections.unmodifiableList(new ArrayList(unfiledEntries)).iterator();
+        }
+    }
+
+    /**
+     * Returns the roster entry associated with the given XMPP address or
+     * <tt>null</tt> if the user is not an entry in the roster.
+     *
+     * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
+     * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
+     * @return the roster entry or <tt>null</tt> if it does not exist.
+     */
+    public RosterEntry getEntry(String user) {
+        if (user == null) {
+            return null;
+        }
+        synchronized (entries) {
+            for (Iterator i=entries.iterator(); i.hasNext(); ) {
+                RosterEntry entry = (RosterEntry)i.next();
+                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the specified XMPP address is an entry in the roster.
+     *
+     * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
+     * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
+     * @return true if the XMPP address is an entry in the roster.
+     */
+    public boolean contains(String user) {
+        if (user == null) {
+            return false;
+        }
+        synchronized (entries) {
+            for (Iterator i=entries.iterator(); i.hasNext(); ) {
+                RosterEntry entry = (RosterEntry)i.next();
+                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the roster group with the specified name, or <tt>null</tt> if the
+     * group doesn't exist.
+     *
+     * @param name the name of the group.
+     * @return the roster group with the specified name.
+     */
+    public RosterGroup getGroup(String name) {
+        synchronized (groups) {
+            return (RosterGroup)groups.get(name);
+        }
+    }
+
+    /**
+     * Returns the number of the groups in the roster.
+     *
+     * @return the number of groups in the roster.
+     */
+    public int getGroupCount() {
+        synchronized (groups) {
+            return groups.size();
+        }
+    }
+
+    /**
+     * Returns an iterator the for all the roster groups.
+     *
+     * @return an iterator for all roster groups.
+     */
+    public Iterator getGroups() {
+        synchronized (groups) {
+            List groupsList = Collections.unmodifiableList(new ArrayList(groups.values()));
+            return groupsList.iterator();
+        }
+    }
+
+    /**
+     * Returns the presence info for a particular user, or <tt>null</tt> if the user
+     * is unavailable (offline) or if no presence information is available, such as
+     * when you are not subscribed to the user's presence updates.<p>
+     * 
+     * If the user has several presences (one for each resource) then answer the presence
+     * with the highest priority.
+     *
+     * @param user a fully qualified xmpp ID. The address could be in any valid format (e.g.
+     * "domain/resource", "user@domain" or "user@domain/resource").
+     * @return the user's current presence, or <tt>null</tt> if the user is unavailable
+     *      or if no presence information is available..
+     */
+    public Presence getPresence(String user) {
+        String key = getPresenceMapKey(user);
+        Map userPresences = (Map) presenceMap.get(key);
+        if (userPresences == null) {
+            return null;
+        }
+        else {
+            // Find the resource with the highest priority
+            // Might be changed to use the resource with the highest availability instead.
+            Iterator it = userPresences.keySet().iterator();
+            Presence p;
+            Presence presence = null;
+
+            while (it.hasNext()) {
+                p = (Presence) userPresences.get(it.next());
+                if (presence == null) {
+                    presence = p;
+                }
+                else {
+                    if (p.getPriority() > presence.getPriority()) {
+                        presence = p;
+                    }
+                }
+            }
+            return presence;
+        }
+    }
+
+    /**                                                                                                    
+     * Returns the presence info for a particular user's resource, or <tt>null</tt> if the user
+     * is unavailable (offline) or if no presence information is available, such as
+     * when you are not subscribed to the user's presence updates.
+     *
+     * @param userResource a fully qualified xmpp ID including a resource.
+     * @return the user's current presence, or <tt>null</tt> if the user is unavailable 
+     * or if no presence information is available.
+     */
+    public Presence getPresenceResource(String userResource) {
+        String key = getPresenceMapKey(userResource);
+        String resource = StringUtils.parseResource(userResource);
+        Map userPresences = (Map)presenceMap.get(key);
+        if (userPresences == null) {
+            return null;
+        }
+        else {
+            return (Presence) userPresences.get(resource);
+        }
+    }
+
+    /**
+     * Returns an iterator (of Presence objects) for all the user's current presences
+     * or <tt>null</tt> if the user is unavailable (offline) or if no presence information
+     * is available, such as when you are not subscribed to the user's presence updates.
+     *
+     * @param user a fully qualified xmpp ID, e.g. jdoe@example.com
+     * @return an iterator (of Presence objects) for all the user's current presences,
+     *      or <tt>null</tt> if the user is unavailable or if no presence information
+     *      is available.
+     */
+    public Iterator getPresences(String user) {
+        String key = getPresenceMapKey(user);
+        Map userPresences = (Map)presenceMap.get(key);
+        if (userPresences == null) {
+            return null;
+        }
+        else {
+            synchronized (userPresences) {
+                return new HashMap(userPresences).values().iterator();
+            }
+        }
+    }
+
+    /**
+     * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster
+     * can contain any valid address format such us "domain/resource", "user@domain" or
+     * "user@domain/resource". If the roster contains an entry associated with the fully qualified
+     * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the
+     * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the
+     * userPresences is useless since it will always contain one entry for the user.
+     *
+     * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work.
+     * @return the key to use in the presenceMap for the fully qualified xmpp ID.
+     */
+    private String getPresenceMapKey(String user) {
+        String key = user;
+        if (!contains(user)) {
+            key = StringUtils.parseBareAddress(user);
+        }
+        return key;
+    }
+
+    /**
+     * Fires roster changed event to roster listeners.
+     */
+    private void fireRosterChangedEvent() {
+        RosterListener [] listeners = null;
+        synchronized (rosterListeners) {
+            listeners = new RosterListener[rosterListeners.size()];
+            rosterListeners.toArray(listeners);
+        }
+        for (int i=0; i<listeners.length; i++) {
+            listeners[i].rosterModified();
+        }
+    }
+
+    /**
+     * Fires roster presence changed event to roster listeners.
+     */
+    private void fireRosterPresenceEvent(String user) {
+        RosterListener [] listeners = null;
+        synchronized (rosterListeners) {
+            listeners = new RosterListener[rosterListeners.size()];
+            rosterListeners.toArray(listeners);
+        }
+        for (int i=0; i<listeners.length; i++) {
+            listeners[i].presenceChanged(user);
+        }
+    }
+
+    /**
+     * Listens for all presence packets and processes them.
+     */
+    private class PresencePacketListener implements PacketListener {
+        public void processPacket(Packet packet) {
+            Presence presence = (Presence)packet;
+            String from = presence.getFrom();
+            String key = getPresenceMapKey(from);
+
+            // If an "available" packet, add it to the presence map. Each presence map will hold
+            // for a particular user a map with the presence packets saved for each resource.
+            if (presence.getType() == Presence.Type.AVAILABLE) {
+                Map userPresences;
+                // Get the user presence map
+                if (presenceMap.get(key) == null) {
+                    userPresences = new HashMap();
+                    presenceMap.put(key, userPresences);
+                }
+                else {
+                    userPresences = (Map)presenceMap.get(key);
+                }
+                // Add the new presence, using the resources as a key.
+                synchronized (userPresences) {
+                    userPresences.put(StringUtils.parseResource(from), presence);
+                }
+                // If the user is in the roster, fire an event.
+                synchronized (entries) {
+                    for (Iterator i = entries.iterator(); i.hasNext();) {
+                        RosterEntry entry = (RosterEntry) i.next();
+                        if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
+                            fireRosterPresenceEvent(from);
+                        }
+                    }
+                }
+            }
+            // If an "unavailable" packet, remove any entries in the presence map.
+            else if (presence.getType() == Presence.Type.UNAVAILABLE) {
+                if (presenceMap.get(key) != null) {
+                    Map userPresences = (Map) presenceMap.get(key);
+                    synchronized (userPresences) {
+                        userPresences.remove(StringUtils.parseResource(from));
+                    }
+                    if (userPresences.isEmpty()) {
+                        presenceMap.remove(key);
+                    }
+                }
+                // If the user is in the roster, fire an event.
+                synchronized (entries) {
+                    for (Iterator i=entries.iterator(); i.hasNext(); ) {
+                        RosterEntry entry = (RosterEntry)i.next();
+                        if (entry.getUser().toLowerCase().equals(key.toLowerCase())) {
+                            fireRosterPresenceEvent(from);
+                        }
+                    }
+                }
+            }
+            else if (presence.getType() == Presence.Type.SUBSCRIBE) {
+                if (subscriptionMode == SUBSCRIPTION_ACCEPT_ALL) {
+                    // Accept all subscription requests.
+                    Presence response = new Presence(Presence.Type.SUBSCRIBED);
+                    response.setTo(presence.getFrom());
+                    connection.sendPacket(response);
+                }
+                else if (subscriptionMode == SUBSCRIPTION_REJECT_ALL) {
+                    // Reject all subscription requests.
+                    Presence response = new Presence(Presence.Type.UNSUBSCRIBED);
+                    response.setTo(presence.getFrom());
+                    connection.sendPacket(response);
+                }
+                // Otherwise, in manual mode so ignore.
+            }
+        }
+    }
+
+    /**
+     * Listens for all roster packets and processes them.
+     */
+    private class RosterPacketListener implements PacketListener {
+
+        public void processPacket(Packet packet) {
+            RosterPacket rosterPacket = (RosterPacket)packet;
+            for (Iterator i=rosterPacket.getRosterItems(); i.hasNext(); ) {
+                RosterPacket.Item item = (RosterPacket.Item)i.next();
+                RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
+                        item.getItemType(), connection);
+
+                // If the packet is of the type REMOVE then remove the entry
+                if (RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
+                    // Remove the entry from the entry list.
+                    if (entries.contains(entry)) {
+                        entries.remove(entry);
+                    }
+                    // Remove the entry from the unfiled entry list.
+                    synchronized (unfiledEntries) {
+                        if (unfiledEntries.contains(entry)) {
+                            unfiledEntries.remove(entry);
+                        }
+                    }
+                    // Removing the user from the roster, so remove any presence information
+                    // about them.
+                    String key = StringUtils.parseName(item.getUser()) + "@" +
+                            StringUtils.parseServer(item.getUser());
+                    presenceMap.remove(key);
+                }
+                else {
+                    // Make sure the entry is in the entry list.
+                    if (!entries.contains(entry)) {
+                        entries.add(entry);
+                    }
+                    else {
+                        // If the entry was in then list then update its state with the new values
+                        RosterEntry existingEntry =
+                            (RosterEntry) entries.get(entries.indexOf(entry));
+                        existingEntry.updateState(entry.getName(), entry.getType());
+                    }
+                    // If the roster entry belongs to any groups, remove it from the
+                    // list of unfiled entries.
+                    if (item.getGroupNames().hasNext()) {
+                        synchronized (unfiledEntries) {
+                            unfiledEntries.remove(entry);
+                        }
+                    }
+                    // Otherwise add it to the list of unfiled entries.
+                    else {
+                        synchronized (unfiledEntries) {
+                            if (!unfiledEntries.contains(entry)) {
+                                unfiledEntries.add(entry);
+                            }
+                        }
+                    }
+                }
+
+                // Find the list of groups that the user currently belongs to.
+                List currentGroupNames = new ArrayList();
+                for (Iterator j = entry.getGroups(); j.hasNext();  ) {
+                    RosterGroup group = (RosterGroup)j.next();
+                    currentGroupNames.add(group.getName());
+                }
+
+                // If the packet is not of the type REMOVE then add the entry to the groups
+                if (!RosterPacket.ItemType.REMOVE.equals(item.getItemType())) {
+                    // Create the new list of groups the user belongs to.
+                    List newGroupNames = new ArrayList();
+                    for (Iterator k = item.getGroupNames(); k.hasNext();  ) {
+                        String groupName = (String)k.next();
+                        // Add the group name to the list.
+                        newGroupNames.add(groupName);
+
+                        // Add the entry to the group.
+                        RosterGroup group = getGroup(groupName);
+                        if (group == null) {
+                            group = createGroup(groupName);
+                            groups.put(groupName, group);
+                        }
+                        // Add the entry.
+                        group.addEntryLocal(entry);
+                    }
+
+                    // We have the list of old and new group names. We now need to
+                    // remove the entry from the all the groups it may no longer belong
+                    // to. We do this by subracting the new group set from the old.
+                    for (int m=0; m<newGroupNames.size(); m++) {
+                        currentGroupNames.remove(newGroupNames.get(m));
+                    }
+                }
+
+                // Loop through any groups that remain and remove the entries.
+                // This is neccessary for the case of remote entry removals.
+                for (int n=0; n<currentGroupNames.size(); n++) {
+                    String groupName = (String)currentGroupNames.get(n);
+                    RosterGroup group = getGroup(groupName);
+                    group.removeEntryLocal(entry);
+                    if (group.getEntryCount() == 0) {
+                        synchronized (groups) {
+                            groups.remove(groupName);
+                        }
+                    }
+                }
+                // Remove all the groups with no entries. We have to do this because 
+                // RosterGroup.removeEntry removes the entry immediately (locally) and the 
+                // group could remain empty. 
+                // TODO Check the performance/logic for rosters with large number of groups 
+                for (Iterator it = getGroups(); it.hasNext();) {
+                    RosterGroup group = (RosterGroup)it.next();
+                    if (group.getEntryCount() == 0) {
+                        synchronized (groups) {
+                            groups.remove(group.getName());
+                        }                            
+                    }
+                }
+            }
+
+            // Mark the roster as initialized.
+            synchronized (Roster.this) {
+                rosterInitialized = true;
+                Roster.this.notifyAll();
+            }
+
+            // Fire event for roster listeners.
+            fireRosterChangedEvent();
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java
new file mode 100644
index 000000000..1698b3102
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterEntry.java
@@ -0,0 +1,173 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.RosterPacket;
+import org.jivesoftware.smack.packet.IQ;
+
+import java.util.*;
+
+/**
+ * Each user in your roster is represented by a roster entry, which contains the user's
+ * JID and a name or nickname you assign.
+ *
+ * @author Matt Tucker
+ */
+public class RosterEntry {
+
+    private String user;
+    private String name;
+    private RosterPacket.ItemType type;
+    private XMPPConnection connection;
+
+    /**
+     * Creates a new roster entry.
+     *
+     * @param user the user.
+     * @param name the nickname for the entry.
+     * @param type the subscription type.
+     * @param connection a connection to the XMPP server.
+     */
+    RosterEntry(String user, String name, RosterPacket.ItemType type, XMPPConnection connection) {
+        this.user = user;
+        this.name = name;
+        this.type = type;
+        this.connection = connection;
+    }
+
+    /**
+     * Returns the JID of the user associated with this entry.
+     *
+     * @return the user associated with this entry.
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Returns the name associated with this entry.
+     *
+     * @return the name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the name associated with this entry.
+     *
+     * @param name the name.
+     */
+    public void setName(String name) {
+        // Do nothing if the name hasn't changed.
+        if (name != null && name.equals(this.name)) {
+            return;
+        }
+        this.name = name;
+        RosterPacket packet = new RosterPacket();
+        packet.setType(IQ.Type.SET);
+        packet.addRosterItem(toRosterItem(this));
+        connection.sendPacket(packet);
+    }
+
+    /**
+     * Updates the state of the entry with the new values.
+     *
+     * @param name the nickname for the entry.
+     * @param type the subscription type.
+     */
+    void updateState(String name, RosterPacket.ItemType type) {
+        this.name = name;
+        this.type = type;
+    }
+
+    /**
+     * Returns an iterator for all the roster groups that this entry belongs to.
+     *
+     * @return an iterator for the groups this entry belongs to.
+     */
+    public Iterator getGroups() {
+        List results = new ArrayList();
+        // Loop through all roster groups and find the ones that contain this
+        // entry. This algorithm should be fine
+        for (Iterator i=connection.roster.getGroups(); i.hasNext(); ) {
+            RosterGroup group = (RosterGroup)i.next();
+            if (group.contains(this)) {
+                results.add(group);
+            }
+        }
+        return results.iterator();
+    }
+
+    /**
+     * Returns the roster subscription type of the entry. When the type is
+     * RosterPacket.ItemType.NONE, the subscription request is pending.
+     *
+     * @return the type.
+     */
+    public RosterPacket.ItemType getType() {
+        return type;
+    }
+
+    public String toString() {
+        StringBuffer buf = new StringBuffer();
+        if (name != null) {
+            buf.append(name).append(": ");
+        }
+        buf.append(user);
+        Iterator groups = getGroups();
+        if (groups.hasNext()) {
+            buf.append(" [");
+            RosterGroup group = (RosterGroup)groups.next();
+            buf.append(group.getName());
+            while (groups.hasNext()) {
+            buf.append(", ");
+                group = (RosterGroup)groups.next();
+                buf.append(group.getName());
+            }
+            buf.append("]");
+        }
+        return buf.toString();
+    }
+
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object != null && object instanceof RosterEntry) {
+            return user.toLowerCase().equals(((RosterEntry)object).getUser().toLowerCase());
+        }
+        else {
+            return false;
+        }
+    }
+
+    static RosterPacket.Item toRosterItem(RosterEntry entry) {
+        RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
+        item.setItemType(entry.getType());
+        // Set the correct group names for the item.
+        for (Iterator j=entry.getGroups(); j.hasNext(); ) {
+            RosterGroup group = (RosterGroup)j.next();
+            item.addGroupName(group.getName());
+        }
+        return item;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java
new file mode 100644
index 000000000..7f653bc96
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterGroup.java
@@ -0,0 +1,263 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.RosterPacket;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+
+import java.util.*;
+
+/**
+ * A group of roster entries.
+ *
+ * @see Roster#getGroup(String)
+ * @author Matt Tucker
+ */
+public class RosterGroup {
+
+    private String name;
+    private XMPPConnection connection;
+    private List entries;
+
+    /**
+     * Creates a new roster group instance.
+     *
+     * @param name the name of the group.
+     * @param connection the connection the group belongs to.
+     */
+    RosterGroup(String name, XMPPConnection connection) {
+        this.name = name;
+        this.connection = connection;
+        entries = new ArrayList();
+    }
+
+    /**
+     * Returns the name of the group.
+     *
+     * @return the name of the group.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the name of the group. Changing the group's name is like moving all the group entries
+     * of the group to a new group specified by the new name. Since this group won't have entries 
+     * it will be removed from the roster. This means that all the references to this object will 
+     * be invalid and will need to be updated to the new group specified by the new name.
+     *
+     * @param name the name of the group.
+     */
+    public void setName(String name) {
+        synchronized (entries) {
+            for (int i=0; i<entries.size(); i++) {
+                RosterPacket packet = new RosterPacket();
+                packet.setType(IQ.Type.SET);
+                RosterEntry entry = (RosterEntry)entries.get(i);
+                RosterPacket.Item item = RosterEntry.toRosterItem(entry);
+                item.removeGroupName(this.name);
+                item.addGroupName(name);
+                packet.addRosterItem(item);
+                connection.sendPacket(packet);
+            }
+        }
+    }
+
+    /**
+     * Returns the number of entries in the group.
+     *
+     * @return the number of entries in the group.
+     */
+    public int getEntryCount() {
+        synchronized (entries) {
+            return entries.size();
+        }
+    }
+
+    /**
+     * Returns an iterator for the entries in the group.
+     *
+     * @return an iterator for the entries in the group.
+     */
+    public Iterator getEntries() {
+        synchronized (entries) {
+            return Collections.unmodifiableList(new ArrayList(entries)).iterator();
+        }
+    }
+
+    /**
+     * Returns the roster entry associated with the given XMPP address or
+     * <tt>null</tt> if the user is not an entry in the group.
+     *
+     * @param user the XMPP address of the user (eg "jsmith@example.com").
+     * @return the roster entry or <tt>null</tt> if it does not exist in the group.
+     */
+    public RosterEntry getEntry(String user) {
+        if (user == null) {
+            return null;
+        }
+        // Roster entries never include a resource so remove the resource
+        // if it's a part of the XMPP address.
+        user = StringUtils.parseBareAddress(user);
+        synchronized (entries) {
+            for (Iterator i=entries.iterator(); i.hasNext(); ) {
+                RosterEntry entry = (RosterEntry)i.next();
+                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the specified entry is part of this group.
+     *
+     * @param entry a roster entry.
+     * @return true if the entry is part of this group.
+     */
+    public boolean contains(RosterEntry entry) {
+        synchronized (entries) {
+            return entries.contains(entry);
+        }
+    }
+
+    /**
+     * Returns true if the specified XMPP address is an entry in this group.
+     *
+     * @param user the XMPP address of the user.
+     * @return true if the XMPP address is an entry in this group.
+     */
+    public boolean contains(String user) {
+        if (user == null) {
+            return false;
+        }
+        // Roster entries never include a resource so remove the resource
+        // if it's a part of the XMPP address.
+        user = StringUtils.parseBareAddress(user);
+        synchronized (entries) {
+            for (Iterator i=entries.iterator(); i.hasNext(); ) {
+                RosterEntry entry = (RosterEntry)i.next();
+                if (entry.getUser().toLowerCase().equals(user.toLowerCase())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Adds a roster entry to this group. If the entry was unfiled then it will be removed from 
+     * the unfiled list and will be added to this group.
+     *
+     * @param entry a roster entry.
+     * @throws XMPPException if an error occured while trying to add the entry to the group.
+     */
+    public void addEntry(RosterEntry entry) throws XMPPException {
+        PacketCollector collector = null;
+        // Only add the entry if it isn't already in the list.
+        synchronized (entries) {
+            if (!entries.contains(entry)) {
+                RosterPacket packet = new RosterPacket();
+                packet.setType(IQ.Type.SET);
+                packet.addRosterItem(RosterEntry.toRosterItem(entry));
+                // Wait up to a certain number of seconds for a reply from the server.
+                collector = connection
+                        .createPacketCollector(new PacketIDFilter(packet.getPacketID()));
+                connection.sendPacket(packet);
+            }
+        }
+        if (collector != null) {
+            IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+            collector.cancel();
+            if (response == null) {
+                throw new XMPPException("No response from the server.");
+            }
+            // If the server replied with an error, throw an exception.
+            else if (response.getType() == IQ.Type.ERROR) {
+                throw new XMPPException(response.getError());
+            }
+            // Add the new entry to the group since the server processed the request successfully
+            entries.add(entry);
+        }
+    }
+
+    /**
+     * Removes a roster entry from this group. If the entry does not belong to any other group 
+     * then it will be considered as unfiled, therefore it will be added to the list of unfiled 
+     * entries.
+     *
+     * @param entry a roster entry.
+     * @throws XMPPException if an error occured while trying to remove the entry from the group. 
+     */
+    public void removeEntry(RosterEntry entry) throws XMPPException {
+        PacketCollector collector = null;
+        // Only remove the entry if it's in the entry list.
+        // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet)
+        // to take place the entry will exist in the group until a packet is received from the 
+        // server.
+        synchronized (entries) {
+            if (entries.contains(entry)) {
+                RosterPacket packet = new RosterPacket();
+                packet.setType(IQ.Type.SET);
+                RosterPacket.Item item = RosterEntry.toRosterItem(entry);
+                item.removeGroupName(this.getName());
+                packet.addRosterItem(item);
+                // Wait up to a certain number of seconds for a reply from the server.
+                collector = connection
+                        .createPacketCollector(new PacketIDFilter(packet.getPacketID()));
+                connection.sendPacket(packet);
+            }
+        }
+        if (collector != null) {
+            IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+            collector.cancel();
+            if (response == null) {
+                throw new XMPPException("No response from the server.");
+            }
+            // If the server replied with an error, throw an exception.
+            else if (response.getType() == IQ.Type.ERROR) {
+                throw new XMPPException(response.getError());
+            }
+            // Remove the entry locally since the server processed the request successfully
+            entries.remove(entry);
+        }
+    }
+
+    void addEntryLocal(RosterEntry entry) {
+        // Only add the entry if it isn't already in the list.
+        synchronized (entries) {
+            entries.remove(entry);
+            entries.add(entry);
+        }
+    }
+
+    void removeEntryLocal(RosterEntry entry) {
+         // Only remove the entry if it's in the entry list.
+        synchronized (entries) {
+            if (entries.contains(entry)) {
+                entries.remove(entry);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java b/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java
new file mode 100644
index 000000000..4d3f17e75
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/RosterListener.java
@@ -0,0 +1,44 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+/**
+ * A listener that is fired any time a roster is changed or the presence of
+ * a user in the roster is changed.
+ *
+ * @author Matt Tucker
+ */
+public interface RosterListener {
+
+    /**
+     * Called when a roster entry is added or removed.
+     */
+    public void rosterModified();
+
+    /**
+     * Called when the presence of a roster entry is changed.
+     *
+     * @param XMPPAddress the XMPP address of the user who's presence has changed,
+     *      including the resource.
+     */
+    public void presenceChanged(String XMPPAddress);
+}
+
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java b/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java
new file mode 100644
index 000000000..520f1d7d3
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/SSLXMPPConnection.java
@@ -0,0 +1,168 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import javax.net.ssl.SSLSocketFactory;
+import com.sun.net.ssl.*;
+
+import java.io.IOException;
+import java.net.*;
+import java.security.NoSuchAlgorithmException;
+import java.security.KeyManagementException;
+import javax.net.SocketFactory;
+import com.sun.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+
+/**
+ * Creates an SSL connection to a XMPP server.
+ *
+ * @author Matt Tucker
+ */
+public class SSLXMPPConnection extends XMPPConnection {
+
+    private static SocketFactory socketFactory = new DummySSLSocketFactory();
+
+    /**
+     * Creates a new SSL connection to the specified host on the default
+     * SSL port (5223).
+     *
+     * @param host the XMPP host.
+     * @throws XMPPException if an error occurs while trying to establish the connection.
+     *      Two possible errors can occur which will be wrapped by an XMPPException --
+     *      UnknownHostException (XMPP error code 504), and IOException (XMPP error code
+     *      502). The error codes and wrapped exceptions can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public SSLXMPPConnection(String host) throws XMPPException {
+        this(host, 5223);
+    }
+
+    /**
+     * Creates a new SSL connection to the specified host on the specified port.
+     *
+     * @param host the XMPP host.
+     * @param port the port to use for the connection (default XMPP SSL port is 5223).
+     * @throws XMPPException if an error occurs while trying to establish the connection.
+     *      Two possible errors can occur which will be wrapped by an XMPPException --
+     *      UnknownHostException (XMPP error code 504), and IOException (XMPP error code
+     *      502). The error codes and wrapped exceptions can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public SSLXMPPConnection(String host, int port) throws XMPPException {
+        super(host, port, socketFactory);
+    }
+
+    public boolean isSecureConnection() {
+        return true;
+    }
+
+    /**
+     * An SSL socket factory that will let any certifacte past, even if it's expired or
+     * not singed by a root CA.
+     */
+    private static class DummySSLSocketFactory extends SSLSocketFactory {
+
+        private SSLSocketFactory factory;
+
+        public DummySSLSocketFactory() {
+
+            try {
+                SSLContext sslcontent = SSLContext.getInstance("TLS");
+                sslcontent.init(null, // KeyManager not required
+                            new TrustManager[] { new DummyTrustManager() },
+                            new java.security.SecureRandom());
+                factory = sslcontent.getSocketFactory();
+            }
+            catch (NoSuchAlgorithmException e) {
+                e.printStackTrace();
+            }
+            catch (KeyManagementException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public static SocketFactory getDefault() {
+            return new DummySSLSocketFactory();
+        }
+
+        public Socket createSocket(Socket socket, String s, int i, boolean flag)
+                throws IOException
+        {
+            return factory.createSocket(socket, s, i, flag);
+        }
+
+        public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr2, int j)
+                throws IOException
+        {
+            return factory.createSocket(inaddr, i, inaddr2, j);
+        }
+
+        public Socket createSocket(InetAddress inaddr, int i) throws IOException {
+            return factory.createSocket(inaddr, i);
+        }
+
+        public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException {
+            return factory.createSocket(s, i, inaddr, j);
+        }
+
+        public Socket createSocket(String s, int i) throws IOException {
+            return factory.createSocket(s, i);
+        }
+
+        public String[] getDefaultCipherSuites() {
+            return factory.getSupportedCipherSuites();
+        }
+
+        public String[] getSupportedCipherSuites() {
+            return factory.getSupportedCipherSuites();
+        }
+    }
+
+    /**
+     * Trust manager which accepts certificates without any validation
+     * except date validation.
+     */
+    private static class DummyTrustManager implements X509TrustManager {
+
+        public boolean isClientTrusted(X509Certificate[] cert) {
+            return true;
+        }
+
+        public boolean isServerTrusted(X509Certificate[] cert) {
+            try {
+                cert[0].checkValidity();
+                return true;
+            }
+            catch (CertificateExpiredException e) {
+                return false;
+            }
+            catch (CertificateNotYetValidException e) {
+                return false;
+            }
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java b/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java
new file mode 100644
index 000000000..6ac2802cf
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/SmackConfiguration.java
@@ -0,0 +1,207 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+import org.xmlpull.v1.*;
+import org.xmlpull.mxp1.MXParser;
+
+/**
+ * Represents the configuration of Smack. The configuration is used for:
+ * <ul>
+ *      <li> Initializing classes by loading them at start-up.
+ *      <li> Getting the current Smack version.
+ *      <li> Getting and setting global library behavior, such as the period of time
+ *          to wait for replies to packets from the server. Note: setting these values
+ *          via the API will override settings in the configuration file.
+ * </ul>
+ *
+ * Configuration settings are stored in META-INF/smack-config.xml (typically inside the
+ * smack.jar file).
+ * 
+ * @author Gaston Dombiak
+ */
+public final class SmackConfiguration {
+
+    private static final String SMACK_VERSION = "1.5.1";
+
+    private static int packetReplyTimeout = 5000;
+    private static int keepAliveInterval = 30000;
+
+    private SmackConfiguration() {
+    }
+
+    /**
+     * Loads the configuration from the smack-config.xml file.<p>
+     * 
+     * So far this means that:
+     * 1) a set of classes will be loaded in order to execute their static init block
+     * 2) retrieve and set the current Smack release
+     */
+    static {
+        try {
+            // Get an array of class loaders to try loading the providers files from.
+            ClassLoader[] classLoaders = getClassLoaders();
+            for (int i = 0; i < classLoaders.length; i++) {
+                Enumeration configEnum = classLoaders[i].getResources("META-INF/smack-config.xml");
+                while (configEnum.hasMoreElements()) {
+                    URL url = (URL) configEnum.nextElement();
+                    InputStream systemStream = null;
+                    try {
+                        systemStream = url.openStream();
+                        XmlPullParser parser = new MXParser();
+                        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+                        parser.setInput(systemStream, "UTF-8");
+                        int eventType = parser.getEventType();
+                        do {
+                            if (eventType == XmlPullParser.START_TAG) {
+                                if (parser.getName().equals("className")) {
+                                    // Attempt to load the class so that the class can get initialized
+                                    parseClassToLoad(parser);
+                                }
+                                else if (parser.getName().equals("packetReplyTimeout")) {
+                                    packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout);
+                                }
+                                else if (parser.getName().equals("keepAliveInterval")) {
+                                    keepAliveInterval = parseIntProperty(parser, keepAliveInterval);
+                                }
+                            }
+                            eventType = parser.next();
+                        }
+                        while (eventType != XmlPullParser.END_DOCUMENT);
+                    }
+                    catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    finally {
+                        try {
+                            systemStream.close();
+                        }
+                        catch (Exception e) {
+                        }
+                    }
+                }
+            }
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Returns the Smack version information, eg "1.3.0".
+     * 
+     * @return the Smack version information.
+     */
+    public static String getVersion() {
+        return SMACK_VERSION;
+    }
+
+    /**
+     * Returns the number of milliseconds to wait for a response from
+     * the server. The default value is 5000 ms.
+     * 
+     * @return the milliseconds to wait for a response from the server
+     */
+    public static int getPacketReplyTimeout() {
+        // The timeout value must be greater than 0 otherwise we will answer the default value
+        if (packetReplyTimeout <= 0) {
+            packetReplyTimeout = 5000; 
+        }
+        return packetReplyTimeout;
+    }
+
+    /**
+     * Sets the number of milliseconds to wait for a response from
+     * the server.
+     * 
+     * @param timeout the milliseconds to wait for a response from the server
+     */
+    public static void setPacketReplyTimeout(int timeout) {
+        if (timeout <= 0) {
+            throw new IllegalArgumentException();
+        }
+        packetReplyTimeout = timeout;
+    }
+
+    /**
+     * Returns the number of milleseconds delay between sending keep-alive
+     * requests to the server. The default value is 30000 ms. A value of -1
+     * mean no keep-alive requests will be sent to the server.
+     *
+     * @return the milliseconds to wait between keep-alive requests, or -1 if
+     *      no keep-alive should be sent.
+     */
+    public static int getKeepAliveInterval() {
+        return keepAliveInterval;
+    }
+
+    /**
+     * Sets the number of milleseconds delay between sending keep-alive
+     * requests to the server. The default value is 30000 ms. A value of -1
+     * mean no keep-alive requests will be sent to the server.
+     *
+     * @param interval the milliseconds to wait between keep-alive requests,
+     *      or -1 if no keep-alive should be sent.
+     */
+    public static void setKeepAliveInterval(int interval) {
+        keepAliveInterval = interval;
+    }
+
+    private static void parseClassToLoad(XmlPullParser parser) throws Exception {
+        String className = parser.nextText();
+        // Attempt to load the class so that the class can get initialized
+        try {
+            Class.forName(className);
+        }
+        catch (ClassNotFoundException cnfe) {
+            System.err.println("Error! A startup class specified in smack-config.xml could " +
+                    "not be loaded: " + className);
+        }
+    }
+
+    private static int parseIntProperty(XmlPullParser parser, int defaultValue)
+            throws Exception
+    {
+        try {
+            return Integer.parseInt(parser.nextText());
+        }
+        catch (NumberFormatException nfe) {
+            nfe.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns an array of class loaders to load resources from.
+     *
+     * @return an array of ClassLoader instances.
+     */
+    private static ClassLoader[] getClassLoaders() {
+        ClassLoader[] classLoaders = new ClassLoader[2];
+        classLoaders[0] = new SmackConfiguration().getClass().getClassLoader();
+        classLoaders[1] = Thread.currentThread().getContextClassLoader();
+        return classLoaders;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java b/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java
new file mode 100644
index 000000000..83a92e253
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/XMPPConnection.java
@@ -0,0 +1,867 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.debugger.*;
+import org.jivesoftware.smack.filter.*;
+
+import javax.net.SocketFactory;
+import java.lang.reflect.Constructor;
+import java.net.*;
+import java.util.*;
+import java.io.*;
+
+/**
+ * Creates a connection to a XMPP server. A simple use of this API might
+ * look like the following:
+ * <pre>
+ * // Create a connection to the jivesoftware.com XMPP server.
+ * XMPPConnection con = new XMPPConnection("jivesoftware.com");
+ * // Most servers require you to login before performing other tasks.
+ * con.login("jsmith", "mypass");
+ * // Start a new conversation with John Doe and send him a message.
+ * Chat chat = con.createChat("jdoe@jabber.org");
+ * chat.sendMessage("Hey, how's it going?");
+ * </pre>
+ *
+ * @author Matt Tucker
+ */
+public class XMPPConnection {
+
+    /**
+     * Value that indicates whether debugging is enabled. When enabled, a debug
+     * window will apear for each new connection that will contain the following
+     * information:<ul>
+     *      <li> Client Traffic -- raw XML traffic generated by Smack and sent to the server.
+     *      <li> Server Traffic -- raw XML traffic sent by the server to the client.
+     *      <li> Interpreted Packets -- shows XML packets from the server as parsed by Smack.
+     * </ul>
+     *
+     * Debugging can be enabled by setting this field to true, or by setting the Java system
+     * property <tt>smack.debugEnabled</tt> to true. The system property can be set on the
+     * command line such as "java SomeApp -Dsmack.debugEnabled=true".
+     */
+    public static boolean DEBUG_ENABLED = false;
+
+    private static List connectionEstablishedListeners = new ArrayList();
+
+    static {
+        // Use try block since we may not have permission to get a system
+        // property (for example, when an applet).
+        try {
+            DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled");
+        }
+        catch (Exception e) {
+        }
+        // Ensure the SmackConfiguration class is loaded by calling a method in it.
+        SmackConfiguration.getVersion();
+    }
+    private SmackDebugger debugger = null;
+
+    String host;
+    int port;
+    Socket socket;
+
+    String connectionID;
+    private String user = null;
+    private boolean connected = false;
+    private boolean authenticated = false;
+    private boolean anonymous = false;
+
+    PacketWriter packetWriter;
+    PacketReader packetReader;
+
+    Roster roster = null;
+    private AccountManager accountManager = null;
+
+    Writer writer;
+    Reader reader;
+
+    /**
+     * Creates a new connection to the specified XMPP server. The default port of 5222 will
+     * be used.
+     *
+     * @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
+     * @throws XMPPException if an error occurs while trying to establish the connection.
+     *      Two possible errors can occur which will be wrapped by an XMPPException --
+     *      UnknownHostException (XMPP error code 504), and IOException (XMPP error code
+     *      502). The error codes and wrapped exceptions can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public XMPPConnection(String host) throws XMPPException {
+        this(host, 5222);
+    }
+
+    /**
+     * Creates a new connection to the specified XMPP server on the given port.
+     *
+     * @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
+     * @param port the port on the server that should be used; e.g. <tt>5222</tt>.
+     * @throws XMPPException if an error occurs while trying to establish the connection.
+     *      Two possible errors can occur which will be wrapped by an XMPPException --
+     *      UnknownHostException (XMPP error code 504), and IOException (XMPP error code
+     *      502). The error codes and wrapped exceptions can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public XMPPConnection(String host, int port) throws XMPPException {
+        this.host = host;
+        this.port = port;
+        try {
+            this.socket = new Socket(host, port);
+        }
+        catch (UnknownHostException uhe) {
+            throw new XMPPException(
+                "Could not connect to " + host + ":" + port + ".",
+                new XMPPError(504),
+                uhe);
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(
+                "XMPPError connecting to " + host + ":" + port + ".",
+                new XMPPError(502),
+                ioe);
+        }
+        init();
+    }
+
+    /**
+     * Creates a new connection to the specified XMPP server on the given port using the
+     * specified SocketFactory.<p>
+     *
+     * A custom SocketFactory allows fine-grained control of the actual connection to the
+     * XMPP server. A typical use for a custom SocketFactory is when connecting through a
+     * SOCKS proxy.
+     *
+     * @param host the name of the XMPP server to connect to; e.g. <tt>jivesoftware.com</tt>.
+     * @param port the port on the server that should be used; e.g. <tt>5222</tt>.
+     * @param socketFactory a SocketFactory that will be used to create the socket to the XMPP server.
+     * @throws XMPPException if an error occurs while trying to establish the connection.
+     *      Two possible errors can occur which will be wrapped by an XMPPException --
+     *      UnknownHostException (XMPP error code 504), and IOException (XMPP error code
+     *      502). The error codes and wrapped exceptions can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public XMPPConnection(String host, int port, SocketFactory socketFactory) throws XMPPException {
+        this.host = host;
+        this.port = port;
+        try {
+            this.socket = socketFactory.createSocket(host, port);
+        }
+        catch (UnknownHostException uhe) {
+            throw new XMPPException(
+                "Could not connect to " + host + ":" + port + ".",
+                new XMPPError(504),
+                uhe);
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(
+                "XMPPError connecting to " + host + ":" + port + ".",
+                new XMPPError(502),
+                ioe);
+        }
+        init();
+    }
+
+    /**
+     * Package-private default constructor. This constructor is only intended
+     * for unit testing. Normal classes extending XMPPConnection should override
+     * one of the other constructors.
+     */
+    XMPPConnection() {
+
+    }
+
+    /**
+     * Returns the connection ID for this connection, which is the value set by the server
+     * when opening a XMPP stream. If the server does not set a connection ID, this value
+     * will be null.
+     *
+     * @return the ID of this connection returned from the XMPP server.
+     */
+    public String getConnectionID() {
+        return connectionID;
+    }
+
+    /**
+     * Returns the host name of the XMPP server for this connection.
+     *
+     * @return the host name of the XMPP server.
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * Returns the port number of the XMPP server for this connection. The default port
+     * for normal connections is 5222. The default port for SSL connections is 5223.
+     *
+     * @return the port number of the XMPP server.
+     */
+    public int getPort() {
+        return port;
+    }
+
+    /**
+     * Returns the full XMPP address of the user that is logged in to the connection or
+     * <tt>null</tt> if not logged in yet. An XMPP address is in the form
+     * username@server/resource.
+     *
+     * @return the full XMPP address of the user logged in.
+     */
+    public String getUser() {
+        if (!isAuthenticated()) {
+            return null;
+        }
+        return user;
+    }
+
+    /**
+     * Logs in to the server using the strongest authentication mode supported by
+     * the server, then set our presence to available. If more than five seconds
+     * (default timeout) elapses in each step of the authentication process without
+     * a response from the server, or if an error occurs, a XMPPException will be thrown.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @throws XMPPException if an error occurs.
+     */
+    public void login(String username, String password) throws XMPPException {
+        login(username, password, "Smack");
+    }
+
+    /**
+     * Logs in to the server using the strongest authentication mode supported by
+     * the server, then sets presence to available. If more than five seconds
+     * (default timeout) elapses in each step of the authentication process without
+     * a response from the server, or if an error occurs, a XMPPException will be thrown.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @param resource the resource.
+     * @throws XMPPException if an error occurs.
+     * @throws IllegalStateException if not connected to the server, or already logged in
+     *      to the serrver.
+     */
+    public synchronized void login(String username, String password, String resource)
+            throws XMPPException
+    {
+        login(username, password, resource, true);
+    }
+
+    /**
+     * Logs in to the server using the strongest authentication mode supported by
+     * the server, and optionally sends an available presence. if <tt>sendPresence</tt>
+     * is false, a presence packet must be sent manually later. If more than five seconds
+     * (default timeout) elapses in each step of the authentication process without a
+     * response from the server, or if an error occurs, a XMPPException will be thrown.
+     *
+     * @param username the username.
+     * @param password the password.
+     * @param resource the resource.
+     * @param sendPresence if <tt>true</tt> an available presence will be sent automatically
+     *      after login is completed.
+     * @throws XMPPException if an error occurs.
+     * @throws IllegalStateException if not connected to the server, or already logged in
+     *      to the serrver.
+     */
+    public synchronized void login(String username, String password, String resource,
+            boolean sendPresence) throws XMPPException
+    {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        if (authenticated) {
+            throw new IllegalStateException("Already logged in to server.");
+        }
+        // Do partial version of nameprep on the username.
+        username = username.toLowerCase().trim();
+        // If we send an authentication packet in "get" mode with just the username,
+        // the server will return the list of authentication protocols it supports.
+        Authentication discoveryAuth = new Authentication();
+        discoveryAuth.setType(IQ.Type.GET);
+        discoveryAuth.setUsername(username);
+
+        PacketCollector collector =
+            packetReader.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID()));
+        // Send the packet
+        packetWriter.sendPacket(discoveryAuth);
+        // Wait up to a certain number of seconds for a response from the server.
+        IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        if (response == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        // If the server replied with an error, throw an exception.
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+        // Otherwise, no error so continue processing.
+        Authentication authTypes = (Authentication) response;
+        collector.cancel();
+
+        // Now, create the authentication packet we'll send to the server.
+        Authentication auth = new Authentication();
+        auth.setUsername(username);
+
+        // Figure out if we should use digest or plain text authentication.
+        if (authTypes.getDigest() != null) {
+            auth.setDigest(connectionID, password);
+        }
+        else if (authTypes.getPassword() != null) {
+            auth.setPassword(password);
+        }
+        else {
+            throw new XMPPException("Server does not support compatible authentication mechanism.");
+        }
+
+        auth.setResource(resource);
+
+        collector = packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID()));
+        // Send the packet.
+        packetWriter.sendPacket(auth);
+        // Wait up to a certain number of seconds for a response from the server.
+        response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        if (response == null) {
+            throw new XMPPException("Authentication failed.");
+        }
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+        // Set the user.
+        if (response.getTo() != null) {
+            this.user = response.getTo();
+        }
+        else {
+            this.user = username + "@" + this.host;
+            if (resource != null) {
+                this.user += "/" + resource;
+            }
+        }
+        // We're done with the collector, so explicitly cancel it.
+        collector.cancel();
+
+        // Create the roster.
+        this.roster = new Roster(this);
+        roster.reload();
+
+        // Set presence to online.
+        if (sendPresence) {
+            packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
+        }
+
+        // Indicate that we're now authenticated.
+        authenticated = true;
+        anonymous = false;
+
+        // If debugging is enabled, change the the debug window title to include the
+        // name we are now logged-in as.
+        // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger 
+        // will be null
+        if (DEBUG_ENABLED && debugger != null) {
+            debugger.userHasLogged(user);
+        }
+    }
+
+    /**
+     * Logs in to the server anonymously. Very few servers are configured to support anonymous
+     * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login
+     * does succeed, your XMPP address will likely be in the form "server/123ABC" (where "123ABC" is a
+     * random value generated by the server).
+     *
+     * @throws XMPPException if an error occurs or anonymous logins are not supported by the server.
+     * @throws IllegalStateException if not connected to the server, or already logged in
+     *      to the serrver.
+     */
+    public synchronized void loginAnonymously() throws XMPPException {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        if (authenticated) {
+            throw new IllegalStateException("Already logged in to server.");
+        }
+
+        // Create the authentication packet we'll send to the server.
+        Authentication auth = new Authentication();
+
+        PacketCollector collector =
+            packetReader.createPacketCollector(new PacketIDFilter(auth.getPacketID()));
+        // Send the packet.
+        packetWriter.sendPacket(auth);
+        // Wait up to a certain number of seconds for a response from the server.
+        IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        if (response == null) {
+            throw new XMPPException("Anonymous login failed.");
+        }
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+        // Set the user value.
+        if (response.getTo() != null) {
+            this.user = response.getTo();
+        }
+        else {
+            this.user = this.host + "/" + ((Authentication) response).getResource();
+        }
+        // We're done with the collector, so explicitly cancel it.
+        collector.cancel();
+
+        // Anonymous users can't have a roster.
+        roster = null;
+
+        // Set presence to online.
+        packetWriter.sendPacket(new Presence(Presence.Type.AVAILABLE));
+
+        // Indicate that we're now authenticated.
+        authenticated = true;
+        anonymous = true;
+
+        // If debugging is enabled, change the the debug window title to include the
+        // name we are now logged-in as.
+        // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger 
+        // will be null
+        if (DEBUG_ENABLED && debugger != null) {
+            debugger.userHasLogged(user);
+        }
+    }
+
+    /**
+     * Returns the roster for the user logged into the server. If the user has not yet
+     * logged into the server (or if the user is logged in anonymously), this method will return
+     * <tt>null</tt>.
+     *
+     * @return the user's roster, or <tt>null</tt> if the user has not logged in yet.
+     */
+    public Roster getRoster() {
+        if (roster == null) {
+            return null;
+        }
+        // If this is the first time the user has asked for the roster after calling
+        // login, we want to wait for the server to send back the user's roster. This
+        // behavior shields API users from having to worry about the fact that roster
+        // operations are asynchronous, although they'll still have to listen for
+        // changes to the roster. Note: because of this waiting logic, internal
+        // Smack code should be wary about calling the getRoster method, and may need to
+        // access the roster object directly.
+        if (!roster.rosterInitialized) {
+            try {
+                synchronized (roster) {
+                    long waitTime = SmackConfiguration.getPacketReplyTimeout();
+                    long start = System.currentTimeMillis();
+                    while (!roster.rosterInitialized) {
+                        if (waitTime <= 0) {
+                            break;
+                        }
+                        roster.wait(waitTime);
+                        long now = System.currentTimeMillis();
+                        waitTime -= now - start;
+                        start = now;
+                    }
+                }
+            }
+            catch (InterruptedException ie) { }
+        }
+        return roster;
+    }
+
+    /**
+     * Returns an account manager instance for this connection.
+     *
+     * @return an account manager for this connection.
+     */
+    public synchronized AccountManager getAccountManager() {
+        if (accountManager == null) {
+            accountManager = new AccountManager(this);
+        }
+        return accountManager;
+    }
+
+    /**
+     * Creates a new chat with the specified participant. The participant should
+     * be a valid XMPP user such as <tt>jdoe@jivesoftware.com</tt> or
+     * <tt>jdoe@jivesoftware.com/work</tt>.
+     *
+     * @param participant the person to start the conversation with.
+     * @return a new Chat object.
+     */
+    public Chat createChat(String participant) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        return new Chat(this, participant);
+    }
+
+    /**
+     * Creates a new group chat connected to the specified room. The room name
+     * should be full address, such as <tt>room@chat.example.com</tt>.
+     * <p>
+     * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
+     * for the XMPP server example.com). You must ensure that the room address you're
+     * trying to connect to includes the proper chat sub-domain.
+     *
+     * @param room the fully qualifed name of the room.
+     * @return a new GroupChat object.
+     */
+    public GroupChat createGroupChat(String room) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        return new GroupChat(this, room);
+    }
+
+    /**
+     * Returns true if currently connected to the XMPP server.
+     *
+     * @return true if connected.
+     */
+    public boolean isConnected() {
+        return connected;
+    }
+
+    /**
+     * Returns true if the connection is a secured one, such as an SSL connection.
+     *
+     * @return true if a secure connection to the server.
+     */
+    public boolean isSecureConnection() {
+        return false;
+    }
+
+    /**
+     * Returns true if currently authenticated by successfully calling the login method.
+     *
+     * @return true if authenticated.
+     */
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    /**
+     * Returns true if currently authenticated anonymously.
+     *
+     * @return true if authenticated anonymously.
+     */
+    public boolean isAnonymous() {
+        return anonymous;
+    }
+
+    /**
+     * Closes the connection by setting presence to unavailable then closing the stream to
+     * the XMPP server. Once a connection has been closed, it cannot be re-opened.
+     */
+    public void close() {
+        // Set presence to offline.
+        packetWriter.sendPacket(new Presence(Presence.Type.UNAVAILABLE));
+        packetReader.shutdown();
+        packetWriter.shutdown();
+        // Wait 150 ms for processes to clean-up, then shutdown.
+        try {
+            Thread.sleep(150);
+        }
+        catch (Exception e) {
+        }
+
+		// Close down the readers and writers.
+		if (reader != null)
+		{
+			try { reader.close(); } catch (Throwable ignore) { }
+			reader = null;
+		}
+		if (writer != null)
+		{
+			try { writer.close(); } catch (Throwable ignore) { }
+			writer = null;
+		}
+
+        try {
+            socket.close();
+        }
+        catch (Exception e) {
+        }
+        authenticated = false;
+        connected = false;
+    }
+
+    /**
+     * Sends the specified packet to the server.
+     *
+     * @param packet the packet to send.
+     */
+    public void sendPacket(Packet packet) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        if (packet == null) {
+            throw new NullPointerException("Packet is null.");
+        }
+        packetWriter.sendPacket(packet);
+    }
+
+    /**
+     * Registers a packet listener with this connection. A packet filter determines
+     * which packets will be delivered to the listener.
+     *
+     * @param packetListener the packet listener to notify of new packets.
+     * @param packetFilter the packet filter to use.
+     */
+    public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        packetReader.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Removes a packet listener from this connection.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketListener(PacketListener packetListener) {
+        packetReader.removePacketListener(packetListener);
+    }
+
+    /**
+     * Registers a packet listener with this connection. The listener will be
+     * notified of every packet that this connection sends. A packet filter determines
+     * which packets will be delivered to the listener.
+     *
+     * @param packetListener the packet listener to notify of sent packets.
+     * @param packetFilter the packet filter to use.
+     */
+    public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        packetWriter.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Removes a packet listener from this connection.
+     *
+     * @param packetListener the packet listener to remove.
+     */
+    public void removePacketWriterListener(PacketListener packetListener) {
+        packetWriter.removePacketListener(packetListener);
+    }
+
+    /**
+     * Creates a new packet collector for this connection. A packet filter determines
+     * which packets will be accumulated by the collector.
+     *
+     * @param packetFilter the packet filter to use.
+     * @return a new packet collector.
+     */
+    public PacketCollector createPacketCollector(PacketFilter packetFilter) {
+        return packetReader.createPacketCollector(packetFilter);
+    }
+
+    /**
+     * Adds a connection listener to this connection that will be notified when
+     * the connection closes or fails.
+     *
+     * @param connectionListener a connection listener.
+     */
+    public void addConnectionListener(ConnectionListener connectionListener) {
+        if (connectionListener == null) {
+            return;
+        }
+        synchronized (packetReader.connectionListeners) {
+            if (!packetReader.connectionListeners.contains(connectionListener)) {
+                packetReader.connectionListeners.add(connectionListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a connection listener from this connection.
+     *
+     * @param connectionListener a connection listener.
+     */
+    public void removeConnectionListener(ConnectionListener connectionListener) {
+        synchronized (packetReader.connectionListeners) {
+            packetReader.connectionListeners.remove(connectionListener);
+        }
+    }
+
+    /**
+     * Adds a connection established listener that will be notified when a new connection 
+     * is established.
+     *
+     * @param connectionEstablishedListener a listener interested on connection established events.
+     */
+    public static void addConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
+        synchronized (connectionEstablishedListeners) {
+            if (!connectionEstablishedListeners.contains(connectionEstablishedListener)) {
+                connectionEstablishedListeners.add(connectionEstablishedListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener on new established connections.
+     *
+     * @param connectionEstablishedListener a listener interested on connection established events.
+     */
+    public static void removeConnectionListener(ConnectionEstablishedListener connectionEstablishedListener) {
+        synchronized (connectionEstablishedListeners) {
+            connectionEstablishedListeners.remove(connectionEstablishedListener);
+        }
+    }
+
+    /**
+     * Initializes the connection by creating a packet reader and writer and opening a
+     * XMPP stream to the server.
+     *
+     * @throws XMPPException if establishing a connection to the server fails.
+     */
+    private void init() throws XMPPException {
+        try {
+            reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
+            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
+        }
+        catch (IOException ioe) {
+            throw new XMPPException(
+                "XMPPError establishing connection with server.",
+                new XMPPError(502),
+                ioe);
+        }
+
+        // If debugging is enabled, we open a window and write out all network traffic.
+        if (DEBUG_ENABLED) {
+            // Detect the debugger class to use.
+            String className = null;
+            // Use try block since we may not have permission to get a system
+            // property (for example, when an applet).
+            try {
+                className = System.getProperty("smack.debuggerClass");
+            }
+            catch (Throwable t) {
+            }
+            Class debuggerClass = null;
+            if (className != null) {
+                try {
+                    debuggerClass = Class.forName(className);
+                }
+                catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+            if (debuggerClass == null) {
+                try {
+                    debuggerClass =
+                            Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger");
+                }
+                catch (Exception ex) {
+                    try {
+                        debuggerClass = Class.forName("org.jivesoftware.smack.debugger.LiteDebugger");
+                    }
+                    catch (Exception ex2) {
+                        ex2.printStackTrace();
+                    }
+                }
+            }
+            // Create a new debugger instance. If an exception occurs then disable the debugging
+            // option
+            try {
+                Constructor constructor =
+                    debuggerClass.getConstructor(
+                        new Class[] { XMPPConnection.class, Writer.class, Reader.class });
+                debugger =
+                    (SmackDebugger) constructor.newInstance(new Object[] { this, writer, reader });
+                reader = debugger.getReader();
+                writer = debugger.getWriter();
+            }
+            catch (Exception e) {
+                e.printStackTrace();
+                DEBUG_ENABLED = false;
+            }
+        }
+
+		try
+		{
+            packetWriter = new PacketWriter(this);
+            packetReader = new PacketReader(this);
+
+            // If debugging is enabled, we should start the thread that will listen for
+            // all packets and then log them.
+            if (DEBUG_ENABLED) {
+                packetReader.addPacketListener(debugger.getReaderListener(), null);
+                if (debugger.getWriterListener() != null) {
+                    packetWriter.addPacketListener(debugger.getWriterListener(), null);
+                }
+            }
+            // Start the packet writer. This will open a XMPP stream to the server
+            packetWriter.startup();
+            // Start the packet reader. The startup() method will block until we
+            // get an opening stream packet back from server.
+            packetReader.startup();
+
+            // Make note of the fact that we're now connected.
+            connected = true;
+
+            // Notify that a new connection has been established
+            connectionEstablished(this);
+        }
+		catch (XMPPException ex)
+		{
+			// An exception occurred in setting up the connection. Make sure we shut down the
+			// readers and writers and close the socket.
+
+			if (packetWriter != null) {
+				try { packetWriter.shutdown(); } catch (Throwable ignore) { }
+				packetWriter = null;
+			}
+			if (packetReader != null) {
+				try { packetReader.shutdown(); } catch (Throwable ignore) { }
+				packetReader = null;
+			}
+			if (reader != null) {
+				try { reader.close(); } catch (Throwable ignore) { }
+				reader = null;
+			}
+			if (writer != null) {
+				try { writer.close(); } catch (Throwable ignore) { }
+				writer = null;
+			}
+			if (socket != null) {
+				try { socket.close(); } catch (Exception e) { }
+				socket = null;
+			}
+			authenticated = false;
+			connected = false;
+
+			throw ex;		// Everything stoppped. Now throw the exception.
+		}
+    }
+
+    /**
+     * Fires listeners on connection established events.
+     */
+    private static void connectionEstablished(XMPPConnection connection) {
+        ConnectionEstablishedListener[] listeners = null;
+        synchronized (connectionEstablishedListeners) {
+            listeners = new ConnectionEstablishedListener[connectionEstablishedListeners.size()];
+            connectionEstablishedListeners.toArray(listeners);
+        }
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].connectionEstablished(connection);
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java b/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java
new file mode 100644
index 000000000..913ccf888
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/XMPPException.java
@@ -0,0 +1,183 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.packet.XMPPError;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * A generic exception that is thrown when an error occurs performing an
+ * XMPP operation. XMPP servers can respond to error conditions with an error code
+ * and textual description of the problem, which are encapsulated in the XMPPError
+ * class. When appropriate, an XMPPError instance is attached instances of this exception.
+ *
+ * @see XMPPError
+ * @author Matt Tucker
+ */
+public class XMPPException extends Exception {
+
+    private XMPPError error = null;
+    private Throwable wrappedThrowable = null;
+
+    /**
+     * Creates a new XMPPException.
+     */
+    public XMPPException() {
+        super();
+    }
+
+    /**
+     * Creates a new XMPPException with a description of the exception.
+     *
+     * @param message description of the exception.
+     */
+    public XMPPException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new XMPPException with the Throwable that was the root cause of the
+     * exception.
+     *
+     * @param wrappedThrowable the root cause of the exception.
+     */
+    public XMPPException(Throwable wrappedThrowable) {
+        super();
+        this.wrappedThrowable = wrappedThrowable;
+    }
+
+    /**
+     * Cretaes a new XMPPException with the XMPPError that was the root case of the
+     * exception.
+     *
+     * @param error the root cause of the exception.
+     */
+    public XMPPException(XMPPError error) {
+        super();
+        this.error = error;
+    }
+
+    /**
+     * Creates a new XMPPException with a description of the exception and the
+     * Throwable that was the root cause of the exception.
+     *
+     * @param message a description of the exception.
+     * @param wrappedThrowable the root cause of the exception.
+     */
+    public XMPPException(String message, Throwable wrappedThrowable) {
+        super(message);
+        this.wrappedThrowable = wrappedThrowable;
+    }
+
+    /**
+     * Creates a new XMPPException with a description of the exception, an XMPPError,
+     * and the Throwable that was the root cause of the exception.
+     *
+     * @param message a description of the exception.
+     * @param error the root cause of the exception.
+     * @param wrappedThrowable the root cause of the exception.
+     */
+    public XMPPException(String message, XMPPError error, Throwable wrappedThrowable) {
+        super(message);
+        this.error = error;
+        this.wrappedThrowable = wrappedThrowable;
+    }
+
+    /**
+     * Creates a new XMPPException with a description of the exception and the
+     * XMPPException that was the root cause of the exception.
+     *
+     * @param message a description of the exception.
+     * @param error the root cause of the exception.
+     */
+    public XMPPException(String message, XMPPError error) {
+        super(message);
+        this.error = error;
+    }
+
+    /**
+     * Returns the XMPPError asscociated with this exception, or <tt>null</tt> if there
+     * isn't one.
+     *
+     * @return the XMPPError asscociated with this exception.
+     */
+    public XMPPError getXMPPError() {
+        return error;
+    }
+
+    /**
+     * Returns the Throwable asscociated with this exception, or <tt>null</tt> if there
+     * isn't one.
+     *
+     * @return the Throwable asscociated with this exception.
+     */
+    public Throwable getWrappedThrowable() {
+        return wrappedThrowable;
+    }
+
+    public void printStackTrace() {
+        printStackTrace(System.err);
+    }
+
+    public void printStackTrace(PrintStream out) {
+        super.printStackTrace(out);
+        if (wrappedThrowable != null) {
+            out.println("Nested Exception: ");
+            wrappedThrowable.printStackTrace(out);
+        }
+    }
+
+    public void printStackTrace(PrintWriter out) {
+        super.printStackTrace(out);
+        if (wrappedThrowable != null) {
+            out.println("Nested Exception: ");
+            wrappedThrowable.printStackTrace(out);
+        }
+    }
+
+    public String getMessage() {
+        String msg = super.getMessage();
+        // If the message was not set, but there is an XMPPError, return the
+        // XMPPError as the message.
+        if (msg == null && error != null) {
+            return error.toString();
+        }
+        return msg;
+    }
+
+    public String toString() {
+        StringBuffer buf = new StringBuffer();
+        String message = super.getMessage();
+        if (message != null) {
+            buf.append(message).append(": ");
+        }
+        if (error != null) {
+            buf.append(error);
+        }
+        if (wrappedThrowable != null) {
+            buf.append("\n  -- caused by: ").append(wrappedThrowable);
+        }
+
+        return buf.toString();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java
new file mode 100644
index 000000000..d068d9460
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/ConsoleDebugger.java
@@ -0,0 +1,145 @@
+package org.jivesoftware.smack.debugger;
+
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.*;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Very simple debugger that prints to the console (stdout) the sent and received stanzas. Use
+ * this debugger with caution since printing to the console is an expensive operation that may
+ * even block the thread since only one thread may print at a time.<p>
+ * <p/>
+ * It is possible to not only print the raw sent and received stanzas but also the interpreted
+ * packets by Smack. By default interpreted packets won't be printed. To enable this feature
+ * just change the <tt>printInterpreted</tt> static variable to <tt>true</tt>.
+ *
+ * @author Gaston Dombiak
+ */
+public class ConsoleDebugger implements SmackDebugger {
+
+    public static boolean printInterpreted = false;
+    private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
+
+    private XMPPConnection connection = null;
+
+    private PacketListener listener = null;
+    private ConnectionListener connListener = null;
+
+    private Writer writer;
+    private Reader reader;
+    private ReaderListener readerListener;
+    private WriterListener writerListener;
+
+    public ConsoleDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+        this.connection = connection;
+        this.writer = writer;
+        this.reader = reader;
+        createDebug();
+    }
+
+    /**
+     * Creates the listeners that will print in the console when new activity is detected.
+     */
+    private void createDebug() {
+        // Create a special Reader that wraps the main Reader and logs data to the GUI.
+        ObservableReader debugReader = new ObservableReader(reader);
+        readerListener = new ReaderListener() {
+            public void read(String str) {
+                System.out.println(
+                        dateFormatter.format(new Date()) + " RCV  (" + connection.hashCode() +
+                        "): " +
+                        str);
+            }
+        };
+        debugReader.addReaderListener(readerListener);
+
+        // Create a special Writer that wraps the main Writer and logs data to the GUI.
+        ObservableWriter debugWriter = new ObservableWriter(writer);
+        writerListener = new WriterListener() {
+            public void write(String str) {
+                System.out.println(
+                        dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() +
+                        "): " +
+                        str);
+            }
+        };
+        debugWriter.addWriterListener(writerListener);
+
+        // Assign the reader/writer objects to use the debug versions. The packet reader
+        // and writer will use the debug versions when they are created.
+        reader = debugReader;
+        writer = debugWriter;
+
+        // Create a thread that will listen for all incoming packets and write them to
+        // the GUI. This is what we call "interpreted" packet data, since it's the packet
+        // data as Smack sees it and not as it's coming in as raw XML.
+        listener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                if (printInterpreted) {
+                    System.out.println(
+                            dateFormatter.format(new Date()) + " RCV PKT (" +
+                            connection.hashCode() +
+                            "): " +
+                            packet.toXML());
+                }
+            }
+        };
+
+        connListener = new ConnectionListener() {
+            public void connectionClosed() {
+                System.out.println(
+                        dateFormatter.format(new Date()) + " Connection closed (" +
+                        connection.hashCode() +
+                        ")");
+            }
+
+            public void connectionClosedOnError(Exception e) {
+                System.out.println(
+                        dateFormatter.format(new Date()) +
+                        " Connection closed due to an exception (" +
+                        connection.hashCode() +
+                        ")");
+                e.printStackTrace();
+            }
+        };
+    }
+
+    public void userHasLogged(String user) {
+        boolean isAnonymous = "".equals(StringUtils.parseName(user));
+        String title =
+                "User logged (" + connection.hashCode() + "): "
+                + (isAnonymous ? "" : StringUtils.parseBareAddress(user))
+                + "@"
+                + connection.getHost()
+                + ":"
+                + connection.getPort();
+        title += "/" + StringUtils.parseResource(user);
+        System.out.println(title);
+        // Add the connection listener to the connection so that the debugger can be notified
+        // whenever the connection is closed.
+        connection.addConnectionListener(connListener);
+    }
+
+    public Reader getReader() {
+        return reader;
+    }
+
+    public Writer getWriter() {
+        return writer;
+    }
+
+    public PacketListener getReaderListener() {
+        return listener;
+    }
+
+    public PacketListener getWriterListener() {
+        return null;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java
new file mode 100644
index 000000000..c07c81557
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/LiteDebugger.java
@@ -0,0 +1,320 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.debugger;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.*;
+import java.io.*;
+
+import javax.swing.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
+
+/**
+ * The LiteDebugger is a very simple debugger that allows to debug sent, received and 
+ * interpreted messages.
+ * 
+ * @author Gaston Dombiak
+ */
+public class LiteDebugger implements SmackDebugger {
+
+    private static final String NEWLINE = "\n";
+
+    private JFrame frame = null;
+    private XMPPConnection connection = null;
+
+    private PacketListener listener = null;
+
+    private Writer writer;
+    private Reader reader;
+    private ReaderListener readerListener;
+    private WriterListener writerListener;
+
+    public LiteDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+        this.connection = connection;
+        this.writer = writer;
+        this.reader = reader;
+        createDebug();
+    }
+
+    /**
+     * Creates the debug process, which is a GUI window that displays XML traffic.
+     */
+    private void createDebug() {
+        frame = new JFrame("Smack Debug Window -- " + connection.getHost() + ":" +
+                connection.getPort());
+
+        // Add listener for window closing event 
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent evt) {
+                rootWindowClosing(evt);
+            }
+        });
+
+        // We'll arrange the UI into four tabs. The first tab contains all data, the second
+        // client generated XML, the third server generated XML, and the fourth is packet
+        // data from the server as seen by Smack.
+        JTabbedPane tabbedPane = new JTabbedPane();
+
+        JPanel allPane = new JPanel();
+        allPane.setLayout(new GridLayout(3, 1));
+        tabbedPane.add("All", allPane);
+
+        // Create UI elements for client generated XML traffic.
+        final JTextArea sentText1 = new JTextArea();
+        final JTextArea sentText2 = new JTextArea();
+        sentText1.setEditable(false);
+        sentText2.setEditable(false);
+        sentText1.setForeground(new Color(112, 3, 3));
+        sentText2.setForeground(new Color(112, 3, 3));
+        allPane.add(new JScrollPane(sentText1));
+        tabbedPane.add("Sent", new JScrollPane(sentText2));
+
+        // Add pop-up menu.
+        JPopupMenu menu = new JPopupMenu();
+        JMenuItem menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(sentText1.getText()), null);
+            }
+        });
+
+        JMenuItem menuItem2 = new JMenuItem("Clear");
+        menuItem2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                sentText1.setText("");
+                sentText2.setText("");
+            }
+        });
+
+        // Add listener to the text area so the popup menu can come up.
+        MouseListener popupListener = new PopupListener(menu);
+        sentText1.addMouseListener(popupListener);
+        sentText2.addMouseListener(popupListener);
+        menu.add(menuItem1);
+        menu.add(menuItem2);
+
+        // Create UI elements for server generated XML traffic.
+        final JTextArea receivedText1 = new JTextArea();
+        final JTextArea receivedText2 = new JTextArea();
+        receivedText1.setEditable(false);
+        receivedText2.setEditable(false);
+        receivedText1.setForeground(new Color(6, 76, 133));
+        receivedText2.setForeground(new Color(6, 76, 133));
+        allPane.add(new JScrollPane(receivedText1));
+        tabbedPane.add("Received", new JScrollPane(receivedText2));
+
+        // Add pop-up menu.
+        menu = new JPopupMenu();
+        menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(receivedText1.getText()), null);
+            }
+        });
+
+        menuItem2 = new JMenuItem("Clear");
+        menuItem2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                receivedText1.setText("");
+                receivedText2.setText("");
+            }
+        });
+
+        // Add listener to the text area so the popup menu can come up.
+        popupListener = new PopupListener(menu);
+        receivedText1.addMouseListener(popupListener);
+        receivedText2.addMouseListener(popupListener);
+        menu.add(menuItem1);
+        menu.add(menuItem2);
+
+        // Create UI elements for interpreted XML traffic.
+        final JTextArea interpretedText1 = new JTextArea();
+        final JTextArea interpretedText2 = new JTextArea();
+        interpretedText1.setEditable(false);
+        interpretedText2.setEditable(false);
+        interpretedText1.setForeground(new Color(1, 94, 35));
+        interpretedText2.setForeground(new Color(1, 94, 35));
+        allPane.add(new JScrollPane(interpretedText1));
+        tabbedPane.add("Interpreted", new JScrollPane(interpretedText2));
+
+        // Add pop-up menu.
+        menu = new JPopupMenu();
+        menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(interpretedText1.getText()), null);
+            }
+        });
+
+        menuItem2 = new JMenuItem("Clear");
+        menuItem2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                interpretedText1.setText("");
+                interpretedText2.setText("");
+            }
+        });
+
+        // Add listener to the text area so the popup menu can come up.
+        popupListener = new PopupListener(menu);
+        interpretedText1.addMouseListener(popupListener);
+        interpretedText2.addMouseListener(popupListener);
+        menu.add(menuItem1);
+        menu.add(menuItem2);
+
+        frame.getContentPane().add(tabbedPane);
+
+        frame.setSize(550, 400);
+        frame.setVisible(true);
+
+        // Create a special Reader that wraps the main Reader and logs data to the GUI.
+        ObservableReader debugReader = new ObservableReader(reader);
+        readerListener = new ReaderListener() {
+                    public void read(String str) {
+                        int index = str.lastIndexOf(">");
+                        if (index != -1) {
+                            receivedText1.append(str.substring(0, index + 1));
+                            receivedText2.append(str.substring(0, index + 1));
+                            receivedText1.append(NEWLINE);
+                            receivedText2.append(NEWLINE);
+                            if (str.length() > index) {
+                                receivedText1.append(str.substring(index + 1));
+                                receivedText2.append(str.substring(index + 1));
+                            }
+                        }
+                        else {
+                            receivedText1.append(str);
+                            receivedText2.append(str);
+                        }
+                    }
+                };
+        debugReader.addReaderListener(readerListener);
+
+        // Create a special Writer that wraps the main Writer and logs data to the GUI.
+        ObservableWriter debugWriter = new ObservableWriter(writer);
+        writerListener = new WriterListener() {
+                    public void write(String str) {
+                        sentText1.append(str);
+                        sentText2.append(str);
+                        if (str.endsWith(">")) {
+                            sentText1.append(NEWLINE);
+                            sentText2.append(NEWLINE);
+                        }
+                    }
+                };
+        debugWriter.addWriterListener(writerListener);
+
+        // Assign the reader/writer objects to use the debug versions. The packet reader
+        // and writer will use the debug versions when they are created.
+        reader = debugReader;
+        writer = debugWriter;
+
+        // Create a thread that will listen for all incoming packets and write them to
+        // the GUI. This is what we call "interpreted" packet data, since it's the packet
+        // data as Smack sees it and not as it's coming in as raw XML.
+        listener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                interpretedText1.append(packet.toXML());
+                interpretedText2.append(packet.toXML());
+                interpretedText1.append(NEWLINE);
+                interpretedText2.append(NEWLINE);
+            }
+        };
+    }
+
+    /**
+     * Notification that the root window is closing. Stop listening for received and 
+     * transmitted packets.
+     * 
+     * @param evt the event that indicates that the root window is closing 
+     */
+    public void rootWindowClosing(WindowEvent evt) {
+        connection.removePacketListener(listener);
+        ((ObservableReader)reader).removeReaderListener(readerListener);
+        ((ObservableWriter)writer).removeWriterListener(writerListener);
+    }
+
+    /**
+     * Listens for debug window popup dialog events.
+     */
+    private class PopupListener extends MouseAdapter {
+        JPopupMenu popup;
+
+        PopupListener(JPopupMenu popupMenu) {
+            popup = popupMenu;
+        }
+
+        public void mousePressed(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        private void maybeShowPopup(MouseEvent e) {
+            if (e.isPopupTrigger()) {
+                popup.show(e.getComponent(), e.getX(), e.getY());
+            }
+        }
+    }
+
+    public void userHasLogged(String user) {
+        boolean isAnonymous = "".equals(StringUtils.parseName(user));
+        String title =
+            "Smack Debug Window -- "
+                + (isAnonymous ? "" : StringUtils.parseBareAddress(user))
+                + "@"
+                + connection.getHost()
+                + ":"
+                + connection.getPort();
+        title += "/" + StringUtils.parseResource(user);
+        frame.setTitle(title);
+    }
+
+    public Reader getReader() {
+        return reader;
+    }
+
+    public Writer getWriter() {
+        return writer;
+    }
+
+    public PacketListener getReaderListener() {
+        return listener;
+    }
+
+    public PacketListener getWriterListener() {
+        return null;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java b/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java
new file mode 100644
index 000000000..8f3246c53
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/SmackDebugger.java
@@ -0,0 +1,78 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.debugger;
+
+import java.io.*;
+
+import org.jivesoftware.smack.*;
+
+/**
+ * Interface that allows for implementing classes to debug XML traffic. That is a GUI window that 
+ * displays XML traffic.<p>
+ * 
+ * Every implementation of this interface <b>must</b> have a public constructor with the following 
+ * arguments: XMPPConnection, Writer, Reader.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface SmackDebugger {
+
+    /**
+     * Called when a user has logged in to the server. The user could be an anonymous user, this 
+     * means that the user would be of the form host/resource instead of the form 
+     * user@host/resource.
+     * 
+     * @param user the user@host/resource that has just logged in
+     */
+    public abstract void userHasLogged(String user);
+
+    /**
+     * Returns the special Reader that wraps the main Reader and logs data to the GUI.
+     * 
+     * @return the special Reader that wraps the main Reader and logs data to the GUI.
+     */
+    public abstract Reader getReader();
+
+    /**
+     * Returns the special Writer that wraps the main Writer and logs data to the GUI.
+     * 
+     * @return the special Writer that wraps the main Writer and logs data to the GUI.
+     */
+    public abstract Writer getWriter();
+
+    /**
+     * Returns the thread that will listen for all incoming packets and write them to the GUI. 
+     * This is what we call "interpreted" packet data, since it's the packet data as Smack sees 
+     * it and not as it's coming in as raw XML.
+     * 
+     * @return the PacketListener that will listen for all incoming packets and write them to 
+     * the GUI
+     */
+    public abstract PacketListener getReaderListener();
+
+    /**
+     * Returns the thread that will listen for all outgoing packets and write them to the GUI. 
+     * 
+     * @return the PacketListener that will listen for all sent packets and write them to 
+     * the GUI
+     */
+    public abstract PacketListener getWriterListener();
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html b/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html
new file mode 100644
index 000000000..afb861f5e
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/debugger/package.html
@@ -0,0 +1 @@
+<body>Core debugger functionality.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java
new file mode 100644
index 000000000..2ca4a1c2b
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/AndFilter.java
@@ -0,0 +1,103 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Implements the logical AND operation over two or more packet filters.
+ * In other words, packets pass this filter if they pass <b>all</b> of the filters.
+ *
+ * @author Matt Tucker
+ */
+public class AndFilter implements PacketFilter {
+
+    /**
+     * The current number of elements in the filter.
+     */
+    private int size;
+
+    /**
+     * The list of filters.
+     */
+    private PacketFilter [] filters;
+
+    /**
+     * Creates an empty AND filter. Filters should be added using the
+     * {@link #addFilter(PacketFilter)} method.
+     */
+    public AndFilter() {
+        size = 0;
+        filters = new PacketFilter[3];
+    }
+
+    /**
+     * Creates an AND filter using the two specified filters.
+     *
+     * @param filter1 the first packet filter.
+     * @param filter2 the second packet filter.
+     */
+    public AndFilter(PacketFilter filter1, PacketFilter filter2) {
+        if (filter1 == null || filter2 == null) {
+            throw new IllegalArgumentException("Parameters cannot be null.");
+        }
+        size = 2;
+        filters = new PacketFilter[2];
+        filters[0] = filter1;
+        filters[1] = filter2;
+    }
+
+    /**
+     * Adds a filter to the filter list for the AND operation. A packet
+     * will pass the filter if all of the filters in the list accept it.
+     *
+     * @param filter a filter to add to the filter list.
+     */
+    public void addFilter(PacketFilter filter) {
+        if (filter == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        // If there is no more room left in the filters array, expand it.
+        if (size == filters.length) {
+            PacketFilter [] newFilters = new PacketFilter[filters.length+2];
+            for (int i=0; i<filters.length; i++) {
+                newFilters[i] = filters[i];
+            }
+            filters = newFilters;
+        }
+        // Add the new filter to the array.
+        filters[size] = filter;
+        size++;
+    }
+
+    public boolean accept(Packet packet) {
+        for (int i=0; i<size; i++) {
+            if (!filters[i].accept(packet)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public String toString() {
+        return filters.toString();
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java
new file mode 100644
index 000000000..7b5862184
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromContainsFilter.java
@@ -0,0 +1,54 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets where the "from" field contains a specified value.
+ *
+ * @author Matt Tucker
+ */
+public class FromContainsFilter implements PacketFilter {
+
+    private String from;
+
+    /**
+     * Creates a "from" contains filter using the "from" field part.
+     *
+     * @param from the from field value the packet must contain.
+     */
+    public FromContainsFilter(String from) {
+        if (from == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        this.from = from.toLowerCase();
+    }
+
+    public boolean accept(Packet packet) {
+        if (packet.getFrom() == null) {
+            return false;
+        }
+        else {
+            return packet.getFrom().toLowerCase().indexOf(from) != -1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java
new file mode 100644
index 000000000..1562d5cac
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/FromMatchesFilter.java
@@ -0,0 +1,71 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Filter for packets where the "from" field exactly matches a specified JID. If the specified
+ * address is a bare JID then the filter will match any address whose bare JID matches the
+ * specified JID. But if the specified address is a full JID then the filter will only match
+ * if the sender of the packet matches the specified resource.
+ *
+ * @author Gaston Dombiak
+ */
+public class FromMatchesFilter implements PacketFilter {
+
+    private String address;
+    /**
+     * Flag that indicates if the checking will be done against bare JID addresses or full JIDs.
+     */
+    private boolean matchBareJID = false;
+
+    /**
+     * Creates a "from" filter using the "from" field part. If the specified address is a bare JID
+     * then the filter will match any address whose bare JID matches the specified JID. But if the
+     * specified address is a full JID then the filter will only match if the sender of the packet
+     * matches the specified resource.
+     *
+     * @param address the from field value the packet must match. Could be a full or bare JID.
+     */
+    public FromMatchesFilter(String address) {
+        if (address == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        this.address = address.toLowerCase();
+        matchBareJID = "".equals(StringUtils.parseResource(address));
+    }
+
+    public boolean accept(Packet packet) {
+        if (packet.getFrom() == null) {
+            return false;
+        }
+        else if (matchBareJID) {
+            // Check if the bare JID of the sender of the packet matches the specified JID
+            return packet.getFrom().toLowerCase().startsWith(address);
+        }
+        else {
+            // Check if the full JID of the sender of the packet matches the specified JID
+            return address.equals(packet.getFrom().toLowerCase());
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java
new file mode 100644
index 000000000..618ca6793
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/MessageTypeFilter.java
@@ -0,0 +1,54 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets of a specific type of Message (e.g. CHAT).
+ * 
+ * @see org.jivesoftware.smack.packet.Message.Type
+ * @author Ward Harold
+ */
+public class MessageTypeFilter implements PacketFilter {
+
+    private final Message.Type type;
+
+    /**
+     * Creates a new message type filter using the specified message type.
+     * 
+     * @param type the message type.
+     */
+    public MessageTypeFilter(Message.Type type) {
+        this.type = type;
+    }
+
+    public boolean accept(Packet packet) {
+        if (!(packet instanceof Message)) {
+            return false;
+        }
+        else {
+            return ((Message) packet).getType().equals(this.type);
+        }
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java
new file mode 100644
index 000000000..4e6e5494c
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/NotFilter.java
@@ -0,0 +1,50 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Implements the logical NOT operation on a packet filter. In other words, packets
+ * pass this filter if they do not pass the supplied filter.
+ *
+ * @author Matt Tucker
+ */
+public class NotFilter implements PacketFilter {
+
+    private PacketFilter filter;
+
+    /**
+     * Creates a NOT filter using the specified filter.
+     *
+     * @param filter the filter.
+     */
+    public NotFilter(PacketFilter filter) {
+        if (filter == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        this.filter = filter;
+    }
+
+    public boolean accept(Packet packet) {
+        return !filter.accept(packet);
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java
new file mode 100644
index 000000000..22c3d91af
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/OrFilter.java
@@ -0,0 +1,103 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Implements the logical OR operation over two or more packet filters. In
+ * other words, packets pass this filter if they pass <b>any</b> of the filters.
+ *
+ * @author Matt Tucker
+ */
+public class OrFilter implements PacketFilter {
+
+    /**
+     * The current number of elements in the filter.
+     */
+    private int size;
+
+    /**
+     * The list of filters.
+     */
+    private PacketFilter [] filters;
+
+    /**
+     * Creates an empty OR filter. Filters should be added using the
+     * {@link #addFilter(PacketFilter)} method.
+     */
+    public OrFilter() {
+        size = 0;
+        filters = new PacketFilter[3];
+    }
+
+    /**
+     * Creates an OR filter using the two specified filters.
+     *
+     * @param filter1 the first packet filter.
+     * @param filter2 the second packet filter.
+     */
+    public OrFilter(PacketFilter filter1, PacketFilter filter2) {
+        if (filter1 == null || filter2 == null) {
+            throw new IllegalArgumentException("Parameters cannot be null.");
+        }
+        size = 2;
+        filters = new PacketFilter[2];
+        filters[0] = filter1;
+        filters[1] = filter2;
+    }
+
+    /**
+     * Adds a filter to the filter list for the OR operation. A packet
+     * will pass the filter if any filter in the list accepts it.
+     *
+     * @param filter a filter to add to the filter list.
+     */
+    public void addFilter(PacketFilter filter) {
+        if (filter == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        // If there is no more room left in the filters array, expand it.
+        if (size == filters.length) {
+            PacketFilter [] newFilters = new PacketFilter[filters.length+2];
+            for (int i=0; i<filters.length; i++) {
+                newFilters[i] = filters[i];
+            }
+            filters = newFilters;
+        }
+        // Add the new filter to the array.
+        filters[size] = filter;
+        size++;
+    }
+
+    public boolean accept(Packet packet) {
+        for (int i=0; i<size; i++) {
+            if (filters[i].accept(packet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public String toString() {
+        return filters.toString();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java
new file mode 100644
index 000000000..b46c118cc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java
@@ -0,0 +1,51 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets with a particular type of packet extension.
+ *
+ * @author Matt Tucker
+ */
+public class PacketExtensionFilter implements PacketFilter {
+
+    private String elementName;
+    private String namespace;
+
+    /**
+     * Creates a new packet extension filter. Packets will pass the filter if
+     * they have a packet extension that matches the specified element name
+     * and namespace.
+     *
+     * @param elementName the XML element name of the packet extension.
+     * @param namespace the XML namespace of the packet extension.
+     */
+    public PacketExtensionFilter(String elementName, String namespace) {
+        this.elementName = elementName;
+        this.namespace = namespace;
+    }
+
+    public boolean accept(Packet packet) {
+        return packet.getExtension(elementName, namespace) != null;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java
new file mode 100644
index 000000000..fbdcac5a1
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketFilter.java
@@ -0,0 +1,63 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Defines a way to filter packets for particular attributes. Packet filters are
+ * used when constructing packet listeners or collectors -- the filter defines
+ * what packets match the criteria of the collector or listener for further
+ * packet processing.<p>
+ *
+ * Several pre-defined filters are defined. These filters can be logically combined
+ * for more complex packet filtering by using the
+ * {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and
+ * {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible
+ * to define your own filters by implementing this interface. The code example below
+ * creates a trivial filter for packets with a specific ID.
+ *
+ * <pre>
+ * // Use an anonymous inner class to define a packet filter that returns
+ * // all packets that have a packet ID of "RS145".
+ * PacketFilter myFilter = new PacketFilter() {
+ *     public boolean accept(Packet packet) {
+ *         return "RS145".equals(packet.getPacketID());
+ *     }
+ * };
+ * // Create a new packet collector using the filter we created.
+ * PacketCollector myCollector = packetReader.createPacketCollector(myFilter);
+ * </pre>
+ *
+ * @see org.jivesoftware.smack.PacketCollector
+ * @see org.jivesoftware.smack.PacketListener
+ * @author Matt Tucker
+ */
+public interface PacketFilter {
+
+    /**
+     * Tests whether or not the specified packet should pass the filter.
+     *
+     * @param packet the packet to test.
+     * @return true if and only if <tt>packet</tt> passes the filter.
+     */
+    public boolean accept(Packet packet);
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java
new file mode 100644
index 000000000..03a007f37
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketIDFilter.java
@@ -0,0 +1,49 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets with a particular packet ID.
+ *
+ * @author Matt Tucker
+ */
+public class PacketIDFilter implements PacketFilter {
+
+    private String packetID;
+
+    /**
+     * Creates a new packet ID filter using the specified packet ID.
+     *
+     * @param packetID the packet ID to filter for.
+     */
+    public PacketIDFilter(String packetID) {
+        if (packetID == null) {
+            throw new IllegalArgumentException("Packet ID cannot be null.");
+        }
+        this.packetID = packetID;
+    }
+
+    public boolean accept(Packet packet) {
+        return packetID.equals(packet.getPacketID());
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java
new file mode 100644
index 000000000..1a736e114
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/PacketTypeFilter.java
@@ -0,0 +1,58 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets of a particular type. The type is given as a Class object, so
+ * example types would:
+ * <ul>
+ *      <li><tt>Message.class</tt>
+ *      <li><tt>IQ.class</tt>
+ *      <li><tt>Presence.class</tt>
+ * </ul>
+ *
+ * @author Matt Tucker
+ */
+public class PacketTypeFilter implements PacketFilter {
+
+    Class packetType;
+
+    /**
+     * Creates a new packet type filter that will filter for packets that are the
+     * same type as <tt>packetType</tt>.
+     *
+     * @param packetType the Class type.
+     */
+    public PacketTypeFilter(Class packetType) {
+        // Ensure the packet type is a sub-class of Packet.
+        if (!Packet.class.isAssignableFrom(packetType)) {
+            throw new IllegalArgumentException("Packet type must be a sub-class of Packet.");
+        }
+        this.packetType = packetType;
+    }
+
+    public boolean accept(Packet packet) {
+        return packetType.isInstance(packet);
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java
new file mode 100644
index 000000000..b9c296d11
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/ThreadFilter.java
@@ -0,0 +1,55 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * Filters for message packets with a particular thread value.
+ *
+ * @author Matt Tucker
+ */
+public class ThreadFilter implements PacketFilter {
+
+    private String thread;
+
+    /**
+     * Creates a new thread filter using the specified thread value.
+     *
+     * @param thread the thread value to filter for.
+     */
+    public ThreadFilter(String thread) {
+        if (thread == null) {
+            throw new IllegalArgumentException("Thread cannot be null.");
+        }
+        this.thread = thread;
+    }
+
+    public boolean accept(Packet packet) {
+        if (packet instanceof Message) {
+            return thread.equals(((Message)packet).getThread());
+        }
+        else {
+            return false;
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java b/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java
new file mode 100644
index 000000000..1ec8a8a05
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/ToContainsFilter.java
@@ -0,0 +1,55 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.filter;
+
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Filters for packets where the "to" field contains a specified value. For example,
+ * the filter could be used to listen for all packets sent to a group chat nickname.
+ *
+ * @author Matt Tucker
+ */
+public class ToContainsFilter implements PacketFilter {
+
+    private String to;
+
+    /**
+     * Creates a "to" contains filter using the "to" field part.
+     *
+     * @param to the to field value the packet must contain.
+     */
+    public ToContainsFilter(String to) {
+        if (to == null) {
+            throw new IllegalArgumentException("Parameter cannot be null.");
+        }
+        this.to = to.toLowerCase();
+    }
+
+    public boolean accept(Packet packet) {
+        if (packet.getTo() == null) {
+            return false;
+        }
+        else {
+            return packet.getTo().toLowerCase().indexOf(to) != -1;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html b/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html
new file mode 100644
index 000000000..8b3fe8034
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/filter/package.html
@@ -0,0 +1 @@
+<body>Allows {@link org.jivesoftware.smack.PacketCollector} and {@link org.jivesoftware.smack.PacketListener} instances to filter for packets with particular attributes.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/package.html b/CopyOftrunk/source/org/jivesoftware/smack/package.html
new file mode 100644
index 000000000..2758d781a
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/package.html
@@ -0,0 +1 @@
+<body>Core classes of the Smack API.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java
new file mode 100644
index 000000000..3ff5b8a87
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Authentication.java
@@ -0,0 +1,186 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Authentication packet, which can be used to login to a XMPP server as well
+ * as discover login information from the server.
+ */
+public class Authentication extends IQ {
+
+    private String username = null;
+    private String password = null;
+    private String digest = null;
+    private String resource = null;
+
+    /**
+     * Create a new authentication packet. By default, the packet will be in
+     * "set" mode in order to perform an actual authentication with the server.
+     * In order to send a "get" request to get the available authentication
+     * modes back from the server, change the type of the IQ packet to "get":
+     *
+     * <p><tt>setType(IQ.Type.GET);</tt>
+     */
+    public Authentication() {
+        setType(IQ.Type.SET);
+    }
+
+    /**
+     * Returns the username, or <tt>null</tt> if the username hasn't been sent.
+     *
+     * @return the username.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Sets the username.
+     *
+     * @param username the username.
+     */
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    /**
+     * Returns the plain text password or <tt>null</tt> if the password hasn't
+     * been set.
+     *
+     * @return the password.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the plain text password.
+     *
+     * @param password the password.
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * Returns the password digest or <tt>null</tt> if the digest hasn't
+     * been set. Password digests offer a more secure alternative for
+     * authentication compared to plain text. The digest is the hex-encoded
+     * SHA-1 hash of the connection ID plus the user's password. If the
+     * digest and password are set, digest authentication will be used. If
+     * only one value is set, the respective authentication mode will be used.
+     *
+     * @return the digest of the user's password.
+     */
+    public String getDigest() {
+        return digest;
+    }
+
+    /**
+     * Sets the digest value using a connection ID and password. Password
+     * digests offer a more secure alternative for authentication compared to
+     * plain text. The digest is the hex-encoded SHA-1 hash of the connection ID
+     * plus the user's password. If the digest and password are set, digest
+     * authentication will be used. If only one value is set, the respective
+     * authentication mode will be used.
+     *
+     * @param connectionID the connection ID.
+     * @param password the password.
+     * @see org.jivesoftware.smack.XMPPConnection#getConnectionID()
+     */
+    public void setDigest(String connectionID, String password) {
+        this.digest = StringUtils.hash(connectionID + password);
+    }
+
+    /**
+     * Sets the digest value directly. Password digests offer a more secure
+     * alternative for authentication compared to plain text. The digest is
+     * the hex-encoded SHA-1 hash of the connection ID plus the user's password.
+     * If the digest and password are set, digest authentication will be used.
+     * If only one value is set, the respective authentication mode will be used.
+     *
+     * @param digest the digest, which is the SHA-1 hash of the connection ID
+     *      the user's password, encoded as hex.
+     * @see org.jivesoftware.smack.XMPPConnection#getConnectionID()
+     */
+    public void setDigest(String digest) {
+        this.digest = digest;
+    }
+
+    /**
+     * Returns the resource or <tt>null</tt> if the resource hasn't been set.
+     *
+     * @return the resource.
+     */
+    public String getResource() {
+        return resource;
+    }
+
+    /**
+     * Sets the resource.
+     *
+     * @param resource the resource.
+     */
+    public void setResource(String resource) {
+        this.resource = resource;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"jabber:iq:auth\">");
+        if (username != null) {
+            if (username.equals("")) {
+                buf.append("<username/>");
+            }
+            else {
+                buf.append("<username>").append( username).append("</username>");
+            }
+        }
+        if (digest != null) {
+            if (digest.equals("")) {
+                buf.append("<digest/>");
+            }
+            else {
+                buf.append("<digest>").append(digest).append("</digest>");
+            }
+        }
+        if (password != null && digest == null) {
+            if (password.equals("")) {
+                buf.append("<password/>");
+            }
+            else {
+                buf.append("<password>").append(password).append("</password>");
+            }
+        }
+        if (resource != null) {
+            if (resource.equals("")) {
+                buf.append("<resource/>");
+            }
+            else {
+                buf.append("<resource>").append(resource).append("</resource>");
+            }
+        }
+        buf.append("</query>");
+        return buf.toString();
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java
new file mode 100644
index 000000000..cbf1b5efe
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/DefaultPacketExtension.java
@@ -0,0 +1,134 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import java.util.*;
+
+/**
+ * Default implementation of the PacketExtension interface. Unless a PacketExtensionProvider
+ * is registered with {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager},
+ * instances of this class will be returned when getting packet extensions.<p>
+ *
+ * This class provides a very simple representation of an XML sub-document. Each element
+ * is a key in a Map with its CDATA being the value. For example, given the following
+ * XML sub-document:
+ *
+ * <pre>
+ * &lt;foo xmlns="http://bar.com"&gt;
+ *     &lt;color&gt;blue&lt;/color&gt;
+ *     &lt;food&gt;pizza&lt;/food&gt;
+ * &lt;/foo&gt;</pre>
+ *
+ * In this case, getValue("color") would return "blue", and getValue("food") would
+ * return "pizza". This parsing mechanism mechanism is very simplistic and will not work
+ * as desired in all cases (for example, if some of the elements have attributes. In those
+ * cases, a custom PacketExtensionProvider should be used.
+ *
+ * @author Matt Tucker
+ */
+public class DefaultPacketExtension implements PacketExtension {
+
+    private String elementName;
+    private String namespace;
+    private Map map;
+
+    /**
+     * Creates a new generic packet extension.
+     *
+     * @param elementName the name of the element of the XML sub-document.
+     * @param namespace the namespace of the element.
+     */
+    public DefaultPacketExtension(String elementName, String namespace) {
+        this.elementName = elementName;
+        this.namespace = namespace;
+    }
+
+     /**
+     * Returns the XML element name of the extension sub-packet root element.
+     *
+     * @return the XML element name of the packet extension.
+     */
+    public String getElementName() {
+        return elementName;
+    }
+
+    /**
+     * Returns the XML namespace of the extension sub-packet root element.
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">");
+        for (Iterator i=getNames(); i.hasNext(); ) {
+            String name = (String)i.next();
+            String value = getValue(name);
+            buf.append("<").append(name).append(">");
+            buf.append(value);
+            buf.append("</").append(name).append(">");
+        }
+        buf.append("</").append(elementName).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * Returns an Iterator for the names that can be used to get
+     * values of the packet extension.
+     *
+     * @return an Iterator for the names.
+     */
+    public synchronized Iterator getNames() {
+        if (map == null) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator();
+    }
+
+    /**
+     * Returns a packet extension value given a name.
+     *
+     * @param name the name.
+     * @return the value.
+     */
+    public synchronized String getValue(String name) {
+        if (map == null) {
+            return null;
+        }
+        return (String)map.get(name);
+    }
+
+    /**
+     * Sets a packet extension value using the given name.
+     *
+     * @param name the name.
+     * @param value the value.
+     */
+    public synchronized void setValue(String name, String value) {
+        if (map == null) {
+            map = new HashMap();
+        }
+        map.put(name, value);
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java
new file mode 100644
index 000000000..926e4e422
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/IQ.java
@@ -0,0 +1,167 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * The base IQ (Info/Query) packet. IQ packets are used to get and set information
+ * on the server, including authentication, roster operations, and creating
+ * accounts. Each IQ packet has a specific type that indicates what type of action
+ * is being taken: "get", "set", "result", or "error".<p>
+ *
+ * IQ packets can contain a single child element that exists in a specific XML
+ * namespace. The combination of the element name and namespace determines what
+ * type of IQ packet it is. Some example IQ subpacket snippets:<ul>
+ *
+ *  <li>&lt;query xmlns="jabber:iq:auth"&gt; -- an authentication IQ.
+ *  <li>&lt;query xmlns="jabber:iq:private"&gt; -- a private storage IQ.
+ *  <li>&lt;pubsub xmlns="http://jabber.org/protocol/pubsub"&gt; -- a pubsub IQ.
+ * </ul>
+ *
+ * @author Matt Tucker
+ */
+public abstract class IQ extends Packet {
+
+    private Type type = Type.GET;
+
+    /**
+     * Returns the type of the IQ packet.
+     *
+     * @return the type of the IQ packet.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Sets the type of the IQ packet.
+     *
+     * @param type the type of the IQ packet.
+     */
+    public void setType(Type type) {
+        if (type == null) {
+            this.type = Type.GET;
+        }
+        else {
+            this.type = type;
+        }
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<iq ");
+        if (getPacketID() != null) {
+            buf.append("id=\"" + getPacketID() + "\" ");
+        }
+        if (getTo() != null) {
+            buf.append("to=\"").append(StringUtils.escapeForXML(getTo())).append("\" ");
+        }
+        if (getFrom() != null) {
+            buf.append("from=\"").append(StringUtils.escapeForXML(getFrom())).append("\" ");
+        }
+        if (type == null) {
+            buf.append("type=\"get\">");
+        }
+        else {
+            buf.append("type=\"").append(getType()).append("\">");
+        }
+        // Add the query section if there is one.
+        String queryXML = getChildElementXML();
+        if (queryXML != null) {
+            buf.append(queryXML);
+        }
+        // Add the error sub-packet, if there is one.
+        XMPPError error = getError();
+        if (error != null) {
+            buf.append(error.toXML());
+        }
+        buf.append("</iq>");
+        return buf.toString();
+    }
+
+    /**
+     * Returns the sub-element XML section of the IQ packet, or <tt>null</tt> if there
+     * isn't one. Packet extensions <b>must</b> be included, if any are defined.<p>
+     *
+     * Extensions of this class must override this method.
+     *
+     * @return the child element section of the IQ XML.
+     */
+    public abstract String getChildElementXML();
+
+    /**
+     * A class to represent the type of the IQ packet. The types are:
+     *
+     * <ul>
+     *      <li>IQ.Type.GET
+     *      <li>IQ.Type.SET
+     *      <li>IQ.Type.RESULT
+     *      <li>IQ.Type.ERROR
+     * </ul>
+     */
+    public static class Type {
+
+        public static final Type GET = new Type("get");
+        public static final Type SET = new Type("set");
+        public static final Type RESULT = new Type("result");
+        public static final Type ERROR = new Type("error");
+
+        /**
+         * Converts a String into the corresponding types. Valid String values
+         * that can be converted to types are: "get", "set", "result", and "error".
+         *
+         * @param type the String value to covert.
+         * @return the corresponding Type.
+         */
+        public static Type fromString(String type) {
+            if (type == null) {
+                return null;
+            }
+            type = type.toLowerCase();
+            if (GET.toString().equals(type)) {
+                return GET;
+            }
+            else if (SET.toString().equals(type)) {
+                return SET;
+            }
+            else if (ERROR.toString().equals(type)) {
+                return ERROR;
+            }
+            else if (RESULT.toString().equals(type)) {
+                return RESULT;
+            }
+            else {
+                return null;
+            }
+        }
+
+        private String value;
+
+        private Type(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java
new file mode 100644
index 000000000..193b24d1f
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Message.java
@@ -0,0 +1,273 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Represents XMPP message packets. A message can be one of several types:
+ *
+ * <ul>
+ *      <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
+ *      <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
+ *      <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
+ *      <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
+ *      <li>Message.Type.ERROR -- indicates a messaging error.
+ * </ul>
+ *
+ * For each message type, different message fields are typically used as follows:
+ * <p>
+ * <table border="1">
+ * <tr><td>&nbsp;</td><td colspan="5"><b>Message type</b></td></tr>
+ * <tr><td><i>Field</i></td><td><b>Normal</b></td><td><b>Chat</b></td><td><b>Group Chat</b></td><td><b>Headline</b></td><td><b>XMPPError</b></td></tr>
+ * <tr><td><i>subject</i></td> <td>SHOULD</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td></tr>
+ * <tr><td><i>thread</i></td>  <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr>
+ * <tr><td><i>body</i></td>    <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr>
+ * <tr><td><i>error</i></td>   <td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST</td></tr>
+ * </table>
+ *
+ * @author Matt Tucker
+ */
+public class Message extends Packet {
+
+    private Type type = Type.NORMAL;
+    private String subject = null;
+    private String body = null;
+    private String thread = null;
+
+    /**
+     * Creates a new, "normal" message.
+     */
+    public Message() {
+    }
+
+    /**
+     * Creates a new "normal" message to the specified recipient.
+     *
+     * @param to the recipient of the message.
+     */
+    public Message(String to) {
+        if (to == null) {
+            throw new IllegalArgumentException("Parameter cannot be null");
+        }
+        setTo(to);
+    }
+
+    /**
+     * Creates a new message of the specified type to a recipient.
+     *
+     * @param to the user to send the message to.
+     * @param type the message type.
+     */
+    public Message(String to, Type type) {
+        if (to == null || type == null) {
+            throw new IllegalArgumentException("Parameters cannot be null.");
+        }
+        setTo(to);
+        this.type = type;
+    }
+
+    /**
+     * Returns the type of the message.
+     *
+     * @return the type of the message.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Sets the type of the message.
+     *
+     * @param type the type of the message.
+     */
+    public void setType(Type type) {
+        if (type == null) {
+            throw new IllegalArgumentException("Type cannot be null.");
+        }
+        this.type = type;
+    }
+
+    /**
+     * Returns the subject of the message, or null if the subject has not been set.
+     * The subject is a short description of message contents.
+     *
+     * @return the subject of the message.
+     */
+    public String getSubject() {
+        return subject;
+    }
+
+    /**
+     * Sets the subject of the message. The subject is a short description of
+     * message contents.
+     *
+     * @param subject the subject of the message.
+     */
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    /**
+     * Returns the body of the message, or null if the body has not been set. The body
+     * is the main message contents.
+     *
+     * @return the body of the message.
+     */
+    public String getBody() {
+        return body;
+    }
+
+    /**
+     * Sets the body of the message. The body is the main message contents.
+     * @param body
+     */
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    /**
+     * Returns the thread id of the message, which is a unique identifier for a sequence
+     * of "chat" messages. If no thread id is set, <tt>null</tt> will be returned.
+     *
+     * @return the thread id of the message, or <tt>null</tt> if it doesn't exist.
+     */
+    public String getThread() {
+        return thread;
+    }
+
+    /**
+     * Sets the thread id of the message, which is a unique identifier for a sequence
+     * of "chat" messages.
+     *
+     * @param thread the thread id of the message.
+     */
+    public void setThread(String thread) {
+        this.thread = thread;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<message");
+        if (getPacketID() != null) {
+            buf.append(" id=\"").append(getPacketID()).append("\"");
+        }
+        if (getTo() != null) {
+            buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
+        }
+        if (getFrom() != null) {
+            buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
+        }
+        if (type != Type.NORMAL) {
+            buf.append(" type=\"").append(type).append("\"");
+        }
+        buf.append(">");
+        if (subject != null) {
+            buf.append("<subject>").append(StringUtils.escapeForXML(subject)).append("</subject>");
+        }
+        if (body != null) {
+            buf.append("<body>").append(StringUtils.escapeForXML(body)).append("</body>");
+        }
+        if (thread != null) {
+            buf.append("<thread>").append(thread).append("</thread>");
+        }
+        // Append the error subpacket if the message type is an error.
+        if (type == Type.ERROR) {
+            XMPPError error = getError();
+            if (error != null) {
+                buf.append(error.toXML());
+            }
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</message>");
+        return buf.toString();
+    }
+
+    /**
+     * Represents the type of a message.
+     */
+    public static class Type {
+
+        /**
+         * (Default) a normal text message used in email like interface.
+         */
+        public static final Type NORMAL = new Type("normal");
+
+        /**
+         * Typically short text message used in line-by-line chat interfaces.
+         */
+        public static final Type CHAT = new Type("chat");
+
+        /**
+         * Chat message sent to a groupchat server for group chats.
+         */
+        public static final Type GROUP_CHAT = new Type("groupchat");
+
+        /**
+         * Text message to be displayed in scrolling marquee displays.
+         */
+        public static final Type HEADLINE = new Type("headline");
+
+        /**
+         * indicates a messaging error.
+         */
+        public static final Type ERROR = new Type("error");
+
+        /**
+         * Converts a String value into its Type representation.
+         *
+         * @param type the String value.
+         * @return the Type corresponding to the String.
+         */
+        public static Type fromString(String type) {
+            if (type == null) {
+                return NORMAL;
+            }
+            type = type.toLowerCase();
+            if (CHAT.toString().equals(type)) {
+                return CHAT;
+            }
+            else if (GROUP_CHAT.toString().equals(type)) {
+                return GROUP_CHAT;
+            }
+            else if (HEADLINE.toString().equals(type)) {
+                return HEADLINE;
+            }
+            else if (ERROR.toString().equals(type)) {
+                return ERROR;
+            }
+            else {
+                return NORMAL;
+            }
+        }
+
+        private String value;
+
+        private Type(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java
new file mode 100644
index 000000000..a0de7a602
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Packet.java
@@ -0,0 +1,423 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * Base class for XMPP packets. Every packet has a unique ID (which is automatically
+ * generated, but can be overriden). Optionally, the "to" and "from" fields can be set,
+ * as well as an arbitrary number of properties.
+ *
+ * Properties provide an easy mechanism for clients to share data. Each property has a
+ * String name, and a value that is a Java primitive (int, long, float, double, boolean)
+ * or any Serializable object (a Java object is Serializable when it implements the
+ * Serializable interface).
+ *
+ * @author Matt Tucker
+ */
+public abstract class Packet {
+
+    /**
+     * Constant used as packetID to indicate that a packet has no id. To indicate that a packet
+     * has no id set this constant as the packet's id. When the packet is asked for its id the 
+     * answer will be <tt>null</tt>.
+     */
+    public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";
+
+    /**
+     * A prefix helps to make sure that ID's are unique across mutliple instances.
+     */
+    private static String prefix = StringUtils.randomString(5) + "-";
+
+    /**
+     * Keeps track of the current increment, which is appended to the prefix to
+     * forum a unique ID.
+     */
+    private static long id = 0;
+
+    /**
+     * Returns the next unique id. Each id made up of a short alphanumeric
+     * prefix along with a unique numeric value.
+     *
+     * @return the next id.
+     */
+    private static synchronized String nextID() {
+        return prefix + Long.toString(id++);
+    }
+
+    private String packetID = null;
+    private String to = null;
+    private String from = null;
+    private List packetExtensions = null;
+    private Map properties = null;
+    private XMPPError error = null;
+
+    /**
+     * Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
+     * ID_NOT_AVAILABLE was set as the packet's id.
+     *
+     * @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
+     */
+    public String getPacketID() {
+        if (ID_NOT_AVAILABLE.equals(packetID)) {
+            return null;
+        }
+        
+        if (packetID == null) {
+            packetID = nextID();
+        }
+        return packetID;
+    }
+
+    /**
+     * Sets the unique ID of the packet. To indicate that a packet has no id 
+     * pass the constant ID_NOT_AVAILABLE as the packet's id value.
+     *
+     * @param packetID the unique ID for the packet.
+     */
+    public void setPacketID(String packetID) {
+        this.packetID = packetID;
+    }
+
+    /**
+     * Returns who the packet is being sent "to", or <tt>null</tt> if
+     * the value is not set. The XMPP protocol often makes the "to"
+     * attribute optional, so it does not always need to be set.
+     *
+     * @return who the packet is being sent to, or <tt>null</tt> if the
+     *      value has not been set.
+     */
+    public String getTo() {
+        return to;
+    }
+
+    /**
+     * Sets who the packet is being sent "to". The XMPP protocol often makes
+     * the "to" attribute optional, so it does not always need to be set.
+     *
+     * @param to who the packet is being sent to.
+     */
+    public void setTo(String to) {
+        this.to = to;
+    }
+
+    /**
+     * Returns who the packet is being sent "from" or <tt>null</tt> if
+     * the value is not set. The XMPP protocol often makes the "from"
+     * attribute optional, so it does not always need to be set.
+     *
+     * @return who the packet is being sent from, or <tt>null</tt> if the
+     *      valud has not been set.
+     */
+    public String getFrom() {
+        return from;
+    }
+
+    /**
+     * Sets who the packet is being sent "from". The XMPP protocol often
+     * makes the "from" attribute optional, so it does not always need to
+     * be set.
+     *
+     * @param from who the packet is being sent to.
+     */
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+    /**
+     * Returns the error associated with this packet, or <tt>null</tt> if there are
+     * no errors.
+     *
+     * @return the error sub-packet or <tt>null</tt> if there isn't an error.
+     */
+    public XMPPError getError() {
+        return error;
+    }
+
+    /**
+     * Sets the error for this packet.
+     *
+     * @param error the error to associate with this packet.
+     */
+    public void setError(XMPPError error) {
+        this.error = error;
+    }
+
+    /**
+     * Returns an Iterator for the packet extensions attached to the packet.
+     *
+     * @return an Iterator for the packet extensions.
+     */
+    public synchronized Iterator getExtensions() {
+        if (packetExtensions == null) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return Collections.unmodifiableList(new ArrayList(packetExtensions)).iterator();
+    }
+
+    /**
+     * Returns the first packet extension that matches the specified element name and
+     * namespace, or <tt>null</tt> if it doesn't exist. Packet extensions are
+     * are arbitrary XML sub-documents in standard XMPP packets. By default, a 
+     * DefaultPacketExtension instance will be returned for each extension. However, 
+     * PacketExtensionProvider instances can be registered with the 
+     * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
+     * class to handle custom parsing. In that case, the type of the Object
+     * will be determined by the provider.
+     *
+     * @param elementName the XML element name of the packet extension.
+     * @param namespace the XML element namespace of the packet extension.
+     * @return the extension, or <tt>null</tt> if it doesn't exist.
+     */
+    public synchronized PacketExtension getExtension(String elementName, String namespace) {
+        if (packetExtensions == null || elementName == null || namespace == null) {
+            return null;
+        }
+        for (Iterator i=packetExtensions.iterator(); i.hasNext(); ) {
+            PacketExtension ext = (PacketExtension)i.next();
+            if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
+                return ext;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a packet extension to the packet.
+     *
+     * @param extension a packet extension.
+     */
+    public synchronized void addExtension(PacketExtension extension) {
+        if (packetExtensions == null) {
+            packetExtensions = new ArrayList();
+        }
+        packetExtensions.add(extension);
+    }
+
+    /**
+     * Removes a packet extension from the packet.
+     *
+     * @param extension the packet extension to remove.
+     */
+    public synchronized void removeExtension(PacketExtension extension)  {
+        if (packetExtensions != null) {
+            packetExtensions.remove(extension);
+        }
+    }
+
+    /**
+     * Returns the packet property with the specified name or <tt>null</tt> if the
+     * property doesn't exist. Property values that were orginally primitives will
+     * be returned as their object equivalent. For example, an int property will be
+     * returned as an Integer, a double as a Double, etc.
+     *
+     * @param name the name of the property.
+     * @return the property, or <tt>null</tt> if the property doesn't exist.
+     */
+    public synchronized Object getProperty(String name) {
+        if (properties == null) {
+            return null;
+        }
+        return properties.get(name);
+    }
+
+    /**
+     * Sets a packet property with an int value.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, int value) {
+        setProperty(name, new Integer(value));
+    }
+
+    /**
+     * Sets a packet property with a long value.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, long value) {
+        setProperty(name, new Long(value));
+    }
+
+    /**
+     * Sets a packet property with a float value.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, float value) {
+        setProperty(name, new Float(value));
+    }
+
+    /**
+     * Sets a packet property with a double value.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, double value) {
+        setProperty(name, new Double(value));
+    }
+
+    /**
+     * Sets a packet property with a bboolean value.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public void setProperty(String name, boolean value) {
+        setProperty(name, new Boolean(value));
+    }
+
+    /**
+     * Sets a property with an Object as the value. The value must be Serializable
+     * or an IllegalArgumentException will be thrown.
+     *
+     * @param name the name of the property.
+     * @param value the value of the property.
+     */
+    public synchronized void setProperty(String name, Object value) {
+        if (!(value instanceof Serializable)) {
+            throw new IllegalArgumentException("Value must be serialiazble");
+        }
+        if (properties == null) {
+            properties = new HashMap();
+        }
+        properties.put(name, value);
+    }
+
+    /**
+     * Deletes a property.
+     *
+     * @param name the name of the property to delete.
+     */
+    public synchronized void deleteProperty(String name) {
+        if (properties == null) {
+            return;
+        }
+        properties.remove(name);
+    }
+
+    /**
+     * Returns an Iterator for all the property names that are set.
+     *
+     * @return an Iterator for all property names.
+     */
+    public synchronized Iterator getPropertyNames() {
+        if (properties == null) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return properties.keySet().iterator();
+    }
+
+    /**
+     * Returns the packet as XML. Every concrete extension of Packet must implement
+     * this method. In addition to writing out packet-specific data, every sub-class
+     * should also write out the error and the extensions data if they are defined.
+     *
+     * @return the XML format of the packet as a String.
+     */
+    public abstract String toXML();
+
+    /**
+     * Returns the extension sub-packets (including properties data) as an XML
+     * String, or the Empty String if there are no packet extensions.
+     *
+     * @return the extension sub-packets as XML or the Empty String if there
+     * are no packet extensions.
+     */
+    protected synchronized String getExtensionsXML() {
+        StringBuffer buf = new StringBuffer();
+        // Add in all standard extension sub-packets.
+        Iterator extensions = getExtensions();
+        while (extensions.hasNext()) {
+            PacketExtension extension = (PacketExtension)extensions.next();
+            buf.append(extension.toXML());
+        }
+        // Add in packet properties.
+        if (properties != null && !properties.isEmpty()) {
+            buf.append("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">");
+            // Loop through all properties and write them out.
+            for (Iterator i=getPropertyNames(); i.hasNext(); ) {
+                String name = (String)i.next();
+                Object value = getProperty(name);
+                buf.append("<property>");
+                buf.append("<name>").append(StringUtils.escapeForXML(name)).append("</name>");
+                buf.append("<value type=\"");
+                if (value instanceof Integer) {
+                    buf.append("integer\">").append(value).append("</value>");
+                }
+                else if (value instanceof Long) {
+                    buf.append("long\">").append(value).append("</value>");
+                }
+                else if (value instanceof Float) {
+                    buf.append("float\">").append(value).append("</value>");
+                }
+                else if (value instanceof Double) {
+                    buf.append("double\">").append(value).append("</value>");
+                }
+                else if (value instanceof Boolean) {
+                    buf.append("boolean\">").append(value).append("</value>");
+                }
+                else if (value instanceof String) {
+                    buf.append("string\">");
+                    buf.append(StringUtils.escapeForXML((String)value));
+                    buf.append("</value>");
+                }
+                // Otherwise, it's a generic Serializable object. Serialized objects are in
+                // a binary format, which won't work well inside of XML. Therefore, we base-64
+                // encode the binary data before adding it.
+                else {
+                    ByteArrayOutputStream byteStream = null;
+                    ObjectOutputStream out = null;
+                    try {
+                        byteStream = new ByteArrayOutputStream();
+                        out = new ObjectOutputStream(byteStream);
+                        out.writeObject(value);
+                        buf.append("java-object\">");
+                        String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray());
+                        buf.append(encodedVal).append("</value>");
+                    }
+                    catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                    finally {
+                        if (out != null) {
+                            try { out.close(); } catch (Exception e) { }
+                        }
+                        if (byteStream != null) {
+                            try { byteStream.close(); } catch (Exception e) { }
+                        }
+                    }
+                }
+                buf.append("</property>");
+            }
+            buf.append("</properties>");
+        }
+        return buf.toString();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java
new file mode 100644
index 000000000..e402e9d29
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/PacketExtension.java
@@ -0,0 +1,56 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+/**
+ * Interface to represent packet extensions. A packet extension is an XML subdocument
+ * with a root element name and namespace. Packet extensions are used to provide
+ * extended functionality beyond what is in the base XMPP specification. Examples of
+ * packet extensions include message events, message properties, and extra presence data.
+ * IQ packets cannot contain packet extensions.
+ *
+ * @see DefaultPacketExtension
+ * @see org.jivesoftware.smack.provider.PacketExtensionProvider
+ * @author Matt Tucker
+ */
+public interface PacketExtension {
+
+    /**
+     * Returns the root element name.
+     *
+     * @return the element name.
+     */
+    public String getElementName();
+
+    /**
+     * Returns the root element XML namespace.
+     *
+     * @return the namespace.
+     */
+    public String getNamespace();
+
+    /**
+     * Returns the XML reppresentation of the PacketExtension.
+     *
+     * @return the packet extension as XML.
+     */
+    public String toXML();
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java
new file mode 100644
index 000000000..52e17d0f0
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Presence.java
@@ -0,0 +1,327 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Represents XMPP presence packets. Every presence packet has a type, which is one of
+ * the following values:
+ * <ul>
+ *      <li><tt>Presence.Type.AVAILABLE</tt> -- (Default) indicates the user is available to
+ *          receive messages.
+ *      <li><tt>Presence.Type.UNAVAILABLE</tt> -- the user is unavailable to receive messages.
+ *      <li><tt>Presence.Type.SUBSCRIBE</tt> -- request subscription to recipient's presence.
+ *      <li><tt>Presence.Type.SUBSCRIBED</tt> -- grant subscription to sender's presence.
+ *      <li><tt>Presence.Type.UNSUBSCRIBE</tt> -- request removal of subscription to sender's
+ *          presence.
+ *      <li><tt>Presence.Type.UNSUBSCRIBED</tt> -- grant removal of subscription to sender's
+ *          presence.
+ *      <li><tt>Presence.Type.ERROR</tt> -- the presence packet contains an error message.
+ * </ul><p>
+ *
+ * A number of attributes are optional:
+ * <ul>
+ *      <li>Status -- free-form text describing a user's presence (i.e., gone to lunch).
+ *      <li>Priority -- non-negative numerical priority of a sender's resource. The
+ *          highest resource priority is the default recipient of packets not addressed
+ *          to a particular resource.
+ *      <li>Mode -- one of five presence modes: available (the default), chat, away,
+ *          xa (extended away, and dnd (do not disturb).
+ * </ul><p>
+ *
+ * Presence packets are used for two purposes. First, to notify the server of our
+ * the clients current presence status. Second, they are used to subscribe and
+ * unsubscribe users from the roster.
+ *
+ * @see RosterPacket
+ * @author Matt Tucker
+ */
+public class Presence extends Packet {
+
+    private Type type = Type.AVAILABLE;
+    private String status = null;
+    private int priority = -1;
+    private Mode mode = Mode.AVAILABLE;
+
+    /**
+     * Creates a new presence update. Status, priority, and mode are left un-set.
+     *
+     * @param type the type.
+     */
+    public Presence(Type type) {
+        this.type = type;
+    }
+
+    /**
+     * Creates a new presence update with a specified status, priority, and mode.
+     *
+     * @param type the type.
+     * @param status a text message describing the presence update.
+     * @param priority the priority of this presence update.
+     * @param mode the mode type for this presence update.
+     */
+    public Presence(Type type, String status, int priority, Mode mode) {
+        this.type = type;
+        this.status = status;
+        this.priority = priority;
+        this.mode = mode;
+    }
+
+    /**
+     * Returns the type of this presence packet.
+     *
+     * @return the type of the presence packet.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Sets the type of the presence packet.
+     *
+     * @param type the type of the presence packet.
+     */
+    public void setType(Type type) {
+        this.type = type;
+    }
+
+    /**
+     * Returns the status message of the presence update, or <tt>null</tt> if there
+     * is not a status. The status is free-form text describing a user's presence
+     * (i.e., "gone to lunch").
+     *
+     * @return the status message.
+     */
+    public String getStatus() {
+        return status;
+    }
+
+    /**
+     * Sets the status message of the presence update. The status is free-form text
+     * describing a user's presence (i.e., "gone to lunch").
+     *
+     * @param status the status message.
+     */
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    /**
+     * Returns the priority of the presence, or -1 if no priority has been set.
+     *
+     * @return the priority.
+     */
+    public int getPriority() {
+        return priority;
+    }
+
+    /**
+     * Sets the priority of the presence. The valid range is -128 through 128.
+     *
+     * @param priority the priority of the presence.
+     * @throws IllegalArgumentException if the priority is outside the valid range.
+     */
+    public void setPriority(int priority) {
+        if (priority < -128 || priority > 128) {
+            throw new IllegalArgumentException("Priority value " + priority +
+                    " is not valid. Valid range is -128 through 128.");
+        }
+        this.priority = priority;
+    }
+
+    /**
+     * Returns the mode of the presence update.
+     *
+     * @return the mode.
+     */
+    public Mode getMode() {
+        return mode;
+    }
+
+    /**
+     * Sets the mode of the presence update. For the standard "available" state, set
+     * the mode to <tt>null</tt>.
+     *
+     * @param mode the mode.
+     */
+    public void setMode(Mode mode) {
+        this.mode = mode;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<presence");
+        if (getPacketID() != null) {
+            buf.append(" id=\"").append(getPacketID()).append("\"");
+        }
+        if (getTo() != null) {
+            buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\"");
+        }
+        if (getFrom() != null) {
+            buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\"");
+        }
+        if (type != Type.AVAILABLE) {
+            buf.append(" type=\"").append(type).append("\"");
+        }
+        buf.append(">");
+        if (status != null) {
+            buf.append("<status>").append(status).append("</status>");
+        }
+        if (priority != -1) {
+            buf.append("<priority>").append(priority).append("</priority>");
+        }
+        if (mode != null && mode != Mode.AVAILABLE) {
+            buf.append("<show>").append(mode).append("</show>");
+        }
+
+        buf.append(this.getExtensionsXML());
+
+        // Add the error sub-packet, if there is one.
+        XMPPError error = getError();
+        if (error != null) {
+            buf.append(error.toXML());
+        }
+
+        buf.append("</presence>");
+        
+        return buf.toString();
+    }
+
+    public String toString() {
+        StringBuffer buf = new StringBuffer();
+        buf.append(type);
+        if (mode != null) {
+            buf.append(": ").append(mode);
+        }
+        if (status != null) {
+            buf.append(" (").append(status).append(")");
+        }
+        return buf.toString();
+    }
+
+    /**
+     * A typsafe enum class to represent the presecence type.
+     */
+    public static class Type {
+
+        public static final Type AVAILABLE = new Type("available");
+        public static final Type UNAVAILABLE = new Type("unavailable");
+        public static final Type SUBSCRIBE = new Type("subscribe");
+        public static final Type SUBSCRIBED = new Type("subscribed");
+        public static final Type UNSUBSCRIBE = new Type("unsubscribe");
+        public static final Type UNSUBSCRIBED = new Type("unsubscribed");
+        public static final Type ERROR = new Type("error");
+
+        private String value;
+
+        private Type(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+
+        /**
+         * Returns the type constant associated with the String value.
+         */
+        public static Type fromString(String value) {
+            if (value == null) {
+                return AVAILABLE;
+            }
+            value = value.toLowerCase();
+            if ("unavailable".equals(value)) {
+                return UNAVAILABLE;
+            }
+            else if ("subscribe".equals(value)) {
+                return SUBSCRIBE;
+            }
+            else if ("subscribed".equals(value)) {
+                return SUBSCRIBED;
+            }
+            else if ("unsubscribe".equals(value)) {
+                return UNSUBSCRIBE;
+            }
+            else if ("unsubscribed".equals(value)) {
+                return UNSUBSCRIBED;
+            }
+            else if ("error".equals(value)) {
+                return ERROR;
+            }
+            // Default to available.
+            else {
+                return AVAILABLE;
+            }
+        }
+    }
+
+    /**
+     * A typsafe enum class to represent the presence mode.
+     */
+    public static class Mode {
+
+        public static final Mode AVAILABLE = new Mode("available");
+        public static final Mode CHAT = new Mode("chat");
+        public static final Mode AWAY =  new Mode("away");
+        public static final Mode EXTENDED_AWAY = new Mode("xa");
+        public static final Mode DO_NOT_DISTURB = new Mode("dnd");
+        public static final Mode INVISIBLE = new Mode("invisible");
+
+        private String value;
+
+        private Mode(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+
+        /**
+         * Returns the mode constant associated with the String value.
+         */
+        public static Mode fromString(String value) {
+            if (value == null) {
+                return AVAILABLE;
+            }
+            value = value.toLowerCase();
+            if (value.equals("chat")) {
+                return CHAT;
+            }
+            else if (value.equals("away")) {
+                return AWAY;
+            }
+            else if (value.equals("xa")) {
+                return EXTENDED_AWAY;
+            }
+            else if (value.equals("dnd")) {
+                return DO_NOT_DISTURB;
+            }
+            else if (value.equals("invisible")) {
+                return INVISIBLE;
+            }
+            else {
+                return AVAILABLE;
+            }
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java
new file mode 100644
index 000000000..07b06daa7
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/Registration.java
@@ -0,0 +1,113 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ * Represents registration packets. An empty GET query will cause the server to return information
+ * about it's registration support. SET queries can be used to create accounts or update
+ * existing account information. XMPP servers may require a number of attributes to be set
+ * when creating a new account. The standard account attributes are as follows:
+ * <ul>
+ *      <li>name -- the user's name.
+ *      <li>first -- the user's first name.
+ *      <li>last -- the user's last name.
+ *      <li>email -- the user's email address.
+ *      <li>city -- the user's city.
+ *      <li>state -- the user's state.
+ *      <li>zip -- the user's ZIP code.
+ *      <li>phone -- the user's phone number.
+ *      <li>url -- the user's website.
+ *      <li>date -- the date the registration took place.
+ *      <li>misc -- other miscellaneous information to associate with the account.
+ *      <li>text -- textual information to associate with the account.
+ *      <li>remove -- empty flag to remove account.
+ * </ul>
+ *
+ * @author Matt Tucker
+ */
+public class Registration extends IQ {
+
+    private String instructions = null;
+    private Map attributes = null;
+
+    /**
+     * Returns the registration instructions, or <tt>null</tt> if no instructions
+     * have been set. If present, instructions should be displayed to the end-user
+     * that will complete the registration process.
+     *
+     * @return the registration instructions, or <tt>null</tt> if there are none.
+     */
+    public String getInstructions() {
+        return instructions;
+    }
+
+    /**
+     * Sets the registration instructions.
+     *
+     * @param instructions the registration instructions.
+     */
+    public void setInstructions(String instructions) {
+        this.instructions = instructions;
+    }
+
+    /**
+     * Returns the map of String key/value pairs of account attributes.
+     *
+     * @return the account attributes.
+     */
+    public Map getAttributes() {
+        return attributes;
+    }
+
+    /**
+     * Sets the account attributes. The map must only contain String key/value pairs.
+     *
+     * @param attributes the account attributes.
+     */
+    public void setAttributes(Map attributes) {
+        this.attributes = attributes;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"jabber:iq:register\">");
+        if (instructions != null) {
+            buf.append("<instructions>").append(instructions).append("</instructions>");
+        }
+        if (attributes != null && attributes.size() > 0) {
+            Iterator fieldNames = attributes.keySet().iterator();
+            while (fieldNames.hasNext()) {
+                String name = (String)fieldNames.next();
+                String value = (String)attributes.get(name);
+                buf.append("<").append(name).append(">");
+                buf.append(value);
+                buf.append("</").append(name).append(">");
+            }
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</query>");
+        return buf.toString();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java
new file mode 100644
index 000000000..0008428d0
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/RosterPacket.java
@@ -0,0 +1,348 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+import java.util.*;
+
+/**
+ * Represents XMPP roster packets.
+ *
+ * @author Matt Tucker
+ */
+public class RosterPacket extends IQ {
+
+    private List rosterItems = new ArrayList();
+
+    /**
+     * Adds a roster item to the packet.
+     *
+     * @param item a roster item.
+     */
+    public void addRosterItem(Item item) {
+        synchronized (rosterItems) {
+            rosterItems.add(item);
+        }
+    }
+
+    /**
+     * Returns the number of roster items in this roster packet.
+     *
+     * @return the number of roster items.
+     */
+    public int getRosterItemCount() {
+        synchronized (rosterItems) {
+            return rosterItems.size();
+        }
+    }
+
+    /**
+     * Returns an Iterator for the roster items in the packet.
+     *
+     * @return and Iterator for the roster items in the packet.
+     */
+    public Iterator getRosterItems() {
+        synchronized (rosterItems) {
+            List entries = Collections.unmodifiableList(new ArrayList(rosterItems));
+            return entries.iterator();
+        }
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"jabber:iq:roster\">");
+        synchronized (rosterItems) {
+            for (int i=0; i<rosterItems.size(); i++) {
+                Item entry = (Item)rosterItems.get(i);
+                buf.append(entry.toXML());
+            }
+        }
+        buf.append("</query>");
+        return buf.toString();
+    }
+
+    /**
+     * A roster item, which consists of a JID, their name, the type of subscription, and
+     * the groups the roster item belongs to.
+     */
+    public static class Item {
+
+        private String user;
+        private String name;
+        private ItemType itemType;
+        private ItemStatus itemStatus;
+        private List groupNames;
+
+        /**
+         * Creates a new roster item.
+         *
+         * @param user the user.
+         * @param name the user's name.
+         */
+        public Item(String user, String name) {
+            this.user = user;
+            this.name = name;
+            itemType = null;
+            itemStatus = null;
+            groupNames = new ArrayList();
+        }
+
+        /**
+         * Returns the user.
+         *
+         * @return the user.
+         */
+        public String getUser() {
+            return user;
+        }
+
+        /**
+         * Returns the user's name.
+         *
+         * @return the user's name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the user's name.
+         *
+         * @param name the user's name.
+         */
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        /**
+         * Returns the roster item type.
+         *
+         * @return the roster item type.
+         */
+        public ItemType getItemType() {
+            return itemType;
+        }
+
+        /**
+         * Sets the roster item type.
+         *
+         * @param itemType the roster item type.
+         */
+        public void setItemType(ItemType itemType) {
+            this.itemType = itemType;
+        }
+
+        /**
+         * Returns the roster item status.
+         *
+         * @return the roster item status.
+         */
+        public ItemStatus getItemStatus() {
+            return itemStatus;
+        }
+
+        /**
+         * Sets the roster item status.
+         *
+         * @param itemStatus the roster item status.
+         */
+        public void setItemStatus(ItemStatus itemStatus) {
+            this.itemStatus = itemStatus;
+        }
+
+        /**
+         * Returns an Iterator for the group names (as Strings) that the roster item
+         * belongs to.
+         *
+         * @return an Iterator for the group names.
+         */
+        public Iterator getGroupNames() {
+            synchronized (groupNames) {
+                return Collections.unmodifiableList(groupNames).iterator();
+            }
+        }
+
+        /**
+         * Adds a group name.
+         *
+         * @param groupName the group name.
+         */
+        public void addGroupName(String groupName) {
+            synchronized (groupNames) {
+                if (!groupNames.contains(groupName)) {
+                    groupNames.add(groupName);
+                }
+            }
+        }
+
+        /**
+         * Removes a group name.
+         *
+         * @param groupName the group name.
+         */
+        public void removeGroupName(String groupName) {
+            synchronized (groupNames) {
+                groupNames.remove(groupName);
+            }
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item jid=\"").append(user).append("\"");
+            if (name != null) {
+                buf.append(" name=\"").append(name).append("\"");
+            }
+            if (itemType != null) {
+                buf.append(" subscription=\"").append(itemType).append("\"");
+            }
+            if (itemStatus != null) {
+                buf.append(" ask=\"").append(itemStatus).append("\"");
+            }
+            buf.append(">");
+            synchronized (groupNames) {
+                for (int i=0; i<groupNames.size(); i++) {
+                    String groupName = (String)groupNames.get(i);
+                    buf.append("<group>").append(groupName).append("</group>");
+                }
+            }
+            buf.append("</item>");
+            return buf.toString();
+        }
+    }
+
+    /**
+     * The subscription status of a roster item. An optional element that indicates
+     * the subscription status if a change request is pending.
+     */
+    public static class ItemStatus {
+
+        /**
+         * Request to subcribe.
+         */
+        public static final ItemStatus SUBSCRIPTION_PENDING = new ItemStatus("subscribe");
+
+        /**
+         * Request to unsubscribe.
+         */
+        public static final ItemStatus UNSUBCRIPTION_PENDING = new ItemStatus("unsubscribe");
+
+        public static ItemStatus fromString(String value) {
+            if (value == null) {
+                return null;
+            }
+            value = value.toLowerCase();
+            if ("unsubscribe".equals(value)) {
+                return SUBSCRIPTION_PENDING;
+            }
+            else if ("subscribe".equals(value)) {
+                return SUBSCRIPTION_PENDING;
+            }
+            else {
+                return null;
+            }
+        }
+
+        private String value;
+
+        /**
+         * Returns the item status associated with the specified string.
+         *
+         * @param value the item status.
+         */
+        private ItemStatus(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+
+    /**
+     * The subscription type of a roster item.
+     */
+    public static class ItemType {
+
+        /**
+         * The user and subscriber have no interest in each other's presence.
+         */
+        public static final ItemType NONE = new ItemType("none");
+
+        /**
+         * The user is interested in receiving presence updates from the subscriber.
+         */
+        public static final ItemType TO = new ItemType("to");
+
+        /**
+         * The subscriber is interested in receiving presence updates from the user.
+         */
+        public static final ItemType FROM = new ItemType("from");
+
+        /**
+         * The user and subscriber have a mutual interest in each other's presence.
+         */
+        public static final ItemType BOTH = new ItemType("both");
+
+        /**
+         * The user wishes to stop receiving presence updates from the subscriber.
+         */
+        public static final ItemType REMOVE = new ItemType("remove");
+
+        public static ItemType fromString(String value) {
+            if (value == null) {
+                return null;
+            }
+            value = value.toLowerCase();
+            if ("none".equals(value)) {
+                return NONE;
+            }
+            else if ("to".equals(value)) {
+                return TO;
+            }
+            else if ("from".equals(value)) {
+                return FROM;
+            }
+            else if ("both".equals(value)) {
+                return BOTH;
+            }
+            else if ("remove".equals(value)) {
+                return REMOVE;
+            }
+            else {
+                return null;
+            }
+        }
+
+        private String value;
+
+        /**
+         * Returns the item type associated with the specified string.
+         *
+         * @param value the item type.
+         */
+        public ItemType(String value) {
+            this.value = value;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java b/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java
new file mode 100644
index 000000000..6d90c48e6
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/XMPPError.java
@@ -0,0 +1,117 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.packet;
+
+/**
+ * Represents a XMPP error sub-packet. Typically, a server responds to a request that has
+ * problems by sending the packet back and including an error packet. Each error has a code
+ * as well as as an optional text explanation. Typical error codes are as follows:<p>
+ *
+ * <table border=1>
+ *      <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
+ *      <tr><td> 302 </td><td> Redirect </td></tr>
+ *      <tr><td> 400 </td><td> Bad Request </td></tr>
+ *      <tr><td> 401 </td><td> Unauthorized </td></tr>
+ *      <tr><td> 402 </td><td> Payment Required </td></tr>
+ *      <tr><td> 403 </td><td> Forbidden </td></tr>
+ *      <tr><td> 404 </td><td> Not Found </td></tr>
+ *      <tr><td> 405 </td><td> Not Allowed </td></tr>
+ *      <tr><td> 406 </td><td> Not Acceptable </td></tr>
+ *      <tr><td> 407 </td><td> Registration Required </td></tr>
+ *      <tr><td> 408 </td><td> Request Timeout </td></tr>
+ *      <tr><td> 409 </td><td> Conflict </td></tr>
+ *      <tr><td> 500 </td><td> Internal Server XMPPError </td></tr>
+ *      <tr><td> 501 </td><td> Not Implemented </td></tr>
+ *      <tr><td> 502 </td><td> Remote Server Error </td></tr>
+ *      <tr><td> 503 </td><td> Service Unavailable </td></tr>
+ *      <tr><td> 504 </td><td> Remote Server Timeout </td></tr>
+ * </table>
+ *
+ * @author Matt Tucker
+ */
+public class XMPPError {
+
+    private int code;
+    private String message;
+
+    /**
+     * Creates a new  error with the specified code and no message..
+     *
+     * @param code the error code.
+     */
+    public XMPPError(int code) {
+        this.code = code;
+        this.message = null;
+    }
+
+    /**
+     * Creates a new error with the specified code and message.
+     *
+     * @param code the error code.
+     * @param message a message describing the error.
+     */
+    public XMPPError(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    /**
+     * Returns the error code.
+     *
+     * @return the error code.
+     */
+    public int getCode() {
+        return code;
+    }
+
+    /**
+     * Returns the message describing the error, or null if there is no message.
+     *
+     * @return the message describing the error, or null if there is no message.
+     */
+    public String getMessage() {
+        return message;
+    }
+
+    /**
+     * Returns the error as XML.
+     *
+     * @return the error as XML.
+     */
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<error code=\"").append(code).append("\">");
+        if (message != null) {
+            buf.append(message);
+        }
+        buf.append("</error>");
+        return buf.toString();
+    }
+
+    public String toString() {
+        StringBuffer txt = new StringBuffer();
+        txt.append("(").append(code).append(")");
+        if (message != null) {
+            txt.append(" ").append(message);
+        }
+        return txt.toString();
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html b/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html
new file mode 100644
index 000000000..18a6405c8
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/packet/package.html
@@ -0,0 +1 @@
+<body>XML packets that are part of the XMPP protocol.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java
new file mode 100644
index 000000000..8273283f1
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/IQProvider.java
@@ -0,0 +1,47 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * An interface for parsing custom IQ packets. Each IQProvider must be registered with
+ * the ProviderManager class for it to be used. Every implementation of this
+ * interface <b>must</b> have a public, no-argument constructor.
+ *
+ * @author Matt Tucker
+ */
+public interface IQProvider {
+
+    /**
+     * Parse the IQ sub-document and create an IQ instance. Each IQ must have a
+     * single child element. At the beginning of the method call, the xml parser
+     * will be positioned at the opening tag of the IQ child element. At the end
+     * of the method call, the parser <b>must</b> be positioned on the closing tag
+     * of the child element.
+     *
+     * @param parser an XML parser.
+     * @return a new IQ instance.
+     * @throws Exception if an error occurs parsing the XML.
+     */
+    public IQ parseIQ(XmlPullParser parser) throws Exception;
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java
new file mode 100644
index 000000000..40baeaafc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/PacketExtensionProvider.java
@@ -0,0 +1,46 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * An interface for parsing custom packets extensions. Each PacketExtensionProvider must
+ * be registered with the ProviderManager class for it to be used. Every implementation
+ * of this interface <b>must</b> have a public, no-argument constructor.
+ *
+ * @author Matt Tucker
+ */
+public interface PacketExtensionProvider {
+
+    /**
+     * Parse an extension sub-packet and create a PacketExtension instance. At
+     * the beginning of the method call, the xml parser will be positioned on the
+     * opening element of the packet extension. At the end of the method call, the
+     * parser <b>must</b> be positioned on the closing element of the packet extension.
+     *
+     * @param parser an XML parser.
+     * @return a new IQ instance.
+     * @throws java.lang.Exception if an error occurs parsing the XML.
+     */
+    public PacketExtension parseExtension(XmlPullParser parser) throws Exception;
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java b/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java
new file mode 100644
index 000000000..2a97f4068
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/ProviderManager.java
@@ -0,0 +1,359 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.xmlpull.v1.*;
+import org.xmlpull.mxp1.MXParser;
+
+import java.util.*;
+import java.net.URL;
+
+/**
+ * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of
+ * providers exist:<ul>
+ *      <li>IQProvider -- parses IQ requests into Java objects.
+ *      <li>PacketExtension -- parses XML sub-documents attached to packets into
+ *          PacketExtension instances.</ul>
+ *
+ * <b>IQProvider</b><p>
+ *
+ * By default, Smack only knows how to process IQ packets with sub-packets that
+ * are in a few namespaces such as:<ul>
+ *      <li>jabber:iq:auth
+ *      <li>jabber:iq:roster
+ *      <li>jabber:iq:register</ul>
+ *
+ * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing
+ * mechanism is provided. IQ providers are registered programatically or by creating a
+ * smack.providers file in the META-INF directory of your JAR file. The file is an XML
+ * document that contains one or more iqProvider entries, as in the following example:
+ *
+ * <pre>
+ * &lt;?xml version="1.0"?&gt;
+ * &lt;smackProviders&gt;
+ *     &lt;iqProvider&gt;
+ *         &lt;elementName&gt;query&lt;/elementName&gt;
+ *         &lt;namespace&gt;jabber:iq:time&lt;/namespace&gt;
+ *         &lt;className&gt;org.jivesoftware.smack.packet.Time&lt/className&gt;
+ *     &lt;/iqProvider&gt;
+ * &lt;/smackProviders&gt;</pre>
+ *
+ * Each IQ provider is associated with an element name and a namespace. If multiple provider
+ * entries attempt to register to handle the same namespace, the first entry loaded from the
+ * classpath will take precedence. The IQ provider class can either implement the IQProvider
+ * interface, or extend the IQ class. In the former case, each IQProvider is responsible for
+ * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection
+ * is used to try to automatically set properties of the IQ instance using the values found
+ * in the IQ packet XML. For example, an XMPP time packet resembles the following:
+ * <pre>
+ * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
+ *     &lt;query xmlns='jabber:iq:time'&gt;
+ *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
+ *         &lt;tz&gt;MDT&lt;/tz&gt;
+ *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
+ *     &lt;/query&gt;
+ * &lt;/iq&gt;</pre>
+ *
+ * In order for this packet to be automatically mapped to the Time object listed in the
+ * providers file above, it must have the methods setUtc(String), setTz(String), and
+ * setDisplay(String). The introspection service will automatically try to convert the String
+ * value from the XML into a boolean, int, long, float, double, or Class depending on the
+ * type the IQ instance expects.<p>
+ *
+ * A pluggable system for packet extensions, child elements in a custom namespace for
+ * message and presence packets, also exists. Each extension provider
+ * is registered with a name space in the smack.providers file as in the following example:
+ *
+ * <pre>
+ * &lt;?xml version="1.0"?&gt;
+ * &lt;smackProviders&gt;
+ *     &lt;extensionProvider&gt;
+ *         &lt;elementName&gt;x&lt;/elementName&gt;
+ *         &lt;namespace&gt;jabber:iq:event&lt;/namespace&gt;
+ *         &lt;className&gt;org.jivesoftware.smack.packet.MessageEvent&lt/className&gt;
+ *     &lt;/extensionProvider&gt;
+ * &lt;/smackProviders&gt;</pre>
+ *
+ * If multiple provider entries attempt to register to handle the same element name and namespace,
+ * the first entry loaded from the classpath will take precedence. Whenever a packet extension
+ * is found in a packet, parsing will be passed to the correct provider. Each provider
+ * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In
+ * the former case, each extension provider is responsible for parsing the raw XML stream to
+ * contruct an object. In the latter case, bean introspection is used to try to automatically
+ * set the properties of the class using the values in the packet extension sub-element. When an
+ * extension provider is not registered for an element name and namespace combination, Smack will
+ * store all top-level elements of the sub-packet in DefaultPacketExtension object and then
+ * attach it to the packet.
+ *
+ * @author Matt Tucker
+ */
+public class ProviderManager {
+
+    private static Map extensionProviders = new Hashtable();
+    private static Map iqProviders = new Hashtable();
+
+    static {
+        // Load IQ processing providers.
+        try {
+            // Get an array of class loaders to try loading the providers files from.
+            ClassLoader[] classLoaders = getClassLoaders();
+            for (int i=0; i<classLoaders.length; i++) {
+                Enumeration providerEnum = classLoaders[i].getResources(
+                        "META-INF/smack.providers");
+                while (providerEnum.hasMoreElements()) {
+                    URL url = (URL)providerEnum.nextElement();
+                    java.io.InputStream providerStream = null;
+                    try {
+                        providerStream = url.openStream();
+                        XmlPullParser parser = new MXParser();
+                        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+                        parser.setInput(providerStream, "UTF-8");
+                        int eventType = parser.getEventType();
+                        do {
+                            if (eventType == XmlPullParser.START_TAG) {
+                                if (parser.getName().equals("iqProvider")) {
+                                    parser.next();
+                                    parser.next();
+                                    String elementName = parser.nextText();
+                                    parser.next();
+                                    parser.next();
+                                    String namespace = parser.nextText();
+                                    parser.next();
+                                    parser.next();
+                                    String className = parser.nextText();
+                                    // Only add the provider for the namespace if one isn't
+                                    // already registered.
+                                    String key = getProviderKey(elementName, namespace);
+                                    if (!iqProviders.containsKey(key)) {
+                                        // Attempt to load the provider class and then create
+                                        // a new instance if it's an IQProvider. Otherwise, if it's
+                                        // an IQ class, add the class object itself, then we'll use
+                                        // reflection later to create instances of the class.
+                                        try {
+                                            // Add the provider to the map.
+                                            Class provider = Class.forName(className);
+                                            if (IQProvider.class.isAssignableFrom(provider)) {
+                                                iqProviders.put(key, provider.newInstance());
+                                            }
+                                            else if (IQ.class.isAssignableFrom(provider)) {
+                                                iqProviders.put(key, provider);
+                                            }
+                                        }
+                                        catch (ClassNotFoundException cnfe) {
+                                            cnfe.printStackTrace();
+                                        }
+                                    }
+                                }
+                                else if (parser.getName().equals("extensionProvider")) {
+                                    parser.next();
+                                    parser.next();
+                                    String elementName = parser.nextText();
+                                    parser.next();
+                                    parser.next();
+                                    String namespace = parser.nextText();
+                                    parser.next();
+                                    parser.next();
+                                    String className = parser.nextText();
+                                    // Only add the provider for the namespace if one isn't
+                                    // already registered.
+                                    String key = getProviderKey(elementName, namespace);
+                                    if (!extensionProviders.containsKey(key)) {
+                                        // Attempt to load the provider class and then create
+                                        // a new instance if it's a Provider. Otherwise, if it's
+                                        // a PacketExtension, add the class object itself and
+                                        // then we'll use reflection later to create instances
+                                        // of the class.
+                                        try {
+                                            // Add the provider to the map.
+                                            Class provider = Class.forName(className);
+                                            if (PacketExtensionProvider.class.isAssignableFrom(
+                                                    provider))
+                                            {
+                                                extensionProviders.put(key, provider.newInstance());
+                                            }
+                                            else if (PacketExtension.class.isAssignableFrom(
+                                                    provider))
+                                            {
+                                                extensionProviders.put(key, provider);
+                                            }
+                                        }
+                                        catch (ClassNotFoundException cnfe) {
+                                            cnfe.printStackTrace();
+                                        }
+                                    }
+                                }
+                            }
+                            eventType = parser.next();
+                        } while (eventType != XmlPullParser.END_DOCUMENT);
+                    }
+                    finally {
+                        try { providerStream.close(); }
+                        catch (Exception e) { }
+                    }
+                }
+            }
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Returns the IQ provider registered to the specified XML element name and namespace.
+     * For example, if a provider was registered to the element name "query" and the
+     * namespace "jabber:iq:time", then the following packet would trigger the provider:
+     *
+     * <pre>
+     * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
+     *     &lt;query xmlns='jabber:iq:time'&gt;
+     *         &lt;utc&gt;20020910T17:58:35&lt;/utc&gt;
+     *         &lt;tz&gt;MDT&lt;/tz&gt;
+     *         &lt;display&gt;Tue Sep 10 12:58:35 2002&lt;/display&gt;
+     *     &lt;/query&gt;
+     * &lt;/iq&gt;</pre>
+     *
+     * <p>Note: this method is generally only called by the internal Smack classes.
+     *
+     * @param elementName the XML element name.
+     * @param namespace the XML namespace.
+     * @return the IQ provider.
+     */
+    public static Object getIQProvider(String elementName, String namespace) {
+        String key = getProviderKey(elementName, namespace);
+        return iqProviders.get(key);
+    }
+
+    /**
+     * Returns an Iterator for all IQProvider instances.
+     *
+     * @return an Iterator for all IQProvider instances.
+     */
+    public static Iterator getIQProviders() {
+        return Collections.unmodifiableCollection(new HashMap(iqProviders).values()).iterator();
+    }
+
+    /**
+     * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ)
+     * with the specified element name and name space. The provider will override any providers
+     * loaded through the classpath.
+     *
+     * @param elementName the XML element name.
+     * @param namespace the XML namespace.
+     * @param provider the IQ provider.
+     */
+    public static void addIQProvider(String elementName, String namespace,
+            Object provider)
+    {
+        if (!(provider instanceof IQProvider || (provider instanceof Class &&
+                IQ.class.isAssignableFrom((Class)provider))))
+        {
+            throw new IllegalArgumentException("Provider must be an IQProvider " +
+                    "or a Class instance.");
+        }
+        String key = getProviderKey(elementName, namespace);
+        iqProviders.put(key, provider);
+    }
+
+    /**
+     * Returns the packet extension provider registered to the specified XML element name
+     * and namespace. For example, if a provider was registered to the element name "x" and the
+     * namespace "jabber:x:event", then the following packet would trigger the provider:
+     *
+     * <pre>
+     * &lt;message to='romeo@montague.net' id='message_1'&gt;
+     *     &lt;body&gt;Art thou not Romeo, and a Montague?&lt;/body&gt;
+     *     &lt;x xmlns='jabber:x:event'&gt;
+     *         &lt;composing/&gt;
+     *     &lt;/x&gt;
+     * &lt;/message&gt;</pre>
+     *
+     * <p>Note: this method is generally only called by the internal Smack classes.
+     *
+     * @param elementName
+     * @param namespace
+     * @return the extenion provider.
+     */
+    public static Object getExtensionProvider(String elementName, String namespace) {
+        String key = getProviderKey(elementName, namespace);
+        return extensionProviders.get(key);
+    }
+
+    /**
+     * Adds an extension provider with the specified element name and name space. The provider
+     * will override any providers loaded through the classpath. The provider must be either
+     * a PacketExtensionProvider instance, or a Class object of a Javabean.
+     *
+     * @param elementName the XML element name.
+     * @param namespace the XML namespace.
+     * @param provider the extension provider.
+     */
+    public static void addExtensionProvider(String elementName, String namespace,
+            Object provider)
+    {
+        if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) {
+            throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " +
+                    "or a Class instance.");
+        }
+        String key = getProviderKey(elementName, namespace);
+        extensionProviders.put(key, provider);
+    }
+
+    /**
+     * Returns an Iterator for all PacketExtensionProvider instances.
+     *
+     * @return an Iterator for all PacketExtensionProvider instances.
+     */
+    public static Iterator getExtensionProviders() {
+        return Collections.unmodifiableCollection(
+                new HashMap(extensionProviders).values()).iterator();
+    }
+
+    /**
+     * Returns a String key for a given element name and namespace.
+     *
+     * @param elementName the element name.
+     * @param namespace the namespace.
+     * @return a unique key for the element name and namespace pair.
+     */
+    private static String getProviderKey(String elementName, String namespace) {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
+        return buf.toString();
+    }
+
+    /**
+     * Returns an array of class loaders to load resources from.
+     *
+     * @return an array of ClassLoader instances.
+     */
+    private static ClassLoader[] getClassLoaders() {
+        ClassLoader[] classLoaders = new ClassLoader[2];
+        classLoaders[0] = new ProviderManager().getClass().getClassLoader();
+        classLoaders[1] = Thread.currentThread().getContextClassLoader();
+        return classLoaders;
+    }
+
+    private ProviderManager() {
+
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html b/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html
new file mode 100644
index 000000000..fccc3836d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/provider/package.html
@@ -0,0 +1 @@
+<body>Provides pluggable parsing of incoming IQ's and packet extensions.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java
new file mode 100644
index 000000000..5b073b830
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableReader.java
@@ -0,0 +1,118 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * An ObservableReader is a wrapper on a Reader that notifies to its listeners when
+ * reading character streams.
+ * 
+ * @author Gaston Dombiak
+ */
+public class ObservableReader extends Reader {
+
+    Reader wrappedReader = null;
+    List listeners = new ArrayList();
+
+    public ObservableReader(Reader wrappedReader) {
+        this.wrappedReader = wrappedReader;
+    }
+        
+    public int read(char[] cbuf, int off, int len) throws IOException {
+        int count = wrappedReader.read(cbuf, off, len);
+        if (count > 0) {
+            String str = new String(cbuf, off, count);
+            // Notify that a new string has been read
+            ReaderListener[] readerListeners = null;
+            synchronized (listeners) {
+                readerListeners = new ReaderListener[listeners.size()];
+                listeners.toArray(readerListeners);
+            }
+            for (int i = 0; i < readerListeners.length; i++) {
+                readerListeners[i].read(str);
+            }
+        }
+        return count;
+    }
+
+    public void close() throws IOException {
+        wrappedReader.close();
+    }
+
+    public int read() throws IOException {
+        return wrappedReader.read();
+    }
+
+    public int read(char cbuf[]) throws IOException {
+        return wrappedReader.read(cbuf);
+    }
+
+    public long skip(long n) throws IOException {
+        return wrappedReader.skip(n);
+    }
+
+    public boolean ready() throws IOException {
+        return wrappedReader.ready();
+    }
+
+    public boolean markSupported() {
+        return wrappedReader.markSupported();
+    }
+
+    public void mark(int readAheadLimit) throws IOException {
+        wrappedReader.mark(readAheadLimit);
+    }
+
+    public void reset() throws IOException {
+        wrappedReader.reset();
+    }
+
+    /**
+     * Adds a reader listener to this reader that will be notified when
+     * new strings are read.
+     *
+     * @param readerListener a reader listener.
+     */
+    public void addReaderListener(ReaderListener readerListener) {
+        if (readerListener == null) {
+            return;
+        }
+        synchronized (listeners) {
+            if (!listeners.contains(readerListener)) {
+                listeners.add(readerListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a reader listener from this reader.
+     *
+     * @param readerListener a reader listener.
+     */
+    public void removeReaderListener(ReaderListener readerListener) {
+        synchronized (listeners) {
+            listeners.remove(readerListener);
+        }
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java
new file mode 100644
index 000000000..ea1c0356e
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ObservableWriter.java
@@ -0,0 +1,120 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * An ObservableWriter is a wrapper on a Writer that notifies to its listeners when
+ * writing to character streams.
+ * 
+ * @author Gaston Dombiak
+ */
+public class ObservableWriter extends Writer {
+
+    Writer wrappedWriter = null;
+    List listeners = new ArrayList();
+
+    public ObservableWriter(Writer wrappedWriter) {
+        this.wrappedWriter = wrappedWriter;
+    }
+
+    public void write(char cbuf[], int off, int len) throws IOException {
+        wrappedWriter.write(cbuf, off, len);
+        String str = new String(cbuf, off, len);
+        notifyListeners(str);
+    }
+
+    public void flush() throws IOException {
+        wrappedWriter.flush();
+    }
+
+    public void close() throws IOException {
+        wrappedWriter.close();
+    }
+
+    public void write(int c) throws IOException {
+        wrappedWriter.write(c);
+    }
+
+    public void write(char cbuf[]) throws IOException {
+        wrappedWriter.write(cbuf);
+        String str = new String(cbuf);
+        notifyListeners(str);
+    }
+
+    public void write(String str) throws IOException {
+        wrappedWriter.write(str);
+        notifyListeners(str);
+    }
+
+    public void write(String str, int off, int len) throws IOException {
+        wrappedWriter.write(str, off, len);
+        str = str.substring(off, off + len);
+        notifyListeners(str);
+    }
+
+    /**
+     * Notify that a new string has been written.
+     * 
+     * @param str the written String to notify 
+     */
+    private void notifyListeners(String str) {
+        WriterListener[] writerListeners = null;
+        synchronized (listeners) {
+            writerListeners = new WriterListener[listeners.size()];
+            listeners.toArray(writerListeners);
+        }
+        for (int i = 0; i < writerListeners.length; i++) {
+            writerListeners[i].write(str);
+        }
+    }
+
+    /**
+     * Adds a writer listener to this writer that will be notified when
+     * new strings are sent.
+     *
+     * @param writerListener a writer listener.
+     */
+    public void addWriterListener(WriterListener writerListener) {
+        if (writerListener == null) {
+            return;
+        }
+        synchronized (listeners) {
+            if (!listeners.contains(writerListener)) {
+                listeners.add(writerListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a writer listener from this writer.
+     *
+     * @param writerListener a writer listener.
+     */
+    public void removeWriterListener(WriterListener writerListener) {
+        synchronized (listeners) {
+            listeners.remove(writerListener);
+        }
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java b/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java
new file mode 100644
index 000000000..73c21ea3d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/PacketParserUtils.java
@@ -0,0 +1,417 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+import java.beans.PropertyDescriptor;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.io.ObjectInputStream;
+import java.io.ByteArrayInputStream;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smack.provider.ProviderManager;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Utility class that helps to parse packets. Any parsing packets method that must be shared
+ * between many clients must be placed in this utility class.   
+ * 
+ * @author Gaston Dombiak
+ */
+public class PacketParserUtils {
+
+    /**
+     * Namespace used to store packet properties.
+     */
+    private static final String PROPERTIES_NAMESPACE =
+            "http://www.jivesoftware.com/xmlns/xmpp/properties";
+
+    /**
+     * Parses a message packet.
+     *
+     * @param parser the XML parser, positioned at the start of a message packet.
+     * @return a Message packet.
+     * @throws Exception if an exception occurs while parsing the packet.
+     */
+    public static Packet parseMessage(XmlPullParser parser) throws Exception {
+        Message message = new Message();
+        String id = parser.getAttributeValue("", "id");
+        message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
+        message.setTo(parser.getAttributeValue("", "to"));
+        message.setFrom(parser.getAttributeValue("", "from"));
+        message.setType(Message.Type.fromString(parser.getAttributeValue("", "type")));
+
+        // Parse sub-elements. We include extra logic to make sure the values
+        // are only read once. This is because it's possible for the names to appear
+        // in arbitrary sub-elements.
+        boolean done = false;
+        String subject = null;
+        String body = null;
+        String thread = null;
+        Map properties = null;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                String elementName = parser.getName();
+                String namespace = parser.getNamespace();
+                if (elementName.equals("subject")) {
+                    if (subject == null) {
+                        subject = parser.nextText();
+                    }
+                }
+                else if (elementName.equals("body")) {
+                    if (body == null) {
+                        body = parser.nextText();
+                    }
+                }
+                else if (elementName.equals("thread")) {
+                    if (thread == null) {
+                        thread = parser.nextText();
+                    }
+                }
+                else if (elementName.equals("error")) {
+                    message.setError(parseError(parser));
+                }
+                else if (elementName.equals("properties") &&
+                        namespace.equals(PROPERTIES_NAMESPACE))
+                {
+                    properties = parseProperties(parser);
+                }
+                // Otherwise, it must be a packet extension.
+                else {
+                    message.addExtension(
+                    PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("message")) {
+                    done = true;
+                }
+            }
+        }
+        message.setSubject(subject);
+        message.setBody(body);
+        message.setThread(thread);
+        // Set packet properties.
+        if (properties != null) {
+            for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
+                String name = (String)i.next();
+                message.setProperty(name, properties.get(name));
+            }
+        }
+        return message;
+    }
+
+    /**
+     * Parses a presence packet.
+     *
+     * @param parser the XML parser, positioned at the start of a presence packet.
+     * @return a Presence packet.
+     * @throws Exception if an exception occurs while parsing the packet.
+     */
+    public static Presence parsePresence(XmlPullParser parser) throws Exception {
+        Presence.Type type = Presence.Type.fromString(parser.getAttributeValue("", "type"));
+
+        Presence presence = new Presence(type);
+        presence.setTo(parser.getAttributeValue("", "to"));
+        presence.setFrom(parser.getAttributeValue("", "from"));
+        String id = parser.getAttributeValue("", "id");
+        presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
+
+        // Parse sub-elements
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                String elementName = parser.getName();
+                String namespace = parser.getNamespace();
+                if (elementName.equals("status")) {
+                    presence.setStatus(parser.nextText());
+                }
+                else if (elementName.equals("priority")) {
+                    try {
+                        int priority = Integer.parseInt(parser.nextText());
+                        presence.setPriority(priority);
+                    }
+                    catch (NumberFormatException nfe) { }
+                    catch (IllegalArgumentException iae) {
+                        // Presence priority is out of range so assume priority to be zero
+                        presence.setPriority(0);
+                    }
+                }
+                else if (elementName.equals("show")) {
+                    presence.setMode(Presence.Mode.fromString(parser.nextText()));
+                }
+                else if (elementName.equals("error")) {
+                    presence.setError(parseError(parser));
+                }
+                else if (elementName.equals("properties") &&
+                        namespace.equals(PROPERTIES_NAMESPACE))
+                {
+                    Map properties = parseProperties(parser);
+                    // Set packet properties.
+                    for (Iterator i=properties.keySet().iterator(); i.hasNext(); ) {
+                        String name = (String)i.next();
+                        presence.setProperty(name, properties.get(name));
+                    }
+                }
+                // Otherwise, it must be a packet extension.
+                else {
+                    presence.addExtension(
+                        PacketParserUtils.parsePacketExtension(elementName, namespace, parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("presence")) {
+                    done = true;
+                }
+            }
+        }
+        return presence;
+    }
+
+    /**
+     * Parse a properties sub-packet. If any errors occur while de-serializing Java object
+     * properties, an exception will be printed and not thrown since a thrown
+     * exception will shut down the entire connection. ClassCastExceptions will occur
+     * when both the sender and receiver of the packet don't have identical versions
+     * of the same class.
+     *
+     * @param parser the XML parser, positioned at the start of a properties sub-packet.
+     * @return a map of the properties.
+     * @throws Exception if an error occurs while parsing the properties.
+     */
+    public static Map parseProperties(XmlPullParser parser) throws Exception {
+        Map properties = new HashMap();
+        while (true) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
+                // Parse a property
+                boolean done = false;
+                String name = null;
+                String type = null;
+                String valueText = null;
+                Object value = null;
+                while (!done) {
+                    eventType = parser.next();
+                    if (eventType == XmlPullParser.START_TAG) {
+                        String elementName = parser.getName();
+                        if (elementName.equals("name")) {
+                            name = parser.nextText();
+                        }
+                        else if (elementName.equals("value")) {
+                            type = parser.getAttributeValue("", "type");
+                            valueText = parser.nextText();
+                        }
+                    }
+                    else if (eventType == XmlPullParser.END_TAG) {
+                        if (parser.getName().equals("property")) {
+                            if ("integer".equals(type)) {
+                                value = new Integer(valueText);
+                            }
+                            else if ("long".equals(type))  {
+                                value = new Long(valueText);
+                            }
+                            else if ("float".equals(type)) {
+                                value = new Float(valueText);
+                            }
+                            else if ("double".equals(type)) {
+                                value = new Double(valueText);
+                            }
+                            else if ("boolean".equals(type)) {
+                                value = new Boolean(valueText);
+                            }
+                            else if ("string".equals(type)) {
+                                value = valueText;
+                            }
+                            else if ("java-object".equals(type)) {
+                                try {
+                                    byte [] bytes = StringUtils.decodeBase64(valueText);
+                                    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
+                                    value = in.readObject();
+                                }
+                                catch (Exception e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                            if (name != null && value != null) {
+                                properties.put(name, value);
+                            }
+                            done = true;
+                        }
+                    }
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("properties")) {
+                    break;
+                }
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * Parses error sub-packets.
+     *
+     * @param parser the XML parser.
+     * @return an error sub-packet.
+     * @throws Exception if an exception occurs while parsing the packet.
+     */
+    public static XMPPError parseError(XmlPullParser parser) throws Exception {
+        String errorCode = "-1";
+        String message = null;
+        for (int i=0; i<parser.getAttributeCount(); i++) {
+            if (parser.getAttributeName(i).equals("code")) {
+                errorCode = parser.getAttributeValue("", "code");
+            }
+        }
+        // Get the error text in a safe way since we are not sure about the error message format
+        try {
+            message = parser.nextText();
+        }
+        catch (XmlPullParserException ex) {}
+        while (true) {
+            if (parser.getEventType() == XmlPullParser.END_TAG && parser.getName().equals("error")) {
+                break;
+            }
+            parser.next();
+        }
+        return new XMPPError(Integer.parseInt(errorCode), message);
+    }
+
+    /**
+     * Parses a packet extension sub-packet.
+     *
+     * @param elementName the XML element name of the packet extension.
+     * @param namespace the XML namespace of the packet extension.
+     * @param parser the XML parser, positioned at the starting element of the extension.
+     * @return a PacketExtension.
+     * @throws Exception if a parsing error occurs.
+     */
+    public static PacketExtension parsePacketExtension(String elementName, String namespace, XmlPullParser parser)
+            throws Exception
+    {
+        // See if a provider is registered to handle the extension.
+        Object provider = ProviderManager.getExtensionProvider(elementName, namespace);
+        if (provider != null) {
+            if (provider instanceof PacketExtensionProvider) {
+                return ((PacketExtensionProvider)provider).parseExtension(parser);
+            }
+            else if (provider instanceof Class) {
+                return (PacketExtension)parseWithIntrospection(
+                        elementName, (Class)provider, parser);
+            }
+        }
+        // No providers registered, so use a default extension.
+        DefaultPacketExtension extension = new DefaultPacketExtension(elementName, namespace);
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                String name = parser.getName();
+                // If an empty element, set the value with the empty string.
+                if (parser.isEmptyElementTag()) {
+                    extension.setValue(name,"");
+                }
+                // Otherwise, get the the element text.
+                else {
+                    eventType = parser.next();
+                    if (eventType == XmlPullParser.TEXT) {
+                        String value = parser.getText();
+                        extension.setValue(name, value);
+                    }
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(elementName)) {
+                    done = true;
+                }
+            }
+        }
+        return extension;
+    }
+
+    public static Object parseWithIntrospection(String elementName,
+            Class objectClass, XmlPullParser parser) throws Exception
+    {
+        boolean done = false;
+        Object object = objectClass.newInstance();
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                String name = parser.getName();
+                String stringValue = parser.nextText();
+                PropertyDescriptor descriptor = new PropertyDescriptor(name, objectClass);
+                // Load the class type of the property.
+                Class propertyType = descriptor.getPropertyType();
+                // Get the value of the property by converting it from a
+                // String to the correct object type.
+                Object value = decode(propertyType, stringValue);
+                // Set the value of the bean.
+                descriptor.getWriteMethod().invoke(object, new Object[] { value });
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(elementName)) {
+                    done = true;
+                }
+            }
+        }
+        return object;
+    }
+
+    /**
+     * Decodes a String into an object of the specified type. If the object
+     * type is not supported, null will be returned.
+     *
+     * @param type the type of the property.
+     * @param value the encode String value to decode.
+     * @return the String value decoded into the specified type.
+     */
+    private static Object decode(Class type, String value) throws Exception {
+        if (type.getName().equals("java.lang.String")) {
+            return value;
+        }
+        if (type.getName().equals("boolean")) {
+            return Boolean.valueOf(value);
+        }
+        if (type.getName().equals("int")) {
+            return Integer.valueOf(value);
+        }
+        if (type.getName().equals("long")) {
+            return Long.valueOf(value);
+        }
+        if (type.getName().equals("float")) {
+            return Float.valueOf(value);
+        }
+        if (type.getName().equals("double")) {
+            return Double.valueOf(value);
+        }
+        if (type.getName().equals("java.lang.Class")) {
+            return Class.forName(value);
+        }
+        return null;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java b/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java
new file mode 100644
index 000000000..1b818e9b3
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/ReaderListener.java
@@ -0,0 +1,41 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+/**
+ * Interface that allows for implementing classes to listen for string reading
+ * events. Listeners are registered with ObservableReader objects.
+ *
+ * @see ObservableReader#addReaderListener
+ * @see ObservableReader#removeReaderListener
+ * 
+ * @author Gaston Dombiak
+ */
+public interface ReaderListener {
+
+    /**
+     * Notification that the Reader has read a new string.
+     * 
+     * @param str the read String
+     */
+    public abstract void read(String str);
+    
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java b/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java
new file mode 100644
index 000000000..740827c37
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/StringUtils.java
@@ -0,0 +1,437 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.UnsupportedEncodingException;
+import java.util.Random;
+
+/**
+ * A collection of utility methods for String objects.
+ */
+public class StringUtils {
+
+    private static final char[] QUOTE_ENCODE = "&quot;".toCharArray();
+    private static final char[] AMP_ENCODE = "&amp;".toCharArray();
+    private static final char[] LT_ENCODE = "&lt;".toCharArray();
+    private static final char[] GT_ENCODE = "&gt;".toCharArray();
+
+    /**
+     * Returns the name portion of a XMPP address. For example, for the
+     * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no
+     * username is present in the address, the empty string will be returned.
+     *
+     * @param XMPPAddress the XMPP address.
+     * @return the name portion of the XMPP address.
+     */
+    public static String parseName(String XMPPAddress) {
+        if (XMPPAddress == null) {
+            return null;
+        }
+        int atIndex = XMPPAddress.indexOf("@");
+        if (atIndex <= 0) {
+            return "";
+        }
+        else {
+            return XMPPAddress.substring(0, atIndex);
+        }
+    }
+
+    /**
+     * Returns the server portion of a XMPP address. For example, for the
+     * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned.
+     * If no server is present in the address, the empty string will be returned.
+     *
+     * @param XMPPAddress the XMPP address.
+     * @return the server portion of the XMPP address.
+     */
+    public static String parseServer(String XMPPAddress) {
+        if (XMPPAddress == null) {
+            return null;
+        }
+        int atIndex = XMPPAddress.indexOf("@");
+        // If the String ends with '@', return the empty string.
+        if (atIndex + 1 > XMPPAddress.length()) {
+            return "";
+        }
+        int slashIndex = XMPPAddress.indexOf("/");
+        if (slashIndex > 0) {
+            return XMPPAddress.substring(atIndex + 1, slashIndex);
+        }
+        else {
+            return XMPPAddress.substring(atIndex + 1);
+        }
+    }
+
+    /**
+     * Returns the resource portion of a XMPP address. For example, for the
+     * address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no
+     * resource is present in the address, the empty string will be returned.
+     *
+     * @param XMPPAddress the XMPP address.
+     * @return the resource portion of the XMPP address.
+     */
+    public static String parseResource(String XMPPAddress) {
+        if (XMPPAddress == null) {
+            return null;
+        }
+        int slashIndex = XMPPAddress.indexOf("/");
+        if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) {
+            return "";
+        }
+        else {
+            return XMPPAddress.substring(slashIndex + 1);
+        }
+    }
+
+    /**
+     * Returns the XMPP address with any resource information removed. For example,
+     * for the address "matt@jivesoftware.com/Smack", "matt@jivesoftware.com" would
+     * be returned.
+     *
+     * @param XMPPAddress the XMPP address.
+     * @return the bare XMPP address without resource information.
+     */
+    public static String parseBareAddress(String XMPPAddress) {
+        if (XMPPAddress == null) {
+            return null;
+        }
+        int slashIndex = XMPPAddress.indexOf("/");
+        if (slashIndex < 0) {
+            return XMPPAddress;
+        }
+        else if (slashIndex == 0) {
+            return "";
+        }
+        else {
+            return XMPPAddress.substring(0, slashIndex);
+        }
+    }
+
+    /**
+     * Escapes all necessary characters in the String so that it can be used
+     * in an XML doc.
+     *
+     * @param string the string to escape.
+     * @return the string with appropriate characters escaped.
+     */
+    public static final String escapeForXML(String string) {
+        if (string == null) {
+            return null;
+        }
+        char ch;
+        int i=0;
+        int last=0;
+        char[] input = string.toCharArray();
+        int len = input.length;
+        StringBuffer out = new StringBuffer((int)(len*1.3));
+        for (; i < len; i++) {
+            ch = input[i];
+            if (ch > '>') {
+                continue;
+            }
+            else if (ch == '<') {
+                if (i > last) {
+                    out.append(input, last, i - last);
+                }
+                last = i + 1;
+                out.append(LT_ENCODE);
+            }
+            else if (ch == '>') {
+                if (i > last) {
+                    out.append(input, last, i - last);
+                }
+                last = i + 1;
+                out.append(GT_ENCODE);
+            }
+
+            else if (ch == '&') {
+                if (i > last) {
+                    out.append(input, last, i - last);
+                }
+                // Do nothing if the string is of the form &#235; (unicode value) 
+                if (!(len > i + 5
+                    && input[i + 1] == '#'
+                    && Character.isDigit(input[i + 2])
+                    && Character.isDigit(input[i + 3])
+                    && Character.isDigit(input[i + 4])
+                    && input[i + 5] == ';')) {
+                        last = i + 1;
+                        out.append(AMP_ENCODE);
+                    }
+            }
+            else if (ch == '"') {
+                if (i > last) {
+                    out.append(input, last, i - last);
+                }
+                last = i + 1;
+                out.append(QUOTE_ENCODE);
+            }
+        }
+        if (last == 0) {
+            return string;
+        }
+        if (i > last) {
+            out.append(input, last, i - last);
+        }
+        return out.toString();
+    }
+
+    /**
+     * Used by the hash method.
+     */
+    private static MessageDigest digest = null;
+
+    /**
+     * Hashes a String using the SHA-1 algorithm and returns the result as a
+     * String of hexadecimal numbers. This method is synchronized to avoid
+     * excessive MessageDigest object creation. If calling this method becomes
+     * a bottleneck in your code, you may wish to maintain a pool of
+     * MessageDigest objects instead of using this method.
+     * <p>
+     * A hash is a one-way function -- that is, given an
+     * input, an output is easily computed. However, given the output, the
+     * input is almost impossible to compute. This is useful for passwords
+     * since we can store the hash and a hacker will then have a very hard time
+     * determining the original password.
+     *
+     * @param data the String to compute the hash of.
+     * @return a hashed version of the passed-in String
+     */
+    public synchronized static final String hash(String data) {
+        if (digest == null) {
+            try {
+                digest = MessageDigest.getInstance("SHA-1");
+            }
+            catch (NoSuchAlgorithmException nsae) {
+                System.err.println("Failed to load the SHA-1 MessageDigest. " +
+                "Jive will be unable to function normally.");
+            }
+        }
+        // Now, compute hash.
+        try {
+            digest.update(data.getBytes("UTF-8"));
+        }
+        catch (UnsupportedEncodingException e) {
+            System.err.println(e);
+        }
+        return encodeHex(digest.digest());
+    }
+
+    /**
+     * Turns an array of bytes into a String representing each byte as an
+     * unsigned hex number.
+     * <p>
+     * Method by Santeri Paavolainen, Helsinki Finland 1996<br>
+     * (c) Santeri Paavolainen, Helsinki Finland 1996<br>
+     * Distributed under LGPL.
+     *
+     * @param bytes an array of bytes to convert to a hex-string
+     * @return generated hex string
+     */
+    public static final String encodeHex(byte[] bytes) {
+        StringBuffer buf = new StringBuffer(bytes.length * 2);
+        int i;
+
+        for (i = 0; i < bytes.length; i++) {
+            if (((int) bytes[i] & 0xff) < 0x10) {
+                buf.append("0");
+            }
+            buf.append(Long.toString((int) bytes[i] & 0xff, 16));
+        }
+        return buf.toString();
+    }
+
+    //*********************************************************************
+    //* Base64 - a simple base64 encoder and decoder.
+    //*
+    //*     Copyright (c) 1999, Bob Withers - bwit@pobox.com
+    //*
+    //* This code may be freely used for any purpose, either personal
+    //* or commercial, provided the authors copyright notice remains
+    //* intact.
+    //*********************************************************************
+
+    private static final int fillchar = '=';
+    private static final String cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+                                    + "0123456789+/";
+
+    /**
+     * Encodes a String as a base64 String.
+     *
+     * @param data a String to encode.
+     * @return a base64 encoded String.
+     */
+    public static String encodeBase64(String data) {
+        byte [] bytes = null;
+        try {
+            bytes = data.getBytes("ISO-8859-1");
+        }
+        catch (UnsupportedEncodingException uee) {
+            uee.printStackTrace();
+        }
+        return encodeBase64(bytes);
+    }
+
+    /**
+     * Encodes a byte array into a base64 String.
+     *
+     * @param data a byte array to encode.
+     * @return a base64 encode String.
+     */
+    public static String encodeBase64(byte[] data) {
+        int c;
+        int len = data.length;
+        StringBuffer ret = new StringBuffer(((len / 3) + 1) * 4);
+        for (int i = 0; i < len; ++i) {
+            c = (data[i] >> 2) & 0x3f;
+            ret.append(cvt.charAt(c));
+            c = (data[i] << 4) & 0x3f;
+            if (++i < len)
+                c |= (data[i] >> 4) & 0x0f;
+
+            ret.append(cvt.charAt(c));
+            if (i < len) {
+                c = (data[i] << 2) & 0x3f;
+                if (++i < len)
+                    c |= (data[i] >> 6) & 0x03;
+
+                ret.append(cvt.charAt(c));
+            }
+            else {
+                ++i;
+                ret.append((char) fillchar);
+            }
+
+            if (i < len) {
+                c = data[i] & 0x3f;
+                ret.append(cvt.charAt(c));
+            }
+            else {
+                ret.append((char) fillchar);
+            }
+        }
+        return ret.toString();
+    }
+
+    /**
+     * Decodes a base64 String.
+     *
+     * @param data a base64 encoded String to decode.
+     * @return the decoded String.
+     */
+    public static byte[] decodeBase64(String data) {
+        byte [] bytes = null;
+        try {
+            bytes = data.getBytes("ISO-8859-1");
+            return decodeBase64(bytes).getBytes("ISO-8859-1");
+        }
+        catch (UnsupportedEncodingException uee) {
+            uee.printStackTrace();
+        }
+        return new byte[] { };
+    }
+
+    /**
+     * Decodes a base64 aray of bytes.
+     *
+     * @param data a base64 encode byte array to decode.
+     * @return the decoded String.
+     */
+    private static String decodeBase64(byte[] data) {
+        int c, c1;
+        int len = data.length;
+        StringBuffer ret = new StringBuffer((len * 3) / 4);
+        for (int i = 0; i < len; ++i) {
+            c = cvt.indexOf(data[i]);
+            ++i;
+            c1 = cvt.indexOf(data[i]);
+            c = ((c << 2) | ((c1 >> 4) & 0x3));
+            ret.append((char) c);
+            if (++i < len) {
+                c = data[i];
+                if (fillchar == c)
+                    break;
+
+                c = cvt.indexOf(c);
+                c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf);
+                ret.append((char) c1);
+            }
+
+            if (++i < len) {
+                c1 = data[i];
+                if (fillchar == c1)
+                    break;
+
+                c1 = cvt.indexOf(c1);
+                c = ((c << 6) & 0xc0) | c1;
+                ret.append((char) c);
+            }
+        }
+        return ret.toString();
+    }
+
+    /**
+     * Pseudo-random number generator object for use with randomString().
+     * The Random class is not considered to be cryptographically secure, so
+     * only use these random Strings for low to medium security applications.
+     */
+    private static Random randGen = new Random();
+
+    /**
+     * Array of numbers and letters of mixed case. Numbers appear in the list
+     * twice so that there is a more equal chance that a number will be picked.
+     * We can use the array to get a random number or letter by picking a random
+     * array index.
+     */
+    private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" +
+                    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
+
+    /**
+     * Returns a random String of numbers and letters (lower and upper case)
+     * of the specified length. The method uses the Random class that is
+     * built-in to Java which is suitable for low to medium grade security uses.
+     * This means that the output is only pseudo random, i.e., each number is
+     * mathematically generated so is not truly random.<p>
+     *
+     * The specified length must be at least one. If not, the method will return
+     * null.
+     *
+     * @param length the desired length of the random String to return.
+     * @return a random String of numbers and letters of the specified length.
+     */
+    public static final String randomString(int length) {
+        if (length < 1) {
+            return null;
+        }
+        // Create a char buffer to put random letters and numbers in.
+        char [] randBuffer = new char[length];
+        for (int i=0; i<randBuffer.length; i++) {
+            randBuffer[i] = numbersAndLetters[randGen.nextInt(71)];
+        }
+        return new String(randBuffer);
+    }
+
+    private StringUtils() {
+        // Not instantiable.
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java b/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java
new file mode 100644
index 000000000..175f3a66d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/WriterListener.java
@@ -0,0 +1,41 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack.util;
+
+/**
+ * Interface that allows for implementing classes to listen for string writing
+ * events. Listeners are registered with ObservableWriter objects.
+ *
+ * @see ObservableWriter#addWriterListener
+ * @see ObservableWriter#removeWriterListener
+ * 
+ * @author Gaston Dombiak
+ */
+public interface WriterListener {
+
+    /**
+     * Notification that the Writer has written a new string.
+     * 
+     * @param str the written string
+     */
+    public abstract void write(String str);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smack/util/package.html b/CopyOftrunk/source/org/jivesoftware/smack/util/package.html
new file mode 100644
index 000000000..e34bfe316
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smack/util/package.html
@@ -0,0 +1 @@
+<body>Utility classes.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java
new file mode 100644
index 000000000..5cfb15acf
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java
@@ -0,0 +1,55 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+/**
+ *
+ * Default implementation of the MessageEventRequestListener interface.<p>
+ *
+ * This class automatically sends a delivered notification to the sender of the message 
+ * if the sender has requested to be notified when the message is delivered. 
+ *
+ * @author Gaston Dombiak
+ */
+public class DefaultMessageEventRequestListener implements MessageEventRequestListener {
+
+    public void deliveredNotificationRequested(String from, String packetID,
+                MessageEventManager messageEventManager)
+    {
+        // Send to the message's sender that the message has been delivered
+        messageEventManager.sendDeliveredNotification(from, packetID);
+    }
+
+    public void displayedNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager)
+    {
+    }
+
+    public void composingNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager)
+    {
+    }
+
+    public void offlineNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager)
+    {
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/Form.java b/CopyOftrunk/source/org/jivesoftware/smackx/Form.java
new file mode 100644
index 000000000..0f9ae8084
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/Form.java
@@ -0,0 +1,539 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.packet.DataForm;
+
+/**
+ * Represents a Form for gathering data. The form could be of the following types:
+ * <ul>
+ *  <li>form -> Indicates a form to fill out.</li>
+ *  <li>submit -> The form is filled out, and this is the data that is being returned from 
+ * the form.</li>
+ *  <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
+ *  <li>result -> Data results being returned from a search, or some other query.</li>
+ * </ul>
+ * 
+ * Depending of the form's type different operations are available. For example, it's only possible
+ * to set answers if the form is of type "submit".
+ * 
+ * @author Gaston Dombiak
+ */
+public class Form {
+    
+    public static final String TYPE_FORM = "form";
+    public static final String TYPE_SUBMIT = "submit";
+    public static final String TYPE_CANCEL = "cancel";
+    public static final String TYPE_RESULT = "result";
+    
+    private DataForm dataForm;
+    
+    /**
+     * Returns a new ReportedData if the packet is used for gathering data and includes an 
+     * extension that matches the elementName and namespace "x","jabber:x:data".  
+     * 
+     * @param packet the packet used for gathering data.
+     */
+    public static Form getFormFrom(Packet packet) {
+        // Check if the packet includes the DataForm extension
+        PacketExtension packetExtension = packet.getExtension("x","jabber:x:data");
+        if (packetExtension != null) {
+            // Check if the existing DataForm is not a result of a search
+            DataForm dataForm = (DataForm) packetExtension;
+            if (dataForm.getReportedData() == null)
+                return new Form(dataForm);
+        }
+        // Otherwise return null
+        return null;
+    }
+
+    /**
+     * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be
+     * used for gathering data. 
+     * 
+     * @param dataForm the data form used for gathering data. 
+     */
+    private Form(DataForm dataForm) {
+        this.dataForm = dataForm;
+    }
+    
+    /**
+     * Creates a new Form of a given type from scratch.<p>
+     *  
+     * Possible form types are:
+     * <ul>
+     *  <li>form -> Indicates a form to fill out.</li>
+     *  <li>submit -> The form is filled out, and this is the data that is being returned from 
+     * the form.</li>
+     *  <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
+     *  <li>result -> Data results being returned from a search, or some other query.</li>
+     * </ul>
+     * 
+     * @param type the form's type (e.g. form, submit,cancel,result).
+     */
+    public Form(String type) {
+        this.dataForm = new DataForm(type);
+    }
+    
+    /**
+     * Adds a new field to complete as part of the form.
+     * 
+     * @param field the field to complete.
+     */
+    public void addField(FormField field) {
+        dataForm.addField(field);
+    }
+    
+    /**
+     * Sets a new String value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.<p>
+     * 
+     * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
+     * can use this message where the String value is the String representation of the object. 
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the String value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, String value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
+            && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
+            && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())
+            && !FormField.TYPE_JID_SINGLE.equals(field.getType())
+            && !FormField.TYPE_HIDDEN.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type String.");
+        }
+        setAnswer(field, value);
+    }
+
+    /**
+     * Sets a new int value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the int value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, int value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
+            && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
+            && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type int.");
+        }
+        setAnswer(field, new Integer(value));
+    }
+
+    /**
+     * Sets a new long value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the long value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, long value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
+            && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
+            && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type long.");
+        }
+        setAnswer(field, new Long(value));
+    }
+
+    /**
+     * Sets a new float value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the float value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, float value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
+            && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
+            && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type float.");
+        }
+        setAnswer(field, new Float(value));
+    }
+
+    /**
+     * Sets a new double value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the double value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, double value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_TEXT_MULTI.equals(field.getType())
+            && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType())
+            && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type double.");
+        }
+        setAnswer(field, new Double(value));
+    }
+
+    /**
+     * Sets a new boolean value to a given form's field. The field whose variable matches the 
+     * requested variable will be completed with the specified value. If no field could be found 
+     * for the specified variable then an exception will be raised.
+     * 
+     * @param variable the variable name that was completed.
+     * @param value the boolean value that was answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     * @throws IllegalArgumentException if the answer type does not correspond with the field type.
+     */
+    public void setAnswer(String variable, boolean value) {
+        FormField field = getField(variable);
+        if (field == null) {
+            throw new IllegalArgumentException("Field not found for the specified variable name.");
+        }
+        if (!FormField.TYPE_BOOLEAN.equals(field.getType())) {
+            throw new IllegalArgumentException("This field is not of type boolean.");
+        }
+        setAnswer(field, (value ? "1" : "0"));
+    }
+
+    /**
+     * Sets a new Object value to a given form's field. In fact, the object representation 
+     * (i.e. #toString) will be the actual value of the field.<p>
+     * 
+     * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
+     * will need to use {@link #setAnswer(String, String))} where the String value is the 
+     * String representation of the object.<p> 
+     * 
+     * Before setting the new value to the field we will check if the form is of type submit. If 
+     * the form isn't of type submit means that it's not possible to complete the form and an   
+     * exception will be thrown.
+     * 
+     * @param field the form field that was completed.
+     * @param value the Object value that was answered. The object representation will be the 
+     * actual value.
+     * @throws IllegalStateException if the form is not of type "submit".
+     */
+    private void setAnswer(FormField field, Object value) {
+        if (!isSubmitType()) {
+            throw new IllegalStateException("Cannot set an answer if the form is not of type " +
+            "\"submit\"");
+        }
+        field.resetValues();
+        field.addValue(value.toString());
+    }
+
+    /**
+     * Sets a new values to a given form's field. The field whose variable matches the requested 
+     * variable will be completed with the specified values. If no field could be found for 
+     * the specified variable then an exception will be raised.<p>
+     * 
+     * The Objects contained in the List could be of any type. The String representation of them
+     * (i.e. #toString) will be actually used when sending the answer to the server.
+     * 
+     * @param variable the variable that was completed.
+     * @param values the values that were answered.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     */
+    public void setAnswer(String variable, List values) {
+        if (!isSubmitType()) {
+            throw new IllegalStateException("Cannot set an answer if the form is not of type " +
+            "\"submit\"");
+        }
+        FormField field = getField(variable);
+        if (field != null) {
+            // Check that the field can accept a collection of values
+            if (!FormField.TYPE_JID_MULTI.equals(field.getType())
+                && !FormField.TYPE_LIST_MULTI.equals(field.getType())
+                && !FormField.TYPE_LIST_SINGLE.equals(field.getType())
+                && !FormField.TYPE_HIDDEN.equals(field.getType())) {
+                throw new IllegalArgumentException("This field only accept list of values.");
+            }
+            // Clear the old values 
+            field.resetValues();
+            // Set the new values. The string representation of each value will be actually used.
+            field.addValues(values);
+        }
+        else {
+            throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
+        }
+    }
+
+    /**
+     * Sets the default value as the value of a given form's field. The field whose variable matches
+     * the requested variable will be completed with its default value. If no field could be found
+     * for the specified variable then an exception will be raised.
+     *
+     * @param variable the variable to complete with its default value.
+     * @throws IllegalStateException if the form is not of type "submit".
+     * @throws IllegalArgumentException if the form does not include the specified variable.
+     */
+    public void setDefaultAnswer(String variable) {
+        if (!isSubmitType()) {
+            throw new IllegalStateException("Cannot set an answer if the form is not of type " +
+            "\"submit\"");
+        }
+        FormField field = getField(variable);
+        if (field != null) {
+            // Clear the old values
+            field.resetValues();
+            // Set the default value
+            for (Iterator it = field.getValues(); it.hasNext();) {
+                field.addValue((String) it.next());
+            }
+        }
+        else {
+            throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
+        }
+    }
+
+    /**
+     * Returns an Iterator for the fields that are part of the form.
+     *
+     * @return an Iterator for the fields that are part of the form.
+     */
+    public Iterator getFields() {
+        return dataForm.getFields();
+    }
+
+    /**
+     * Returns the field of the form whose variable matches the specified variable.
+     * The fields of type FIXED will never be returned since they do not specify a 
+     * variable. 
+     * 
+     * @param variable the variable to look for in the form fields. 
+     * @return the field of the form whose variable matches the specified variable.
+     */
+    public FormField getField(String variable) {
+        if (variable == null || variable.equals("")) {
+            throw new IllegalArgumentException("Variable must not be null or blank.");
+        }
+        // Look for the field whose variable matches the requested variable
+        FormField field;
+        for (Iterator it=getFields();it.hasNext();) {
+            field = (FormField)it.next();
+            if (variable.equals(field.getVariable())) {
+                return field;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the instructions that explain how to fill out the form and what the form is about.
+     * 
+     * @return instructions that explain how to fill out the form.
+     */
+    public String getInstructions() {
+        StringBuffer sb = new StringBuffer();
+        // Join the list of instructions together separated by newlines
+        for (Iterator it = dataForm.getInstructions(); it.hasNext();) {
+            sb.append((String) it.next());
+            // If this is not the last instruction then append a newline
+            if (it.hasNext()) {
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
+
+
+    /**
+     * Returns the description of the data. It is similar to the title on a web page or an X 
+     * window.  You can put a <title/> on either a form to fill out, or a set of data results.
+     * 
+     * @return description of the data.
+     */
+    public String getTitle() {
+        return dataForm.getTitle();
+    }
+
+
+    /**
+     * Returns the meaning of the data within the context. The data could be part of a form
+     * to fill out, a form submission or data results.<p>
+     * 
+     * Possible form types are:
+     * <ul>
+     *  <li>form -> Indicates a form to fill out.</li>
+     *  <li>submit -> The form is filled out, and this is the data that is being returned from 
+     * the form.</li>
+     *  <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
+     *  <li>result -> Data results being returned from a search, or some other query.</li>
+     * </ul>
+     * 
+     * @return the form's type.
+     */
+    public String getType() {
+        return dataForm.getType(); 
+    }
+    
+
+    /**
+     * Sets instructions that explain how to fill out the form and what the form is about.
+     * 
+     * @param instructions instructions that explain how to fill out the form.
+     */
+    public void setInstructions(String instructions) {
+        // Split the instructions into multiple instructions for each existent newline
+        ArrayList instructionsList = new ArrayList();
+        StringTokenizer st = new StringTokenizer(instructions, "\n");
+        while (st.hasMoreTokens()) {
+            instructionsList.add(st.nextToken());
+        }
+        // Set the new list of instructions
+        dataForm.setInstructions(instructionsList);
+        
+    }
+
+
+    /**
+     * Sets the description of the data. It is similar to the title on a web page or an X window.
+     * You can put a <title/> on either a form to fill out, or a set of data results.
+     * 
+     * @param title description of the data.
+     */
+    public void setTitle(String title) {
+        dataForm.setTitle(title);
+    }
+    
+    /**
+     * Returns a DataForm that serves to send this Form to the server. If the form is of type 
+     * submit, it may contain fields with no value. These fields will be removed since they only 
+     * exist to assist the user while editing/completing the form in a UI. 
+     * 
+     * @return the wrapped DataForm.
+     */
+    public DataForm getDataFormToSend() {
+        if (isSubmitType()) {
+            // Create a new DataForm that contains only the answered fields 
+            DataForm dataFormToSend = new DataForm(getType());
+            for(Iterator it=getFields();it.hasNext();) {
+                FormField field = (FormField)it.next();
+                if (field.getValues().hasNext()) {
+                    dataFormToSend.addField(field);
+                }
+            }
+            return dataFormToSend;
+        }
+        return dataForm;
+    }
+    
+    /**
+     * Returns true if the form is a form to fill out.
+     * 
+     * @return if the form is a form to fill out.
+     */
+    private boolean isFormType() {
+        return TYPE_FORM.equals(dataForm.getType());
+    }
+    
+    /**
+     * Returns true if the form is a form to submit.
+     * 
+     * @return if the form is a form to submit.
+     */
+    private boolean isSubmitType() {
+        return TYPE_SUBMIT.equals(dataForm.getType());
+    }
+
+    /**
+     * Returns a new Form to submit the completed values. The new Form will include all the fields
+     * of the original form except for the fields of type FIXED. Only the HIDDEN fields will 
+     * include the same value of the original form. The other fields of the new form MUST be 
+     * completed. If a field remains with no answer when sending the completed form, then it won't 
+     * be included as part of the completed form.<p>
+     * 
+     * The reason why the fields with variables are included in the new form is to provide a model 
+     * for binding with any UI. This means that the UIs will use the original form (of type 
+     * "form") to learn how to render the form, but the UIs will bind the fields to the form of
+     * type submit.
+     * 
+     * @return a Form to submit the completed values.
+     */
+    public Form createAnswerForm() {
+        if (!isFormType()) {
+            throw new IllegalStateException("Only forms of type \"form\" could be answered");
+        }
+        // Create a new Form
+        Form form = new Form(TYPE_SUBMIT);
+        for (Iterator fields=getFields(); fields.hasNext();) {
+            FormField field = (FormField)fields.next();
+            // Add to the new form any type of field that includes a variable.
+            // Note: The fields of type FIXED are the only ones that don't specify a variable
+            if (field.getVariable() != null) {
+                FormField newField = new FormField(field.getVariable());
+                newField.setType(field.getType());
+                form.addField(newField);
+                // Set the answer ONLY to the hidden fields 
+                if (FormField.TYPE_HIDDEN.equals(field.getType())) {
+                    // Since a hidden field could have many values we need to collect them 
+                    // in a list
+                    List values = new ArrayList();
+                    for (Iterator it=field.getValues();it.hasNext();) {
+                        values.add((String)it.next());
+                    }
+                    form.setAnswer(field.getVariable(), values);
+                }                
+            }
+        }
+        return form;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java b/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java
new file mode 100644
index 000000000..124b0c62f
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/FormField.java
@@ -0,0 +1,350 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+/**
+ * Represents a field of a form. The field could be used to represent a question to complete,
+ * a completed question or a data returned from a search. The exact interpretation of the field
+ * depends on the context where the field is used.
+ * 
+ * @author Gaston Dombiak
+ */
+public class FormField {
+    public static final String TYPE_BOOLEAN = "boolean";
+    public static final String TYPE_FIXED = "fixed";
+    public static final String TYPE_HIDDEN = "hidden";
+    public static final String TYPE_JID_MULTI = "jid-multi";
+    public static final String TYPE_JID_SINGLE = "jid-single";
+    public static final String TYPE_LIST_MULTI = "list-multi";
+    public static final String TYPE_LIST_SINGLE = "list-single";
+    public static final String TYPE_TEXT_MULTI = "text-multi";
+    public static final String TYPE_TEXT_PRIVATE = "text-private";
+    public static final String TYPE_TEXT_SINGLE = "text-single";
+
+    private String description;
+    private boolean required = false;
+    private String label;
+    private String variable;
+    private String type;
+    private List options = new ArrayList();
+    private List values = new ArrayList();
+
+    /**
+     * Creates a new FormField with the variable name that uniquely identifies the field 
+     * in the context of the form. 
+     *  
+     * @param variable the variable name of the question.
+     */
+    public FormField(String variable) {
+        this.variable = variable;
+    }
+    
+    /**
+     * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable 
+     * name. 
+     *  
+     */
+    public FormField() {
+        this.type = FormField.TYPE_FIXED; 
+    }
+    
+    /**
+     * Returns a description that provides extra clarification about the question. This information
+     * could be presented to the user either in tool-tip, help button, or as a section of text 
+     * before the question.<p> 
+     * 
+     * If the question is of type FIXED then the description should remain empty.
+     * 
+     * @return description that provides extra clarification about the question.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Returns the label of the question which should give enough information to the user to 
+     * fill out the form.
+     * 
+     * @return label of the question.
+     */
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * Returns an Iterator for the available options that the user has in order to answer
+     * the question. 
+     * 
+     * @return Iterator for the available options. 
+     */
+    public Iterator getOptions() {
+        synchronized (options) {
+            return Collections.unmodifiableList(new ArrayList(options)).iterator();
+        }
+    }
+
+    /**
+     * Returns true if the question must be answered in order to complete the questionnaire.
+     * 
+     * @return true if the question must be answered in order to complete the questionnaire.
+     */
+    public boolean isRequired() {
+        return required;
+    }
+
+    /**
+     * Returns an indicative of the format for the data to answer. Valid formats are:
+     * 
+     * <ul>
+     *  <li>text-single -> single line or word of text
+     *  <li>text-private -> instead of showing the user what they typed, you show ***** to 
+     * protect it
+     *  <li>text-multi -> multiple lines of text entry
+     *  <li>list-single -> given a list of choices, pick one
+     *  <li>list-multi -> given a list of choices, pick one or more
+     *  <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
+     *  <li>fixed -> fixed for putting in text to show sections, or just advertise your web 
+     * site in the middle of the form
+     *  <li>hidden -> is not given to the user at all, but returned with the questionnaire
+     *  <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based 
+     * on the rules for a JID.
+     *  <li>jid-multi -> multiple entries for JIDs
+     * </ul>
+     * 
+     * @return format for the data to answer.
+     */
+    public String getType() {
+        return type;
+    }
+
+    /**
+     * Returns an Iterator for the default values of the question if the question is part
+     * of a form to fill out. Otherwise, returns an Iterator for the answered values of 
+     * the question. 
+     * 
+     * @return an Iterator for the default values or answered values of the question. 
+     */
+    public Iterator getValues() {
+        synchronized (values) {
+            return Collections.unmodifiableList(new ArrayList(values)).iterator();
+        }
+    }
+
+    /**
+     * Returns the variable name that the question is filling out.
+     * 
+     * @return the variable name of the question.
+     */
+    public String getVariable() {
+        return variable;
+    }
+
+    /**
+     * Sets a description that provides extra clarification about the question. This information
+     * could be presented to the user either in tool-tip, help button, or as a section of text 
+     * before the question.<p> 
+     * 
+     * If the question is of type FIXED then the description should remain empty.
+     * 
+     * @param description provides extra clarification about the question.
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * Sets the label of the question which should give enough information to the user to 
+     * fill out the form.
+     * 
+     * @param label the label of the question.
+     */
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    /**
+     * Sets if the question must be answered in order to complete the questionnaire.
+     * 
+     * @param required if the question must be answered in order to complete the questionnaire.
+     */
+    public void setRequired(boolean required) {
+        this.required = required;
+    }
+
+    /**
+     * Sets an indicative of the format for the data to answer. Valid formats are:
+     * 
+     * <ul>
+     *  <li>text-single -> single line or word of text
+     *  <li>text-private -> instead of showing the user what they typed, you show ***** to 
+     * protect it
+     *  <li>text-multi -> multiple lines of text entry
+     *  <li>list-single -> given a list of choices, pick one
+     *  <li>list-multi -> given a list of choices, pick one or more
+     *  <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
+     *  <li>fixed -> fixed for putting in text to show sections, or just advertise your web 
+     * site in the middle of the form
+     *  <li>hidden -> is not given to the user at all, but returned with the questionnaire
+     *  <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based 
+     * on the rules for a JID.
+     *  <li>jid-multi -> multiple entries for JIDs
+     * </ul>
+     * 
+     * @param type an indicative of the format for the data to answer.
+     */
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    /**
+     * Adds a default value to the question if the question is part of a form to fill out. 
+     * Otherwise, adds an answered value to the question.
+     *  
+     * @param value a default value or an answered value of the question.
+     */
+    public void addValue(String value) {
+        synchronized (values) {
+            values.add(value);
+        }
+    }
+
+    /**
+     * Adds a default values to the question if the question is part of a form to fill out. 
+     * Otherwise, adds an answered values to the question.
+     *  
+     * @param newValues default values or an answered values of the question.
+     */
+    public void addValues(List newValues) {
+        synchronized (values) {
+            values.addAll(newValues);
+        }
+    }
+
+    /**
+     * Removes all the values of the field.
+     *
+     */
+    protected void resetValues() {
+        synchronized (values) {
+            values.removeAll(new ArrayList(values));
+        }
+    }
+    
+    /**
+     * Adss an available options to the question that the user has in order to answer 
+     * the question.
+     *  
+     * @param option a new available option for the question.
+     */
+    public void addOption(Option option) {
+        synchronized (options) {
+            options.add(option);
+        }
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<field");
+        // Add attributes
+        if (getLabel() != null) {
+            buf.append(" label=\"").append(getLabel()).append("\"");
+        }
+        if (getVariable() != null) {
+            buf.append(" var=\"").append(getVariable()).append("\"");
+        }
+        if (getType() != null) {
+            buf.append(" type=\"").append(getType()).append("\"");
+        }
+        buf.append(">");
+        // Add elements
+        if (getDescription() != null) {
+            buf.append("<desc>").append(getDescription()).append("</desc>");
+        }
+        if (isRequired()) {
+            buf.append("<required/>");
+        }
+        // Loop through all the values and append them to the string buffer
+        for (Iterator i = getValues(); i.hasNext();) {
+            buf.append("<value>").append(i.next()).append("</value>");
+        }
+        // Loop through all the values and append them to the string buffer
+        for (Iterator i = getOptions(); i.hasNext();) {
+            buf.append(((Option)i.next()).toXML());
+        }
+        buf.append("</field>");
+        return buf.toString();
+    }
+
+    /**
+     * 
+     * Represents the available option of a given FormField. 
+     * 
+     * @author Gaston Dombiak
+     */
+    public static class Option {
+        private String label;
+        private String value; 
+
+        public Option(String value) {
+            this.value = value;
+        }
+
+        public Option(String label, String value) {
+            this.label = label;
+            this.value = value;
+        }
+        
+        /**
+         * Returns the label that represents the option. 
+         * 
+         * @return the label that represents the option.
+         */
+        public String getLabel() {
+            return label;
+        }
+
+        /**
+         * Returns the value of the option.
+         * 
+         * @return the value of the option.
+         */
+        public String getValue() {
+            return value;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<option");
+            // Add attribute
+            if (getLabel() != null) {
+                buf.append(" label=\"").append(getLabel()).append("\"");
+            }
+            buf.append(">");
+            // Add element
+            buf.append("<value>").append(getValue()).append("</value>");
+
+            buf.append("</option>");
+            return buf.toString();
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java b/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java
new file mode 100644
index 000000000..c2ee81ad1
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/GroupChatInvitation.java
@@ -0,0 +1,115 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A group chat invitation packet extension, which is used to invite other
+ * users to a group chat room. To invite a user to a group chat room, address
+ * a new message to the user and set the room name appropriately, as in the
+ * following code example:
+ *
+ * <pre>
+ * Message message = new Message("user@chat.example.com");
+ * message.setBody("Join me for a group chat!");
+ * message.addExtension(new GroupChatInvitation("room@chat.example.com"););
+ * con.sendPacket(message);
+ * </pre>
+ *
+ * To listen for group chat invitations, use a PacketExtensionFilter for the
+ * <tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the
+ * following code example:
+ *
+ * <pre>
+ * PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference");
+ * // Create a packet collector or packet listeners using the filter...
+ * </pre>
+ *
+ * <b>Note</b>: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available
+ * (<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most
+ * existing clients still use this older protocol. Once MUC support becomes more
+ * widespread, this API may be deprecated.
+ * 
+ * @author Matt Tucker
+ */
+public class GroupChatInvitation implements PacketExtension {
+
+    /**
+     * Element name of the packet extension.
+     */
+    public static final String ELEMENT_NAME = "x";
+
+    /**
+     * Namespace of the packet extension.
+     */
+    public static final String NAMESPACE = "jabber:x:conference";
+
+    private String roomAddress;
+
+    /**
+     * Creates a new group chat invitation to the specified room address.
+     * GroupChat room addresses are in the form <tt>room@service</tt>,
+     * where <tt>service</tt> is the name of groupchat server, such as
+     * <tt>chat.example.com</tt>.
+     *
+     * @param roomAddress the address of the group chat room.
+     */
+    public GroupChatInvitation(String roomAddress) {
+        this.roomAddress = roomAddress;
+    }
+
+    /**
+     * Returns the address of the group chat room. GroupChat room addresses
+     * are in the form <tt>room@service</tt>, where <tt>service</tt> is
+     * the name of groupchat server, such as <tt>chat.example.com</tt>.
+     *
+     * @return the address of the group chat room.
+     */
+    public String getRoomAddress() {
+        return roomAddress;
+    }
+
+    public String getElementName() {
+        return ELEMENT_NAME;
+    }
+
+    public String getNamespace() {
+        return NAMESPACE;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<x xmlns=\"jabber:x:conference\" jid=\"").append(roomAddress).append("\"/>");
+        return buf.toString();
+    }
+
+    public static class Provider implements PacketExtensionProvider {
+        public PacketExtension parseExtension (XmlPullParser parser) throws Exception {
+            String roomAddress = parser.getAttributeValue("", "jid");
+            // Advance to end of extension.
+            parser.next();
+            return new GroupChatInvitation(roomAddress);
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java
new file mode 100644
index 000000000..3fd6c6859
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventManager.java
@@ -0,0 +1,304 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Manages message events requests and notifications. A MessageEventManager provides a high
+ * level access to request for notifications and send event notifications. It also provides 
+ * an easy way to hook up custom logic when requests or notifications are received. 
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEventManager {
+
+    private List messageEventNotificationListeners = new ArrayList();
+    private List messageEventRequestListeners = new ArrayList();
+
+    private XMPPConnection con;
+
+    private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event");
+    private PacketListener packetListener;
+
+    /**
+     * Creates a new message event manager.
+     *
+     * @param con an XMPPConnection.
+     */
+    public MessageEventManager(XMPPConnection con) {
+        this.con = con;
+        init();
+    }
+
+    /**
+     * Adds event notification requests to a message. For each event type that
+     * the user wishes event notifications from the message recepient for, <tt>true</tt>
+     * should be passed in to this method.
+     * 
+     * @param message the message to add the requested notifications.
+     * @param offline specifies if the offline event is requested.
+     * @param delivered specifies if the delivered event is requested.
+     * @param displayed specifies if the displayed event is requested.
+     * @param composing specifies if the composing event is requested.
+     */
+    public static void addNotificationsRequests(Message message, boolean offline,
+            boolean delivered, boolean displayed, boolean composing)
+    {
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setOffline(offline);
+        messageEvent.setDelivered(delivered);
+        messageEvent.setDisplayed(displayed);
+        messageEvent.setComposing(composing);
+        message.addExtension(messageEvent);
+    }
+
+    /**
+     * Adds a message event request listener. The listener will be fired anytime a request for
+     * event notification is received.
+     *
+     * @param messageEventRequestListener a message event request listener.
+     */
+    public void addMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) {
+        synchronized (messageEventRequestListeners) {
+            if (!messageEventRequestListeners.contains(messageEventRequestListener)) {
+                messageEventRequestListeners.add(messageEventRequestListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a message event request listener. The listener will be fired anytime a request for
+     * event notification is received.
+     *
+     * @param messageEventRequestListener a message event request listener.
+     */
+    public void removeMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) {
+        synchronized (messageEventRequestListeners) {
+            messageEventRequestListeners.remove(messageEventRequestListener);
+        }
+    }
+
+    /**
+     * Adds a message event notification listener. The listener will be fired anytime a notification
+     * event is received.
+     *
+     * @param messageEventNotificationListener a message event notification listener.
+     */
+    public void addMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) {
+        synchronized (messageEventNotificationListeners) {
+            if (!messageEventNotificationListeners.contains(messageEventNotificationListener)) {
+                messageEventNotificationListeners.add(messageEventNotificationListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a message event notification listener. The listener will be fired anytime a notification
+     * event is received.
+     *
+     * @param messageEventNotificationListener a message event notification listener.
+     */
+    public void removeMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) {
+        synchronized (messageEventNotificationListeners) {
+            messageEventNotificationListeners.remove(messageEventNotificationListener);
+        }
+    }
+
+    /**
+     * Fires message event request listeners.
+     */
+    private void fireMessageEventRequestListeners(
+        String from,
+        String packetID,
+        String methodName) {
+        MessageEventRequestListener[] listeners = null;
+        Method method;
+        synchronized (messageEventRequestListeners) {
+            listeners = new MessageEventRequestListener[messageEventRequestListeners.size()];
+            messageEventRequestListeners.toArray(listeners);
+        }
+        try {
+            method =
+                MessageEventRequestListener.class.getDeclaredMethod(
+                    methodName,
+                    new Class[] { String.class, String.class, MessageEventManager.class });
+            for (int i = 0; i < listeners.length; i++) {
+                method.invoke(listeners[i], new Object[] { from, packetID, this });
+            }
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Fires message event notification listeners.
+     */
+    private void fireMessageEventNotificationListeners(
+        String from,
+        String packetID,
+        String methodName) {
+        MessageEventNotificationListener[] listeners = null;
+        Method method;
+        synchronized (messageEventNotificationListeners) {
+            listeners =
+                new MessageEventNotificationListener[messageEventNotificationListeners.size()];
+            messageEventNotificationListeners.toArray(listeners);
+        }
+        try {
+            method =
+                MessageEventNotificationListener.class.getDeclaredMethod(
+                    methodName,
+                    new Class[] { String.class, String.class });
+            for (int i = 0; i < listeners.length; i++) {
+                method.invoke(listeners[i], new Object[] { from, packetID });
+            }
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void init() {
+        // Listens for all message event packets and fire the proper message event listeners.
+        packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                MessageEvent messageEvent =
+                    (MessageEvent) message.getExtension("x", "jabber:x:event");
+                if (messageEvent.isMessageEventRequest()) {
+                    // Fire event for requests of message events
+                    for (Iterator it = messageEvent.getEventTypes(); it.hasNext();)
+                        fireMessageEventRequestListeners(
+                            message.getFrom(),
+                            message.getPacketID(),
+                            ((String) it.next()).concat("NotificationRequested"));
+                } else
+                    // Fire event for notifications of message events
+                    for (Iterator it = messageEvent.getEventTypes(); it.hasNext();)
+                        fireMessageEventNotificationListeners(
+                            message.getFrom(),
+                            messageEvent.getPacketID(),
+                            ((String) it.next()).concat("Notification"));
+
+            };
+
+        };
+        con.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Sends the notification that the message was delivered to the sender of the original message
+     * 
+     * @param to the recipient of the notification.
+     * @param packetID the id of the message to send.
+     */
+    public void sendDeliveredNotification(String to, String packetID) {
+        // Create the message to send
+        Message msg = new Message(to);
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setDelivered(true);
+        messageEvent.setPacketID(packetID);
+        msg.addExtension(messageEvent);
+        // Send the packet
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Sends the notification that the message was displayed to the sender of the original message
+     * 
+     * @param to the recipient of the notification.
+     * @param packetID the id of the message to send.
+     */
+    public void sendDisplayedNotification(String to, String packetID) {
+        // Create the message to send
+        Message msg = new Message(to);
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setDisplayed(true);
+        messageEvent.setPacketID(packetID);
+        msg.addExtension(messageEvent);
+        // Send the packet
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Sends the notification that the receiver of the message is composing a reply
+     * 
+     * @param to the recipient of the notification.
+     * @param packetID the id of the message to send.
+     */
+    public void sendComposingNotification(String to, String packetID) {
+        // Create the message to send
+        Message msg = new Message(to);
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setComposing(true);
+        messageEvent.setPacketID(packetID);
+        msg.addExtension(messageEvent);
+        // Send the packet
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Sends the notification that the receiver of the message has cancelled composing a reply.
+     * 
+     * @param to the recipient of the notification.
+     * @param packetID the id of the message to send.
+     */
+    public void sendCancelledNotification(String to, String packetID) {
+        // Create the message to send
+        Message msg = new Message(to);
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setCancelled(true);
+        messageEvent.setPacketID(packetID);
+        msg.addExtension(messageEvent);
+        // Send the packet
+        con.sendPacket(msg);
+    }
+
+    public void destroy() {
+        if (con != null) {
+            con.removePacketListener(packetListener);
+        }
+    }
+
+    public void finalize() {
+        destroy();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java
new file mode 100644
index 000000000..e6442af40
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventNotificationListener.java
@@ -0,0 +1,74 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+/**
+ *
+ * A listener that is fired anytime a message event notification is received.
+ * Message event notifications are received as a consequence of the request
+ * to receive notifications when sending a message.
+ *
+ * @author Gaston Dombiak
+ */
+public interface MessageEventNotificationListener {
+
+    /**
+     * Called when a notification of message delivered is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     */
+    public void deliveredNotification(String from, String packetID);
+
+    /**
+     * Called when a notification of message displayed is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     */
+    public void displayedNotification(String from, String packetID);
+
+    /**
+     * Called when a notification that the receiver of the message is composing a reply is 
+     * received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     */
+    public void composingNotification(String from, String packetID);
+
+    /**
+     * Called when a notification that the receiver of the message is offline is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     */
+    public void offlineNotification(String from, String packetID);
+
+    /**
+     * Called when a notification that the receiver of the message cancelled the reply 
+     * is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     */
+    public void cancelledNotification(String from, String packetID);
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java
new file mode 100644
index 000000000..107168ff4
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/MessageEventRequestListener.java
@@ -0,0 +1,86 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+/**
+ *
+ * A listener that is fired anytime a message event request is received.
+ * Message event requests are received when the received message includes an extension 
+ * like this:
+ * 
+ * <pre>
+ * &lt;x xmlns='jabber:x:event'&gt;
+ *  &lt;offline/&gt;
+ *  &lt;delivered/&gt;
+ *  &lt;composing/&gt;
+ * &lt;/x&gt;
+ * </pre>
+ * 
+ * In this example you can see that the sender of the message requests to be notified
+ * when the user couldn't receive the message because he/she is offline, the message 
+ * was delivered or when the receiver of the message is composing a reply. 
+ *
+ * @author Gaston Dombiak
+ */
+public interface MessageEventRequestListener {
+
+    /**
+     * Called when a request for message delivered notification is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     * @param messageEventManager the messageEventManager that fired the listener.
+     */
+    public void deliveredNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager);
+
+    /**
+     * Called when a request for message displayed notification is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     * @param messageEventManager the messageEventManager that fired the listener.
+     */
+    public void displayedNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager);
+
+    /**
+     * Called when a request that the receiver of the message is composing a reply notification is 
+     * received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     * @param messageEventManager the messageEventManager that fired the listener.
+     */
+    public void composingNotificationRequested(String from, String packetID,
+                MessageEventManager messageEventManager);
+
+    /**
+     * Called when a request that the receiver of the message is offline is received.
+     *  
+     * @param from the user that sent the notification.
+     * @param packetID the id of the message that was sent.
+     * @param messageEventManager the messageEventManager that fired the listener.
+     */
+    public void offlineNotificationRequested(String from, String packetID,
+            MessageEventManager messageEventManager);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java
new file mode 100644
index 000000000..2a06e2f8d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/NodeInformationProvider.java
@@ -0,0 +1,44 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+
+/**
+ * The NodeInformationProvider is responsible for providing information (i.e. DiscoverItems.Item)
+ * about a given node. This information will be requested each time this XMPPP client receives a
+ * disco items requests on the given node.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface NodeInformationProvider {
+    
+    /**
+     * Returns an Iterator on the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item} 
+     * defined in the node. For example, the MUC protocol specifies that an XMPP client should 
+     * answer an Item for each joined room when asked for the rooms where the use has joined.
+     *  
+     * @return an Iterator on the Items defined in the node.
+     */
+    public abstract Iterator getNodeItems();
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java
new file mode 100644
index 000000000..c3ff215a1
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageHeader.java
@@ -0,0 +1,85 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smackx.packet.DiscoverItems;
+
+/**
+ * The OfflineMessageHeader holds header information of an offline message. The header
+ * information was retrieved using the {@link OfflineMessageManager} class.<p>
+ *
+ * Each offline message is identified by the target user of the offline message and a unique stamp.
+ * Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageHeader {
+    /**
+     * Bare JID of the user that was offline when the message was sent.
+     */
+    private String user;
+    /**
+     * Full JID of the user that sent the message.
+     */
+    private String jid;
+    /**
+     * Stamp that uniquely identifies the offline message. This stamp will be used for
+     * getting the specific message or delete it. The stamp may be of the form UTC timestamps
+     * but it is not required to have that format.
+     */
+    private String stamp;
+
+    public OfflineMessageHeader(DiscoverItems.Item item) {
+        super();
+        user = item.getEntityID();
+        jid = item.getName();
+        stamp = item.getNode();
+    }
+
+    /**
+     * Returns the bare JID of the user that was offline when the message was sent.
+     *
+     * @return the bare JID of the user that was offline when the message was sent.
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Returns the full JID of the user that sent the message.
+     *
+     * @return the full JID of the user that sent the message.
+     */
+    public String getJid() {
+        return jid;
+    }
+
+    /**
+     * Returns the stamp that uniquely identifies the offline message. This stamp will
+     * be used for getting the specific message or delete it. The stamp may be of the
+     * form UTC timestamps but it is not required to have that format.
+     *
+     * @return the stamp that uniquely identifies the offline message.
+     */
+    public String getStamp() {
+        return stamp;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java
new file mode 100644
index 000000000..ba4331d83
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/OfflineMessageManager.java
@@ -0,0 +1,284 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+import org.jivesoftware.smackx.packet.OfflineMessageInfo;
+import org.jivesoftware.smackx.packet.OfflineMessageRequest;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * The OfflineMessageManager helps manage offline messages even before the user has sent an
+ * available presence. When a user asks for his offline messages before sending an available
+ * presence then the server will not send a flood with all the offline messages when the user
+ * becomes online. The server will not send a flood with all the offline messages to the session
+ * that made the offline messages request or to any other session used by the user that becomes
+ * online.<p>
+ *
+ * Once the session that made the offline messages request has been closed and the user becomes
+ * offline in all the resources then the server will resume storing the messages offline and will
+ * send all the offline messages to the user when he becomes online. Therefore, the server will
+ * flood the user when he becomes online unless the user uses this class to manage his offline
+ * messages.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageManager {
+
+    private final static String namespace = "http://jabber.org/protocol/offline";
+
+    private XMPPConnection connection;
+
+    private PacketFilter packetFilter;
+
+    public OfflineMessageManager(XMPPConnection connection) {
+        this.connection = connection;
+        packetFilter =
+                new AndFilter(new PacketExtensionFilter("offline", namespace),
+                        new PacketTypeFilter(Message.class));
+    }
+
+    /**
+     * Returns true if the server supports Flexible Offline Message Retrieval. When the server
+     * supports Flexible Offline Message Retrieval it is possible to get the header of the offline
+     * messages, get specific messages, delete specific messages, etc.
+     *
+     * @return a boolean indicating if the server supports Flexible Offline Message Retrieval.
+     * @throws XMPPException If the user is not allowed to make this request.
+     */
+    public boolean supportsFlexibleRetrieval() throws XMPPException {
+        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null);
+        return info.containsFeature(namespace);
+    }
+
+    /**
+     * Returns the number of offline messages for the user of the connection.
+     *
+     * @return the number of offline messages for the user of the connection.
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public int getMessageCount() throws XMPPException {
+        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null,
+                namespace);
+        Form extendedInfo = Form.getFormFrom(info);
+        if (extendedInfo != null) {
+            String value = (String) extendedInfo.getField("number_of_messages").getValues().next();
+            return Integer.parseInt(value);
+        }
+        return 0;
+    }
+
+    /**
+     * Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the
+     * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve
+     * the complete message or delete the specific message.
+     *
+     * @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline
+     *         message.
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public Iterator getHeaders() throws XMPPException {
+        List answer = new ArrayList();
+        DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(
+                null, namespace);
+        for (Iterator it = items.getItems(); it.hasNext();) {
+            DiscoverItems.Item item = (DiscoverItems.Item) it.next();
+            answer.add(new OfflineMessageHeader(item));
+        }
+        return answer.iterator();
+    }
+
+    /**
+     * Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified
+     * request. The request will include the list of stamps that uniquely identifies
+     * the offline messages to retrieve. The returned offline messages will not be deleted
+     * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages.
+     *
+     * @param nodes the list of stamps that uniquely identifies offline message.
+     * @return an Iterator with the offline <tt>Messages</tt> that were received as part of
+     *         this request.
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public Iterator getMessages(final List nodes) throws XMPPException {
+        List messages = new ArrayList();
+        OfflineMessageRequest request = new OfflineMessageRequest();
+        for (Iterator it = nodes.iterator(); it.hasNext();) {
+            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next());
+            item.setAction("view");
+            request.addItem(item);
+        }
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Filter offline messages that were requested by this request
+        PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() {
+            public boolean accept(Packet packet) {
+                OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline",
+                        namespace);
+                return nodes.contains(info.getNode());
+            }
+        });
+        PacketCollector messageCollector = connection.createPacketCollector(messageFilter);
+        // Send the retrieval request to the server.
+        connection.sendPacket(request);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        } else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+
+        // Collect the received offline messages
+        Message message = (Message) messageCollector.nextResult(
+                SmackConfiguration.getPacketReplyTimeout());
+        while (message != null) {
+            messages.add(message);
+            message =
+                    (Message) messageCollector.nextResult(
+                            SmackConfiguration.getPacketReplyTimeout());
+        }
+        // Stop queuing offline messages
+        messageCollector.cancel();
+        return messages.iterator();
+    }
+
+    /**
+     * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline
+     * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)}
+     * to delete the messages.
+     *
+     * @return an Iterator with all the offline <tt>Messages</tt> of the user.
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public Iterator getMessages() throws XMPPException {
+        List messages = new ArrayList();
+        OfflineMessageRequest request = new OfflineMessageRequest();
+        request.setFetch(true);
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Filter offline messages that were requested by this request
+        PacketCollector messageCollector = connection.createPacketCollector(packetFilter);
+        // Send the retrieval request to the server.
+        connection.sendPacket(request);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        } else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+
+        // Collect the received offline messages
+        Message message = (Message) messageCollector.nextResult(
+                SmackConfiguration.getPacketReplyTimeout());
+        while (message != null) {
+            messages.add(message);
+            message =
+                    (Message) messageCollector.nextResult(
+                            SmackConfiguration.getPacketReplyTimeout());
+        }
+        // Stop queuing offline messages
+        messageCollector.cancel();
+        return messages.iterator();
+    }
+
+    /**
+     * Deletes the specified list of offline messages. The request will include the list of
+     * stamps that uniquely identifies the offline messages to delete.
+     *
+     * @param nodes the list of stamps that uniquely identifies offline message.
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public void deleteMessages(List nodes) throws XMPPException {
+        OfflineMessageRequest request = new OfflineMessageRequest();
+        for (Iterator it = nodes.iterator(); it.hasNext();) {
+            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next());
+            item.setAction("remove");
+            request.addItem(item);
+        }
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the deletion request to the server.
+        connection.sendPacket(request);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        } else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    /**
+     * Deletes all offline messages of the user.
+     *
+     * @throws XMPPException If the user is not allowed to make this request or the server does
+     *                       not support offline message retrieval.
+     */
+    public void deleteMessages() throws XMPPException {
+        OfflineMessageRequest request = new OfflineMessageRequest();
+        request.setPurge(true);
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the deletion request to the server.
+        connection.sendPacket(request);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        } else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java
new file mode 100644
index 000000000..96bc6f781
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/PrivateDataManager.java
@@ -0,0 +1,345 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smackx.packet.*;
+import org.jivesoftware.smackx.provider.*;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.Map;
+import java.util.Hashtable;
+
+/**
+ * Manages private data, which is a mechanism to allow users to store arbitrary XML
+ * data on an XMPP server. Each private data chunk is defined by a element name and
+ * XML namespace. Example private data:
+ *
+ * <pre>
+ * &lt;color xmlns="http://example.com/xmpp/color"&gt;
+ *     &lt;favorite&gt;blue&lt;/blue&gt;
+ *     &lt;leastFavorite&gt;puce&lt;/leastFavorite&gt;
+ * &lt;/color&gt;
+ * </pre>
+ *
+ * {@link PrivateDataProvider} instances are responsible for translating the XML into objects.
+ * If no PrivateDataProvider is registered for a given element name and namespace, then
+ * a {@link DefaultPrivateData} instance will be returned.<p>
+ *
+ * Warning: this is an non-standard protocol documented by
+ * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a
+ * non-standard protocol, it is subject to change.
+ *
+ * @author Matt Tucker
+ */
+public class PrivateDataManager {
+
+    /**
+     * Map of provider instances.
+     */
+    private static Map privateDataProviders = new Hashtable();
+
+    /**
+     * Returns the private data provider registered to the specified XML element name and namespace.
+     * For example, if a provider was registered to the element name "prefs" and the
+     * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger
+     * the provider:
+     *
+     * <pre>
+     * &lt;iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'&gt;
+     *     &lt;query xmlns='jabber:iq:private'&gt;
+     *         &lt;prefs xmlns='http://www.xmppclient.com/prefs'&gt;
+     *             &lt;value1&gt;ABC&lt;/value1&gt;
+     *             &lt;value2&gt;XYZ&lt;/value2&gt;
+     *         &lt;/prefs&gt;
+     *     &lt;/query&gt;
+     * &lt;/iq&gt;</pre>
+     *
+     * <p>Note: this method is generally only called by the internal Smack classes.
+     *
+     * @param elementName the XML element name.
+     * @param namespace the XML namespace.
+     * @return the PrivateData provider.
+     */
+    public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) {
+        String key = getProviderKey(elementName, namespace);
+        return (PrivateDataProvider)privateDataProviders.get(key);
+    }
+
+    /**
+     * Adds a private data provider with the specified element name and name space. The provider
+     * will override any providers loaded through the classpath.
+     *
+     * @param elementName the XML element name.
+     * @param namespace the XML namespace.
+     * @param provider the private data provider.
+     */
+    public static void addPrivateDataProvider(String elementName, String namespace,
+            PrivateDataProvider provider)
+    {
+        String key = getProviderKey(elementName, namespace);
+        privateDataProviders.put(key, provider);
+    }
+
+
+    private XMPPConnection connection;
+
+    /**
+     * The user to get and set private data for. In most cases, this value should
+     * be <tt>null</tt>, as the typical use of private data is to get and set
+     * your own private data and not others.
+     */
+    private String user;
+
+    /**
+     * Creates a new private data manager. The connection must have
+     * undergone a successful login before being used to construct an instance of
+     * this class.
+     *
+     * @param connection an XMPP connection which must have already undergone a
+     *      successful login.
+     */
+    public PrivateDataManager(XMPPConnection connection) {
+        if (!connection.isAuthenticated()) {
+            throw new IllegalStateException("Must be logged in to XMPP server.");
+        }
+        this.connection = connection;
+    }
+
+    /**
+     * Creates a new private data manager for a specific user (special case). Most
+     * servers only support getting and setting private data for the user that
+     * authenticated via the connection. However, some servers support the ability
+     * to get and set private data for other users (for example, if you are the
+     * administrator). The connection must have undergone a successful login before
+     * being used to construct an instance of this class.
+     *
+     * @param connection an XMPP connection which must have already undergone a
+     *      successful login.
+     * @param user the XMPP address of the user to get and set private data for.
+     */
+    public PrivateDataManager(XMPPConnection connection, String user) {
+        if (!connection.isAuthenticated()) {
+            throw new IllegalStateException("Must be logged in to XMPP server.");
+        }
+        this.connection = connection;
+        this.user = user;
+    }
+
+    /**
+     * Returns the private data specified by the given element name and namespace. Each chunk
+     * of private data is uniquely identified by an element name and namespace pair.<p>
+     *
+     * If a PrivateDataProvider is registered for the specified element name/namespace pair then
+     * that provider will determine the specific object type that is returned. If no provider
+     * is registered, a {@link DefaultPrivateData} instance will be returned.
+     *
+     * @param elementName the element name.
+     * @param namespace the namespace.
+     * @return the private data.
+     * @throws XMPPException if an error occurs getting the private data.
+     */
+    public PrivateData getPrivateData(final String elementName, final String namespace)
+            throws XMPPException
+    {
+        // Create an IQ packet to get the private data.
+        IQ privateDataGet = new IQ() {
+            public String getChildElementXML() {
+                StringBuffer buf = new StringBuffer();
+                buf.append("<query xmlns=\"jabber:iq:private\">");
+                buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>");
+                buf.append("</query>");
+                return buf.toString();
+            }
+        };
+        privateDataGet.setType(IQ.Type.GET);
+        // Address the packet to the other account if user has been set.
+        if (user != null) {
+            privateDataGet.setTo(user);
+        }
+
+        // Setup a listener for the reply to the set operation.
+        String packetID = privateDataGet.getPacketID();
+        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
+
+        // Send the private data.
+        connection.sendPacket(privateDataGet);
+
+        // Wait up to five seconds for a response from the server.
+        IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (response == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        // If the server replied with an error, throw an exception.
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+        return ((PrivateDataResult)response).getPrivateData();
+    }
+
+    /**
+     * Sets a private data value. Each chunk of private data is uniquely identified by an
+     * element name and namespace pair. If private data has already been set with the
+     * element name and namespace, then the new private data will overwrite the old value.
+     *
+     * @param privateData the private data.
+     * @throws XMPPException if setting the private data fails.
+     */
+    public void setPrivateData(final PrivateData privateData) throws XMPPException {
+        // Create an IQ packet to set the private data.
+        IQ privateDataSet = new IQ() {
+            public String getChildElementXML() {
+                StringBuffer buf = new StringBuffer();
+                buf.append("<query xmlns=\"jabber:iq:private\">");
+                buf.append(privateData.toXML());
+                buf.append("</query>");
+                return buf.toString();
+            }
+        };
+        privateDataSet.setType(IQ.Type.SET);
+        // Address the packet to the other account if user has been set.
+        if (user != null) {
+            privateDataSet.setTo(user);
+        }
+
+        // Setup a listener for the reply to the set operation.
+        String packetID = privateDataSet.getPacketID();
+        PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID));
+
+        // Send the private data.
+        connection.sendPacket(privateDataSet);
+
+        // Wait up to five seconds for a response from the server.
+        IQ response = (IQ)collector.nextResult(5000);
+        // Stop queuing results
+        collector.cancel();
+        if (response == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        // If the server replied with an error, throw an exception.
+        else if (response.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(response.getError());
+        }
+    }
+
+    /**
+     * Returns a String key for a given element name and namespace.
+     *
+     * @param elementName the element name.
+     * @param namespace the namespace.
+     * @return a unique key for the element name and namespace pair.
+     */
+    private static String getProviderKey(String elementName, String namespace) {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(elementName).append("/><").append(namespace).append("/>");
+        return buf.toString();
+    }
+
+    /**
+     * An IQ provider to parse IQ results containing private data.
+     */
+    public static class PrivateDataIQProvider implements IQProvider {
+        public IQ parseIQ(XmlPullParser parser) throws Exception {
+            PrivateData privateData = null;
+            boolean done = false;
+            while (!done) {
+                int eventType = parser.next();
+                if (eventType == XmlPullParser.START_TAG) {
+                    String elementName = parser.getName();
+                    String namespace = parser.getNamespace();
+                    // See if any objects are registered to handle this private data type.
+                    PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace);
+                    // If there is a registered provider, use it.
+                    if (provider != null) {
+                        privateData = provider.parsePrivateData(parser);
+                    }
+                    // Otherwise, use a DefaultPrivateData instance to store the private data.
+                    else {
+                        DefaultPrivateData data = new DefaultPrivateData(elementName, namespace);
+                        boolean finished = false;
+                        while (!finished) {
+                            int event = parser.next();
+                            if (event == XmlPullParser.START_TAG) {
+                                String name = parser.getName();
+                                // If an empty element, set the value with the empty string.
+                                if (parser.isEmptyElementTag()) {
+                                    data.setValue(name,"");
+                                }
+                                // Otherwise, get the the element text.
+                                else {
+                                    event = parser.next();
+                                    if (event == XmlPullParser.TEXT) {
+                                        String value = parser.getText();
+                                        data.setValue(name, value);
+                                    }
+                                }
+                            }
+                            else if (event == XmlPullParser.END_TAG) {
+                                if (parser.getName().equals(elementName)) {
+                                    finished = true;
+                                }
+                            }
+                        }
+                        privateData = data;
+                    }
+                }
+                else if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("query")) {
+                        done = true;
+                    }
+                }
+            }
+            IQ result = new PrivateDataResult(privateData);
+            return result;
+        }
+    }
+
+    /**
+     * An IQ packet to hold PrivateData GET results.
+     */
+    private static class PrivateDataResult extends IQ {
+
+        private PrivateData privateData;
+
+        PrivateDataResult(PrivateData privateData) {
+            this.privateData = privateData;
+        }
+
+        public PrivateData getPrivateData() {
+            return privateData;
+        }
+
+        public String getChildElementXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<query xmlns=\"jabber:iq:private\">");
+            if (privateData != null) {
+                privateData.toXML();
+            }
+            buf.append("</query>");
+            return buf.toString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java b/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java
new file mode 100644
index 000000000..e8c62de31
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/RemoteRosterEntry.java
@@ -0,0 +1,118 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+/**
+ * Represents a roster item, which consists of a JID and , their name and
+ * the groups the roster item belongs to. This roster item does not belong
+ * to the local roster. Therefore, it does not persist in the server.<p> 
+ * 
+ * The idea of a RemoteRosterEntry is to be used as part of a roster exchange.
+ *
+ * @author Gaston Dombiak
+ */
+public class RemoteRosterEntry {
+
+    private String user;
+    private String name;
+    private List groupNames = new ArrayList();
+
+    /**
+     * Creates a new remote roster entry.
+     *
+     * @param user the user.
+     * @param name the user's name.
+     * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
+     *      the roster entry won't belong to a group.
+     */
+    public RemoteRosterEntry(String user, String name, String [] groups) {
+        this.user = user;
+        this.name = name;
+		if (groups != null) {
+			groupNames = new ArrayList(Arrays.asList(groups));
+		}
+    }
+
+    /**
+     * Returns the user.
+     *
+     * @return the user.
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Returns the user's name.
+     *
+     * @return the user's name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns an Iterator for the group names (as Strings) that the roster entry
+     * belongs to.
+     *
+     * @return an Iterator for the group names.
+     */
+    public Iterator getGroupNames() {
+        synchronized (groupNames) {
+            return Collections.unmodifiableList(groupNames).iterator();
+        }
+    }
+
+    /**
+     * Returns a String array for the group names that the roster entry
+     * belongs to.
+     *
+     * @return a String[] for the group names.
+     */
+    public String[] getGroupArrayNames() {
+        synchronized (groupNames) {
+            return (String[])
+                (Collections
+                    .unmodifiableList(groupNames)
+                    .toArray(new String[groupNames.size()]));
+        }
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<item jid=\"").append(user).append("\"");
+        if (name != null) {
+            buf.append(" name=\"").append(name).append("\"");
+        }
+        buf.append(">");
+        synchronized (groupNames) {
+            for (int i = 0; i < groupNames.size(); i++) {
+                String groupName = (String) groupNames.get(i);
+                buf.append("<group>").append(groupName).append("</group>");
+            }
+        }
+        buf.append("</item>");
+        return buf.toString();
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java b/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java
new file mode 100644
index 000000000..35a446afc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/ReportedData.java
@@ -0,0 +1,255 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.packet.DataForm;
+
+/**
+ * Represents a set of data results returned as part of a search. The report is structured 
+ * in columns and rows.
+ * 
+ * @author Gaston Dombiak
+ */
+public class ReportedData {
+    
+    private List columns = new ArrayList();
+    private List rows = new ArrayList();
+    private String title = "";
+    
+    /**
+     * Returns a new ReportedData if the packet is used for reporting data and includes an 
+     * extension that matches the elementName and namespace "x","jabber:x:data".
+     * 
+     * @param packet the packet used for reporting data.
+     */
+    public static ReportedData getReportedDataFrom(Packet packet) {
+        // Check if the packet includes the DataForm extension
+        PacketExtension packetExtension = packet.getExtension("x","jabber:x:data");
+        if (packetExtension != null) {
+            // Check if the existing DataForm is a result of a search
+            DataForm dataForm = (DataForm) packetExtension;
+            if (dataForm.getReportedData() != null)
+                return new ReportedData(dataForm);
+        }
+        // Otherwise return null
+        return null;
+    }
+    
+    
+    /**
+     * Creates a new ReportedData based on the returned dataForm from a search
+     *(namespace "jabber:iq:search"). 
+     * 
+     * @param dataForm the dataForm returned from a search (namespace "jabber:iq:search"). 
+     */
+    private ReportedData(DataForm dataForm) {
+        // Add the columns to the report based on the reported data fields
+        for (Iterator fields = dataForm.getReportedData().getFields(); fields.hasNext();) {
+            FormField field = (FormField)fields.next();
+            columns.add(new Column(field.getLabel(), field.getVariable(), field.getType()));
+        }
+
+        // Add the rows to the report based on the form's items
+        for (Iterator items = dataForm.getItems(); items.hasNext();) {
+            DataForm.Item item = (DataForm.Item)items.next();
+            List fieldList = new ArrayList(columns.size());
+            FormField field;
+            for (Iterator fields = item.getFields(); fields.hasNext();) {
+                field = (FormField) fields.next();
+                // The field is created with all the values of the data form's field
+                List values = new ArrayList();
+                for (Iterator it=field.getValues(); it.hasNext();) {
+                    values.add(it.next());
+                }
+                fieldList.add(new Field(field.getVariable(), values));
+            }
+            rows.add(new Row(fieldList));
+        }
+
+        // Set the report's title
+        this.title = dataForm.getTitle();
+    }
+    
+    /**
+     * Returns an Iterator for the rows returned from a search.
+     *
+     * @return an Iterator for the rows returned from a search.
+     */
+    public Iterator getRows() {
+        return Collections.unmodifiableList(new ArrayList(rows)).iterator();
+    }
+
+    /**
+     * Returns an Iterator for the columns returned from a search.
+     * 
+     * @return an Iterator for the columns returned from a search.
+     */
+    public Iterator getColumns() {
+        return Collections.unmodifiableList(new ArrayList(columns)).iterator();
+    }
+
+
+    /**
+     * Returns the report's title. It is similar to the title on a web page or an X 
+     * window.
+     * 
+     * @return title of the report.
+     */
+    public String getTitle() {
+        return title;
+    }
+    
+    /**
+     * 
+     * Represents the columns definition of the reported data.
+     * 
+     * @author Gaston Dombiak
+     */
+    public static class Column {
+        private String label;
+        private String variable;
+        private String type;
+
+        /**
+         * Creates a new column with the specified definition.
+         * 
+         * @param label the columns's label.
+         * @param variable the variable name of the column.
+         * @param type the format for the returned data.
+         */
+        private Column(String label, String variable, String type) {
+            this.label = label;
+            this.variable = variable;
+            this.type = type;
+        }
+        
+        /**
+         * Returns the column's label.
+         * 
+         * @return label of the column.
+         */
+        public String getLabel() {
+            return label;
+        }
+
+
+        /**
+         * Returns the column's data format. Valid formats are:
+         * 
+         * <ul>
+         *  <li>text-single -> single line or word of text
+         *  <li>text-private -> instead of showing the user what they typed, you show ***** to 
+         * protect it
+         *  <li>text-multi -> multiple lines of text entry
+         *  <li>list-single -> given a list of choices, pick one
+         *  <li>list-multi -> given a list of choices, pick one or more
+         *  <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
+         *  <li>fixed -> fixed for putting in text to show sections, or just advertise your web 
+         * site in the middle of the form
+         *  <li>hidden -> is not given to the user at all, but returned with the questionnaire
+         *  <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based 
+         * on the rules for a JID.
+         *  <li>jid-multi -> multiple entries for JIDs
+         * </ul>
+         * 
+         * @return format for the returned data.
+         */
+        public String getType() {
+            return type;
+        }
+
+
+        /**
+         * Returns the variable name that the column is showing.
+         * 
+         * @return the variable name of the column.
+         */
+        public String getVariable() {
+            return variable;
+        }
+
+
+    }
+    
+    public static class Row {
+        private List fields = new ArrayList();
+        
+        private Row(List fields) {
+            this.fields = fields;
+        }
+        
+        /**
+         * Returns the values of the field whose variable matches the requested variable.
+         * 
+         * @param variable the variable to match.
+         * @return the values of the field whose variable matches the requested variable.
+         */
+        public Iterator getValues(String variable) {
+            for(Iterator it=getFields();it.hasNext();) {
+                Field field = (Field) it.next();
+                if (variable.equals(field.getVariable())) {
+                    return field.getValues();
+                }
+            }
+            return null;
+        }
+        
+        /**
+         * Returns the fields that define the data that goes with the item.
+         * 
+         * @return the fields that define the data that goes with the item.
+         */
+        private Iterator getFields() {
+            return Collections.unmodifiableList(new ArrayList(fields)).iterator();
+        }
+    }
+    
+    private static class Field {
+        private String variable;
+        private List values;
+
+        private Field(String variable, List values) {
+            this.variable = variable;
+            this.values = values;
+        }
+
+        /**
+         * Returns the variable name that the field represents.
+         * 
+         * @return the variable name of the field.
+         */
+        public String getVariable() {
+            return variable;
+        }
+
+        /**
+         * Returns an iterator on the values reported as part of the search.
+         * 
+         * @return the returned values of the search.
+         */
+        public Iterator getValues() {
+            return Collections.unmodifiableList(values).iterator();
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java
new file mode 100644
index 000000000..2c7460b62
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeListener.java
@@ -0,0 +1,42 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+/**
+ *
+ * A listener that is fired anytime a roster exchange is received.
+ *
+ * @author Gaston Dombiak
+ */
+public interface RosterExchangeListener {
+
+    /**
+     * Called when roster entries are received as part of a roster exchange.
+     *  
+     * @param from the user that sent the entries.
+     * @param remoteRosterEntries the entries sent by the user. The entries are instances of 
+     * RemoteRosterEntry.
+     */
+    public void entriesReceived(String from, Iterator remoteRosterEntries);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java
new file mode 100644
index 000000000..66c4f477f
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/RosterExchangeManager.java
@@ -0,0 +1,177 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.packet.RosterExchange;
+
+/**
+ *
+ * Manages Roster exchanges. A RosterExchangeManager provides a high level access to send 
+ * rosters, roster groups and roster entries to XMPP clients. It also provides an easy way
+ * to hook up custom logic when entries are received from another XMPP client through 
+ * RosterExchangeListeners.
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchangeManager {
+
+    private List rosterExchangeListeners = new ArrayList();
+
+    private XMPPConnection con;
+
+    private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster");
+    private PacketListener packetListener;
+
+    /**
+     * Creates a new roster exchange manager.
+     *
+     * @param con an XMPPConnection.
+     */
+    public RosterExchangeManager(XMPPConnection con) {
+        this.con = con;
+        init();
+    }
+
+    /**
+     * Adds a listener to roster exchanges. The listener will be fired anytime roster entries
+     * are received from remote XMPP clients.
+     *
+     * @param rosterExchangeListener a roster exchange listener.
+     */
+    public void addRosterListener(RosterExchangeListener rosterExchangeListener) {
+        synchronized (rosterExchangeListeners) {
+            if (!rosterExchangeListeners.contains(rosterExchangeListener)) {
+                rosterExchangeListeners.add(rosterExchangeListener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from roster exchanges. The listener will be fired anytime roster 
+     * entries are received from remote XMPP clients.
+     *
+     * @param rosterExchangeListener a roster exchange listener..
+     */
+    public void removeRosterListener(RosterExchangeListener rosterExchangeListener) {
+        synchronized (rosterExchangeListeners) {
+            rosterExchangeListeners.remove(rosterExchangeListener);
+        }
+    }
+
+    /**
+     * Sends a roster to userID. All the entries of the roster will be sent to the
+     * target user.
+     * 
+     * @param roster the roster to send
+     * @param targetUserID the user that will receive the roster entries
+     */
+    public void send(Roster roster, String targetUserID) {
+        // Create a new message to send the roster
+        Message msg = new Message(targetUserID);
+        // Create a RosterExchange Package and add it to the message
+        RosterExchange rosterExchange = new RosterExchange(roster);
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Sends a roster entry to userID.
+     * 
+     * @param rosterEntry the roster entry to send
+     * @param targetUserID the user that will receive the roster entries
+     */
+    public void send(RosterEntry rosterEntry, String targetUserID) {
+        // Create a new message to send the roster
+        Message msg = new Message(targetUserID);
+        // Create a RosterExchange Package and add it to the message
+        RosterExchange rosterExchange = new RosterExchange();
+        rosterExchange.addRosterEntry(rosterEntry);
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Sends a roster group to userID. All the entries of the group will be sent to the 
+     * target user.
+     * 
+     * @param rosterGroup the roster group to send
+     * @param targetUserID the user that will receive the roster entries
+     */
+    public void send(RosterGroup rosterGroup, String targetUserID) {
+        // Create a new message to send the roster
+        Message msg = new Message(targetUserID);
+        // Create a RosterExchange Package and add it to the message
+        RosterExchange rosterExchange = new RosterExchange();
+        for (Iterator it = rosterGroup.getEntries(); it.hasNext();)
+            rosterExchange.addRosterEntry((RosterEntry) it.next());
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        con.sendPacket(msg);
+    }
+
+    /**
+     * Fires roster exchange listeners.
+     */
+    private void fireRosterExchangeListeners(String from, Iterator remoteRosterEntries) {
+        RosterExchangeListener[] listeners = null;
+        synchronized (rosterExchangeListeners) {
+            listeners = new RosterExchangeListener[rosterExchangeListeners.size()];
+            rosterExchangeListeners.toArray(listeners);
+        }
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].entriesReceived(from, remoteRosterEntries);
+        }
+    }
+
+    private void init() {
+        // Listens for all roster exchange packets and fire the roster exchange listeners.
+        packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                RosterExchange rosterExchange =
+                    (RosterExchange) message.getExtension("x", "jabber:x:roster");
+                // Fire event for roster exchange listeners
+                fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries());
+            };
+
+        };
+        con.addPacketListener(packetListener, packetFilter);
+    }
+
+    public void destroy() {
+        if (con != null)
+            con.removePacketListener(packetListener);
+
+    }
+    public void finalize() {
+        destroy();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java
new file mode 100644
index 000000000..7cdbd0510
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/ServiceDiscoveryManager.java
@@ -0,0 +1,476 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Manages discovery of services in XMPP entities. This class provides:
+ * <ol>
+ * <li>A registry of supported features in this XMPP entity.
+ * <li>Automatic response when this XMPP entity is queried for information.
+ * <li>Ability to discover items and information of remote XMPP entities.
+ * <li>Ability to publish publicly available items.
+ * </ol>  
+ * 
+ * @author Gaston Dombiak
+ */
+public class ServiceDiscoveryManager {
+
+    private static String identityName = "Smack";
+    private static String identityType = "pc";
+
+    private static Map instances = new Hashtable();
+
+    private XMPPConnection connection;
+    private List features = new ArrayList();
+    private Map nodeInformationProviders = new Hashtable();
+
+    // Create a new ServiceDiscoveryManager on every established connection
+    static {
+        XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
+            public void connectionEstablished(XMPPConnection connection) {
+                new ServiceDiscoveryManager(connection);
+            }
+        });
+    }
+
+    /**
+     * Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the 
+     * service manager will respond to any service discovery request that the connection may
+     * receive. 
+     * 
+     * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
+     */
+    public ServiceDiscoveryManager(XMPPConnection connection) {
+        this.connection = connection;
+        init();
+    }
+
+    /**
+     * Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection.
+     * 
+     * @param connection the connection used to look for the proper ServiceDiscoveryManager.
+     * @return the ServiceDiscoveryManager associated with a given XMPPConnection.
+     */
+    public static ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) {
+        return (ServiceDiscoveryManager) instances.get(connection);
+    }
+
+    /**
+     * Returns the name of the client that will be returned when asked for the client identity
+     * in a disco request. The name could be any value you need to identity this client.
+     * 
+     * @return the name of the client that will be returned when asked for the client identity
+     *          in a disco request.
+     */
+    public static String getIdentityName() {
+        return identityName;
+    }
+
+    /**
+     * Sets the name of the client that will be returned when asked for the client identity
+     * in a disco request. The name could be any value you need to identity this client.
+     * 
+     * @param name the name of the client that will be returned when asked for the client identity
+     *          in a disco request.
+     */
+    public static void setIdentityName(String name) {
+        identityName = name;
+    }
+
+    /**
+     * Returns the type of client that will be returned when asked for the client identity in a 
+     * disco request. The valid types are defined by the category client. Follow this link to learn 
+     * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
+     * 
+     * @return the type of client that will be returned when asked for the client identity in a 
+     *          disco request.
+     */
+    public static String getIdentityType() {
+        return identityType;
+    }
+
+    /**
+     * Sets the type of client that will be returned when asked for the client identity in a 
+     * disco request. The valid types are defined by the category client. Follow this link to learn 
+     * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
+     * 
+     * @param type the type of client that will be returned when asked for the client identity in a 
+     *          disco request.
+     */
+    public static void setIdentityType(String type) {
+        identityType = type;
+    }
+
+    /**
+     * Initializes the packet listeners of the connection that will answer to any
+     * service discovery request. 
+     */
+    private void init() {
+        // Register the new instance and associate it with the connection 
+        instances.put(connection, this);
+        // Add a listener to the connection that removes the registered instance when
+        // the connection is closed
+        connection.addConnectionListener(new ConnectionListener() {
+            public void connectionClosed() {
+                // Unregister this instance since the connection has been closed
+                instances.remove(connection);
+            }
+
+            public void connectionClosedOnError(Exception e) {
+                // Unregister this instance since the connection has been closed
+                instances.remove(connection);
+            }
+        });
+
+        // Listen for disco#items requests and answer with an empty result        
+        PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                DiscoverItems discoverItems = (DiscoverItems) packet;
+                // Send back the items defined in the client if the request is of type GET
+                if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
+                    DiscoverItems response = new DiscoverItems();
+                    response.setType(IQ.Type.RESULT);
+                    response.setTo(discoverItems.getFrom());
+                    response.setPacketID(discoverItems.getPacketID());
+
+                    // Add the defined items related to the requested node. Look for 
+                    // the NodeInformationProvider associated with the requested node.  
+                    if (getNodeInformationProvider(discoverItems.getNode()) != null) {
+                        Iterator items =
+                            getNodeInformationProvider(discoverItems.getNode()).getNodeItems();
+                        while (items.hasNext()) {
+                            response.addItem((DiscoverItems.Item) items.next());
+                        }
+                    }
+                    connection.sendPacket(response);
+                }
+            }
+        };
+        connection.addPacketListener(packetListener, packetFilter);
+
+        // Listen for disco#info requests and answer the client's supported features 
+        // To add a new feature as supported use the #addFeature message        
+        packetFilter = new PacketTypeFilter(DiscoverInfo.class);
+        packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                DiscoverInfo discoverInfo = (DiscoverInfo) packet;
+                // Answer the client's supported features if the request is of the GET type
+                if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
+                    DiscoverInfo response = new DiscoverInfo();
+                    response.setType(IQ.Type.RESULT);
+                    response.setTo(discoverInfo.getFrom());
+                    response.setPacketID(discoverInfo.getPacketID());
+                    // Add the client's identity and features only if "node" is null
+                    if (discoverInfo.getNode() == null) {
+                        // Set this client identity
+                        DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client",
+                                getIdentityName());
+                        identity.setType(getIdentityType());
+                        response.addIdentity(identity);
+                        // Add the registered features to the response
+                        synchronized (features) {
+                            for (Iterator it = getFeatures(); it.hasNext();) {
+                                response.addFeature((String) it.next());
+                            }
+                        }
+                    }
+                    else {
+                        // Return an <item-not-found/> error since a client doesn't have nodes
+                        response.setType(IQ.Type.ERROR);
+                        response.setError(new XMPPError(404, "item-not-found"));
+                    }
+                    connection.sendPacket(response);
+                }
+            }
+        };
+        connection.addPacketListener(packetListener, packetFilter);
+    }
+
+    /**
+     * Returns the NodeInformationProvider responsible for providing information 
+     * (ie items) related to a given node or <tt>null</null> if none.<p>
+     * 
+     * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
+     * NodeInformationProvider will provide information about the rooms where the user has joined.
+     * 
+     * @param node the node that contains items associated with an entity not addressable as a JID.
+     * @return the NodeInformationProvider responsible for providing information related 
+     * to a given node.
+     */
+    private NodeInformationProvider getNodeInformationProvider(String node) {
+        if (node == null) {
+            return null;
+        }
+        return (NodeInformationProvider) nodeInformationProviders.get(node);
+    }
+
+    /**
+     * Sets the NodeInformationProvider responsible for providing information 
+     * (ie items) related to a given node. Every time this client receives a disco request
+     * regarding the items of a given node, the provider associated to that node will be the 
+     * responsible for providing the requested information.<p>
+     * 
+     * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
+     * NodeInformationProvider will provide information about the rooms where the user has joined. 
+     * 
+     * @param node the node whose items will be provided by the NodeInformationProvider.
+     * @param listener the NodeInformationProvider responsible for providing items related
+     *      to the node.
+     */
+    public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
+        nodeInformationProviders.put(node, listener);
+    }
+
+    /**
+     * Removes the NodeInformationProvider responsible for providing information 
+     * (ie items) related to a given node. This means that no more information will be
+     * available for the specified node.
+     * 
+     * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
+     * NodeInformationProvider will provide information about the rooms where the user has joined. 
+     * 
+     * @param node the node to remove the associated NodeInformationProvider.
+     */
+    public void removeNodeInformationProvider(String node) {
+        nodeInformationProviders.remove(node);
+    }
+
+    /**
+     * Returns the supported features by this XMPP entity.
+     * 
+     * @return an Iterator on the supported features by this XMPP entity.
+     */
+    public Iterator getFeatures() {
+        synchronized (features) {
+            return Collections.unmodifiableList(new ArrayList(features)).iterator();
+        }
+    }
+
+    /**
+     * Registers that a new feature is supported by this XMPP entity. When this client is 
+     * queried for its information the registered features will be answered.<p>
+     *
+     * Since no packet is actually sent to the server it is safe to perform this operation
+     * before logging to the server. In fact, you may want to configure the supported features
+     * before logging to the server so that the information is already available if it is required
+     * upon login.
+     *
+     * @param feature the feature to register as supported.
+     */
+    public void addFeature(String feature) {
+        synchronized (features) {
+            features.add(feature);
+        }
+    }
+
+    /**
+     * Removes the specified feature from the supported features by this XMPP entity.<p>
+     *
+     * Since no packet is actually sent to the server it is safe to perform this operation
+     * before logging to the server.
+     *
+     * @param feature the feature to remove from the supported features.
+     */
+    public void removeFeature(String feature) {
+        synchronized (features) {
+            features.remove(feature);
+        }
+    }
+
+    /**
+     * Returns true if the specified feature is registered in the ServiceDiscoveryManager.
+     *
+     * @param feature the feature to look for.
+     * @return a boolean indicating if the specified featured is registered or not.
+     */
+    public boolean includesFeature(String feature) {
+        synchronized (features) {
+            return features.contains(feature);
+        }
+    }
+
+    /**
+     * Returns the discovered information of a given XMPP entity addressed by its JID.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
+        return discoverInfo(entityID, null);
+    }
+
+    /**
+     * Returns the discovered information of a given XMPP entity addressed by its JID and
+     * note attribute. Use this message only when trying to query information which is not 
+     * directly addressable.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException {
+        // Discover the entity's info
+        DiscoverInfo disco = new DiscoverInfo();
+        disco.setType(IQ.Type.GET);
+        disco.setTo(entityID);
+        disco.setNode(node);
+
+        // Create a packet collector to listen for a response.
+        PacketCollector collector =
+            connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
+
+        connection.sendPacket(disco);
+
+        // Wait up to 5 seconds for a result.
+        IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+        return (DiscoverInfo) result;
+    }
+
+    /**
+     * Returns the discovered items of a given XMPP entity addressed by its JID.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return the discovered information.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverItems discoverItems(String entityID) throws XMPPException {
+        return discoverItems(entityID, null);
+    }
+
+    /**
+     * Returns the discovered items of a given XMPP entity addressed by its JID and
+     * note attribute. Use this message only when trying to query information which is not 
+     * directly addressable.
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @return the discovered items.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
+        // Discover the entity's items
+        DiscoverItems disco = new DiscoverItems();
+        disco.setType(IQ.Type.GET);
+        disco.setTo(entityID);
+        disco.setNode(node);
+
+        // Create a packet collector to listen for a response.
+        PacketCollector collector =
+            connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
+
+        connection.sendPacket(disco);
+
+        // Wait up to 5 seconds for a result.
+        IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+        return (DiscoverItems) result;
+    }
+
+    /**
+     * Returns true if the server supports publishing of items. A client may wish to publish items
+     * to the server so that the server can provide items associated to the client. These items will
+     * be returned by the server whenever the server receives a disco request targeted to the bare
+     * address of the client (i.e. user@host.com).
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @return true if the server supports publishing of items.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public boolean canPublishItems(String entityID) throws XMPPException {
+        DiscoverInfo info = discoverInfo(entityID);
+        return info.containsFeature("http://jabber.org/protocol/disco#publish");
+    }
+
+    /**
+     * Publishes new items to a parent entity. The item elements to publish MUST have at least 
+     * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 
+     * specifies the action being taken for that item. Possible action values are: "update" and 
+     * "remove".
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param discoverItems the DiscoveryItems to publish.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public void publishItems(String entityID, DiscoverItems discoverItems)
+            throws XMPPException {
+        publishItems(entityID, null, discoverItems);
+    }
+
+    /**
+     * Publishes new items to a parent entity and node. The item elements to publish MUST have at 
+     * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 
+     * specifies the action being taken for that item. Possible action values are: "update" and 
+     * "remove".
+     * 
+     * @param entityID the address of the XMPP entity.
+     * @param node the attribute that supplements the 'jid' attribute.
+     * @param discoverItems the DiscoveryItems to publish.
+     * @throws XMPPException if the operation failed for some reason.
+     */
+    public void publishItems(String entityID, String node, DiscoverItems discoverItems)
+            throws XMPPException {
+        discoverItems.setType(IQ.Type.SET);
+        discoverItems.setTo(entityID);
+        discoverItems.setNode(node);
+
+        // Create a packet collector to listen for a response.
+        PacketCollector collector =
+            connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID()));
+
+        connection.sendPacket(discoverItems);
+
+        // Wait up to 5 seconds for a result.
+        IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from the server.");
+        }
+        if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java
new file mode 100644
index 000000000..064c03b43
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLManager.java
@@ -0,0 +1,141 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * Manages XHTML formatted texts within messages. A XHTMLManager provides a high level access to 
+ * get and set XHTML bodies to messages, enable and disable XHTML support and check if remote XMPP
+ * clients support XHTML.   
+ * 
+ * @author Gaston Dombiak
+ */
+public class XHTMLManager {
+
+    private final static String namespace = "http://jabber.org/protocol/xhtml-im";
+
+    // Enable the XHTML support on every established connection
+    // The ServiceDiscoveryManager class should have been already initialized
+    static {
+        XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
+            public void connectionEstablished(XMPPConnection connection) {
+                XHTMLManager.setServiceEnabled(connection, true);
+            }
+        });
+    }
+
+    /**
+     * Returns an Iterator for the XHTML bodies in the message. Returns null if 
+     * the message does not contain an XHTML extension.
+     *
+     * @param message an XHTML message
+     * @return an Iterator for the bodies in the message or null if none.
+     */
+    public static Iterator getBodies(Message message) {
+        XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace);
+        if (xhtmlExtension != null)
+            return xhtmlExtension.getBodies();
+        else
+            return null;
+    }
+
+    /**
+     * Adds an XHTML body to the message.
+     *
+     * @param message the message that will receive the XHTML body
+     * @param body the string to add as an XHTML body to the message
+     */
+    public static void addBody(Message message, String body) {
+        XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace);
+        if (xhtmlExtension == null) {
+            // Create an XHTMLExtension and add it to the message
+            xhtmlExtension = new XHTMLExtension();
+            message.addExtension(xhtmlExtension);
+        }
+        // Add the required bodies to the message
+        xhtmlExtension.addBody(body);
+    }
+
+    /**
+     * Returns true if the message contains an XHTML extension.
+     *
+     * @param message the message to check if contains an XHTML extentsion or not
+     * @return a boolean indicating whether the message is an XHTML message
+     */
+    public static boolean isXHTMLMessage(Message message) {
+        return message.getExtension("html", namespace) != null;
+    }
+
+    /**
+     * Enables or disables the XHTML support on a given connection.<p>
+     *  
+     * Before starting to send XHTML messages to a user, check that the user can handle XHTML
+     * messages. Enable the XHTML support to indicate that this client handles XHTML messages.  
+     *
+     * @param connection the connection where the service will be enabled or disabled
+     * @param enabled indicates if the service will be enabled or disabled 
+     */
+    public synchronized static void setServiceEnabled(XMPPConnection connection, boolean enabled) {
+        if (isServiceEnabled(connection) == enabled)
+            return;
+
+        if (enabled) {
+            ServiceDiscoveryManager.getInstanceFor(connection).addFeature(namespace);
+        }
+        else {
+            ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(namespace);
+        }
+    }
+
+    /**
+     * Returns true if the XHTML support is enabled for the given connection.
+     *
+     * @param connection the connection to look for XHTML support
+     * @return a boolean indicating if the XHTML support is enabled for the given connection
+     */
+    public static boolean isServiceEnabled(XMPPConnection connection) {
+        return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(namespace);
+    }
+
+    /**
+     * Returns true if the specified user handles XHTML messages.
+     *
+     * @param connection the connection to use to perform the service discovery
+     * @param userID the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com
+     * @return a boolean indicating whether the specified user handles XHTML messages
+     */
+    public static boolean isServiceEnabled(XMPPConnection connection, String userID) {
+        try {
+            DiscoverInfo result =
+                ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(userID);
+            return result.containsFeature(namespace);
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java
new file mode 100644
index 000000000..ee67e2880
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/XHTMLText.java
@@ -0,0 +1,429 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * An XHTMLText represents formatted text. This class also helps to build valid 
+ * XHTML tags.
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLText {
+
+    private StringBuffer text = new StringBuffer(30);
+
+    /**
+     * Creates a new XHTMLText with body tag params.
+     * 
+     * @param style the XHTML style of the body
+     * @param lang the language of the body
+     */
+    public XHTMLText(String style, String lang) {
+        appendOpenBodyTag(style, lang);
+    }
+
+    /**
+     * Appends a tag that indicates that an anchor section begins.
+     * 
+     * @param href indicates the URL being linked to
+     * @param style the XHTML style of the anchor
+     */
+    public void appendOpenAnchorTag(String href, String style) {
+        StringBuffer sb = new StringBuffer("<a");
+        if (href != null) {
+            sb.append(" href=\"");
+            sb.append(href);
+            sb.append("\"");
+        }
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that an anchor section ends.
+     * 
+     */
+    public void appendCloseAnchorTag() {
+        text.append("</a>");
+    }
+
+    /**
+     * Appends a tag that indicates that a blockquote section begins.
+     * 
+     * @param style the XHTML style of the blockquote
+     */
+    public void appendOpenBlockQuoteTag(String style) {
+        StringBuffer sb = new StringBuffer("<blockquote");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that a blockquote section ends.
+     * 
+     */
+    public void appendCloseBlockQuoteTag() {
+        text.append("</blockquote>");
+    }
+
+    /**
+     * Appends a tag that indicates that a body section begins.
+     * 
+     * @param style the XHTML style of the body
+     * @param lang the language of the body
+     */
+    private void appendOpenBodyTag(String style, String lang) {
+        StringBuffer sb = new StringBuffer("<body");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        if (lang != null) {
+            sb.append(" xml:lang=\"");
+            sb.append(lang);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that a body section ends.
+     * 
+     */
+    private String closeBodyTag() {
+        return "</body>";
+    }
+
+    /**
+     * Appends a tag that inserts a single carriage return.
+     * 
+     */
+    public void appendBrTag() {
+        text.append("<br>");
+    }
+
+    /**
+     * Appends a tag that indicates a reference to work, such as a book, report or web site.
+     * 
+     */
+    public void appendOpenCiteTag() {
+        text.append("<cite>");
+    }
+
+    /**
+     * Appends a tag that indicates text that is the code for a program.
+     * 
+     */
+    public void appendOpenCodeTag() {
+        text.append("<code>");
+    }
+
+    /**
+     * Appends a tag that indicates end of text that is the code for a program.
+     * 
+     */
+    public void appendCloseCodeTag() {
+        text.append("</code>");
+    }
+
+    /**
+     * Appends a tag that indicates emphasis.
+     * 
+     */
+    public void appendOpenEmTag() {
+        text.append("<em>");
+    }
+
+    /**
+     * Appends a tag that indicates end of emphasis.
+     * 
+     */
+    public void appendCloseEmTag() {
+        text.append("</em>");
+    }
+
+    /**
+     * Appends a tag that indicates a header, a title of a section of the message.
+     * 
+     * @param level the level of the Header. It should be a value between 1 and 3
+     * @param style the XHTML style of the blockquote
+     */
+    public void appendOpenHeaderTag(int level, String style) {
+        if (level > 3 || level < 1) {
+            return;
+        }
+        StringBuffer sb = new StringBuffer("<h");
+        sb.append(level);
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that a header section ends.
+     * 
+     * @param level the level of the Header. It should be a value between 1 and 3
+     */
+    public void appendCloseHeaderTag(int level) {
+        if (level > 3 || level < 1) {
+            return;
+        }
+        StringBuffer sb = new StringBuffer("</h");
+        sb.append(level);
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates an image.
+     * 
+     * @param align how text should flow around the picture
+     * @param alt the text to show if you don't show the picture
+     * @param height how tall is the picture
+     * @param src where to get the picture
+     * @param width how wide is the picture
+     */
+    public void appendImageTag(String align, String alt, String height, String src, String width) {
+        StringBuffer sb = new StringBuffer("<img");
+        if (align != null) {
+            sb.append(" align=\"");
+            sb.append(align);
+            sb.append("\"");
+        }
+        if (alt != null) {
+            sb.append(" alt=\"");
+            sb.append(alt);
+            sb.append("\"");
+        }
+        if (height != null) {
+            sb.append(" height=\"");
+            sb.append(height);
+            sb.append("\"");
+        }
+        if (src != null) {
+            sb.append(" src=\"");
+            sb.append(src);
+            sb.append("\"");
+        }
+        if (width != null) {
+            sb.append(" width=\"");
+            sb.append(width);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates the start of a new line item within a list.
+     * 
+     * @param style the style of the line item
+     */
+    public void appendLineItemTag(String style) {
+        StringBuffer sb = new StringBuffer("<li");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that creates an ordered list. "Ordered" means that the order of the items 
+     * in the list is important. To show this, browsers automatically number the list. 
+     * 
+     * @param style the style of the ordered list
+     */
+    public void appendOpenOrderedListTag(String style) {
+        StringBuffer sb = new StringBuffer("<ol");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that an ordered list section ends.
+     * 
+     */
+    public void appendCloseOrderedListTag() {
+        text.append("</ol>");
+    }
+
+    /**
+     * Appends a tag that creates an unordered list. The unordered part means that the items 
+     * in the list are not in any particular order.
+     * 
+     * @param style the style of the unordered list
+     */
+    public void appendOpenUnorderedListTag(String style) {
+        StringBuffer sb = new StringBuffer("<ul");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that an unordered list section ends.
+     * 
+     */
+    public void appendCloseUnorderedListTag() {
+        text.append("</ul>");
+    }
+
+    /**
+     * Appends a tag that indicates the start of a new paragraph. This is usually rendered 
+     * with two carriage returns, producing a single blank line in between the two paragraphs.
+     * 
+     * @param style the style of the paragraph
+     */
+    public void appendOpenParagraphTag(String style) {
+        StringBuffer sb = new StringBuffer("<p");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates the end of a new paragraph. This is usually rendered 
+     * with two carriage returns, producing a single blank line in between the two paragraphs.
+     * 
+     */
+    public void appendCloseParagraphTag() {
+        text.append("</p>");
+    }
+
+    /**
+     * Appends a tag that indicates that an inlined quote section begins.
+     * 
+     * @param style the style of the inlined quote
+     */
+    public void appendOpenInlinedQuoteTag(String style) {
+        StringBuffer sb = new StringBuffer("<q");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that an inlined quote section ends.
+     * 
+     */
+    public void appendCloseInlinedQuoteTag() {
+        text.append("</q>");
+    }
+
+    /**
+     * Appends a tag that allows to set the fonts for a span of text.
+     * 
+     * @param style the style for a span of text
+     */
+    public void appendOpenSpanTag(String style) {
+        StringBuffer sb = new StringBuffer("<span");
+        if (style != null) {
+            sb.append(" style=\"");
+            sb.append(style);
+            sb.append("\"");
+        }
+        sb.append(">");
+        text.append(sb.toString());
+    }
+
+    /**
+     * Appends a tag that indicates that a span section ends.
+     * 
+     */
+    public void appendCloseSpanTag() {
+        text.append("</span>");
+    }
+
+    /**
+     * Appends a tag that indicates text which should be more forceful than surrounding text.
+     * 
+     */
+    public void appendOpenStrongTag() {
+        text.append("<strong>");
+    }
+
+    /**
+     * Appends a tag that indicates that a strong section ends.
+     * 
+     */
+    public void appendCloseStrongTag() {
+        text.append("</strong>");
+    }
+
+    /**
+     * Appends a given text to the XHTMLText.
+     * 
+     * @param textToAppend the text to append   
+     */
+    public void append(String textToAppend) {
+        text.append(StringUtils.escapeForXML(textToAppend));
+    }
+
+    /**
+     * Returns the text of the XHTMLText.
+     * 
+     * Note: Automatically adds the closing body tag.
+     * 
+     * @return the text of the XHTMLText   
+     */
+    public String toString() {
+        return text.toString().concat(closeBodyTag());
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java
new file mode 100644
index 000000000..61f35ae22
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebugger.java
@@ -0,0 +1,858 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.debugger;
+
+import java.awt.*;
+import java.awt.datatransfer.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+import java.text.*;
+import java.util.Date;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.debugger.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.util.*;
+
+/**
+ * The EnhancedDebugger is a debugger that allows to debug sent, received and interpreted messages 
+ * but also provides the ability to send ad-hoc messages composed by the user.<p>
+ * 
+ * A new EnhancedDebugger will be created for each connection to debug. All the EnhancedDebuggers 
+ * will be shown in the same debug window provided by the class EnhancedDebuggerWindow.
+ * 
+ * @author Gaston Dombiak
+ */
+public class EnhancedDebugger implements SmackDebugger {
+
+    private static final String NEWLINE = "\n";
+
+    private static ImageIcon packetReceivedIcon;
+    private static ImageIcon packetSentIcon;
+    private static ImageIcon presencePacketIcon;
+    private static ImageIcon iqPacketIcon;
+    private static ImageIcon messagePacketIcon;
+    private static ImageIcon unknownPacketTypeIcon;
+
+    {
+        URL url;
+        // Load the image icons 
+        url =
+            Thread.currentThread().getContextClassLoader().getResource("images/nav_left_blue.png");
+        if (url != null) {
+            packetReceivedIcon = new ImageIcon(url);
+        }
+        url =
+            Thread.currentThread().getContextClassLoader().getResource("images/nav_right_red.png");
+        if (url != null) {
+            packetSentIcon = new ImageIcon(url);
+        }
+        url =
+            Thread.currentThread().getContextClassLoader().getResource("images/photo_portrait.png");
+        if (url != null) {
+            presencePacketIcon = new ImageIcon(url);
+        }
+        url =
+            Thread.currentThread().getContextClassLoader().getResource(
+                "images/question_and_answer.png");
+        if (url != null) {
+            iqPacketIcon = new ImageIcon(url);
+        }
+        url = Thread.currentThread().getContextClassLoader().getResource("images/message.png");
+        if (url != null) {
+            messagePacketIcon = new ImageIcon(url);
+        }
+        url = Thread.currentThread().getContextClassLoader().getResource("images/unknown.png");
+        if (url != null) {
+            unknownPacketTypeIcon = new ImageIcon(url);
+        }
+    }
+
+    private DefaultTableModel messagesTable = null;
+    private JTextArea messageTextArea = null;
+    private JFormattedTextField userField = null;
+    private JFormattedTextField statusField = null;
+
+    private XMPPConnection connection = null;
+
+    private PacketListener packetReaderListener = null;
+    private PacketListener packetWriterListener = null;
+    private ConnectionListener connListener = null;
+
+    private Writer writer;
+    private Reader reader;
+    private ReaderListener readerListener;
+    private WriterListener writerListener;
+
+    private Date creationTime = new Date();
+
+    // Statistics variables
+    private DefaultTableModel statisticsTable = null;
+    private int sentPackets = 0;
+    private int receivedPackets = 0;
+    private int sentIQPackets = 0;
+    private int receivedIQPackets = 0;
+    private int sentMessagePackets = 0;
+    private int receivedMessagePackets = 0;
+    private int sentPresencePackets = 0;
+    private int receivedPresencePackets = 0;
+    private int sentOtherPackets = 0;
+    private int receivedOtherPackets = 0;
+
+    JTabbedPane tabbedPane;
+
+    public EnhancedDebugger(XMPPConnection connection, Writer writer, Reader reader) {
+        this.connection = connection;
+        this.writer = writer;
+        this.reader = reader;
+        createDebug();
+        EnhancedDebuggerWindow.addDebugger(this);
+    }
+
+    /**
+     * Creates the debug process, which is a GUI window that displays XML traffic.
+     */
+    private void createDebug() {
+        // We'll arrange the UI into six tabs. The first tab contains all data, the second
+        // client generated XML, the third server generated XML, the fourth allows to send 
+        // ad-hoc messages and the fifth contains connection information.
+        tabbedPane = new JTabbedPane();
+
+        // Add the All Packets, Sent, Received and Interpreted panels
+        addBasicPanels();
+
+        // Add the panel to send ad-hoc messages
+        addAdhocPacketPanel();
+
+        // Add the connection information panel
+        addInformationPanel();
+
+        // Create a thread that will listen for all incoming packets and write them to
+        // the GUI. This is what we call "interpreted" packet data, since it's the packet
+        // data as Smack sees it and not as it's coming in as raw XML.
+        packetReaderListener = new PacketListener() {
+            SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
+            public void processPacket(Packet packet) {
+                addReadPacketToTable(dateFormatter, packet);
+            }
+        };
+
+        // Create a thread that will listen for all outgoing packets and write them to
+        // the GUI.
+        packetWriterListener = new PacketListener() {
+            SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
+            public void processPacket(Packet packet) {
+                addSentPacketToTable(dateFormatter, packet);
+            }
+        };
+
+        // Create a thread that will listen for any connection closed event
+        connListener = new ConnectionListener() {
+            public void connectionClosed() {
+                statusField.setValue("Closed");
+                EnhancedDebuggerWindow.connectionClosed(EnhancedDebugger.this);
+            }
+
+            public void connectionClosedOnError(Exception e) {
+                statusField.setValue("Closed due to an exception");
+                EnhancedDebuggerWindow.connectionClosedOnError(EnhancedDebugger.this, e);
+            }
+        };
+    }
+
+    private void addBasicPanels() {
+        JPanel allPane = new JPanel();
+        allPane.setLayout(new GridLayout(2, 1));
+        tabbedPane.add("All Packets", allPane);
+        tabbedPane.setToolTipTextAt(0, "Sent and received packets processed by Smack");
+
+        messagesTable =
+            new DefaultTableModel(
+                new Object[] { "Hide", "Timestamp", "", "", "Message", "Id", "Type", "To", "From" },
+                0) {
+            public boolean isCellEditable(int rowIndex, int mColIndex) {
+                return false;
+            }
+            public Class getColumnClass(int columnIndex) {
+                if (columnIndex == 2 || columnIndex == 3) {
+                    return Icon.class;
+                }
+                return super.getColumnClass(columnIndex);
+            }
+
+        };
+        JTable table = new JTable(messagesTable);
+        // Allow only single a selection
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        // Hide the first column 
+        table.getColumnModel().getColumn(0).setMaxWidth(0);
+        table.getColumnModel().getColumn(0).setMinWidth(0);
+        table.getTableHeader().getColumnModel().getColumn(0).setMaxWidth(0);
+        table.getTableHeader().getColumnModel().getColumn(0).setMinWidth(0);
+        // Set the column "timestamp" size
+        table.getColumnModel().getColumn(1).setMaxWidth(300);
+        table.getColumnModel().getColumn(1).setPreferredWidth(70);
+        // Set the column "direction" icon size
+        table.getColumnModel().getColumn(2).setMaxWidth(50);
+        table.getColumnModel().getColumn(2).setPreferredWidth(30);
+        // Set the column "packet type" icon size
+        table.getColumnModel().getColumn(3).setMaxWidth(50);
+        table.getColumnModel().getColumn(3).setPreferredWidth(30);
+        // Set the column "Id" size
+        table.getColumnModel().getColumn(5).setMaxWidth(100);
+        table.getColumnModel().getColumn(5).setPreferredWidth(55);
+        // Set the column "type" size
+        table.getColumnModel().getColumn(6).setMaxWidth(200);
+        table.getColumnModel().getColumn(6).setPreferredWidth(50);
+        // Set the column "to" size
+        table.getColumnModel().getColumn(7).setMaxWidth(300);
+        table.getColumnModel().getColumn(7).setPreferredWidth(90);
+        // Set the column "from" size
+        table.getColumnModel().getColumn(8).setMaxWidth(300);
+        table.getColumnModel().getColumn(8).setPreferredWidth(90);
+        // Create a table listener that listen for row selection events
+        SelectionListener selectionListener = new SelectionListener(table);
+        table.getSelectionModel().addListSelectionListener(selectionListener);
+        table.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener);
+        allPane.add(new JScrollPane(table));
+        messageTextArea = new JTextArea();
+        messageTextArea.setEditable(false);
+        // Add pop-up menu.
+        JPopupMenu menu = new JPopupMenu();
+        JMenuItem menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(messageTextArea.getText()), null);
+            }
+        });
+        menu.add(menuItem1);
+        // Add listener to the text area so the popup menu can come up.
+        messageTextArea.addMouseListener(new PopupListener(menu));
+        allPane.add(new JScrollPane(messageTextArea));
+
+        // Create UI elements for client generated XML traffic.
+        final JTextArea sentText = new JTextArea();
+        sentText.setEditable(false);
+        sentText.setForeground(new Color(112, 3, 3));
+        tabbedPane.add("Raw Sent Packets", new JScrollPane(sentText));
+        tabbedPane.setToolTipTextAt(1, "Raw text of the sent packets");
+
+        // Add pop-up menu.
+        menu = new JPopupMenu();
+        menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(sentText.getText()), null);
+            }
+        });
+
+        JMenuItem menuItem2 = new JMenuItem("Clear");
+        menuItem2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                sentText.setText("");
+            }
+        });
+
+        // Add listener to the text area so the popup menu can come up.
+        sentText.addMouseListener(new PopupListener(menu));
+        menu.add(menuItem1);
+        menu.add(menuItem2);
+
+        // Create UI elements for server generated XML traffic.
+        final JTextArea receivedText = new JTextArea();
+        receivedText.setEditable(false);
+        receivedText.setForeground(new Color(6, 76, 133));
+        tabbedPane.add("Raw Received Packets", new JScrollPane(receivedText));
+        tabbedPane.setToolTipTextAt(
+            2,
+            "Raw text of the received packets before Smack process them");
+
+        // Add pop-up menu.
+        menu = new JPopupMenu();
+        menuItem1 = new JMenuItem("Copy");
+        menuItem1.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Get the clipboard
+                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+                // Set the sent text as the new content of the clipboard
+                clipboard.setContents(new StringSelection(receivedText.getText()), null);
+            }
+        });
+
+        menuItem2 = new JMenuItem("Clear");
+        menuItem2.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                receivedText.setText("");
+            }
+        });
+
+        // Add listener to the text area so the popup menu can come up.
+        receivedText.addMouseListener(new PopupListener(menu));
+        menu.add(menuItem1);
+        menu.add(menuItem2);
+
+        // Create a special Reader that wraps the main Reader and logs data to the GUI.
+        ObservableReader debugReader = new ObservableReader(reader);
+        readerListener = new ReaderListener() {
+                    public void read(String str) {
+                        int index = str.lastIndexOf(">");
+                        if (index != -1) {
+                            receivedText.append(str.substring(0, index + 1));
+                            receivedText.append(NEWLINE);
+                            if (str.length() > index) {
+                                receivedText.append(str.substring(index + 1));
+                            }
+                        }
+                        else {
+                            receivedText.append(str);
+                        }
+                    }
+                };
+        debugReader.addReaderListener(readerListener);
+
+        // Create a special Writer that wraps the main Writer and logs data to the GUI.
+        ObservableWriter debugWriter = new ObservableWriter(writer);
+        writerListener = new WriterListener() {
+                    public void write(String str) {
+                        sentText.append(str);
+                        if (str.endsWith(">")) {
+                            sentText.append(NEWLINE);
+                        }
+                    }
+                };
+        debugWriter.addWriterListener(writerListener);
+
+        // Assign the reader/writer objects to use the debug versions. The packet reader
+        // and writer will use the debug versions when they are created.
+        reader = debugReader;
+        writer = debugWriter;
+
+    }
+
+    private void addAdhocPacketPanel() {
+        // Create UI elements for sending ad-hoc messages.
+        final JTextArea adhocMessages = new JTextArea();
+        adhocMessages.setEditable(true);
+        adhocMessages.setForeground(new Color(1, 94, 35));
+        tabbedPane.add("Ad-hoc message", new JScrollPane(adhocMessages));
+        tabbedPane.setToolTipTextAt(3, "Panel that allows you to send adhoc packets");
+
+        // Add pop-up menu.
+        JPopupMenu menu = new JPopupMenu();
+        JMenuItem menuItem = new JMenuItem("Message");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                adhocMessages.setText(
+                    "<message to=\"\" id=\""
+                        + StringUtils.randomString(5)
+                        + "-X\"><body></body></message>");
+            }
+        });
+        menu.add(menuItem);
+
+        menuItem = new JMenuItem("IQ Get");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                adhocMessages.setText(
+                    "<iq type=\"get\" to=\"\" id=\""
+                        + StringUtils.randomString(5)
+                        + "-X\"><query xmlns=\"\"></query></iq>");
+            }
+        });
+        menu.add(menuItem);
+
+        menuItem = new JMenuItem("IQ Set");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                adhocMessages.setText(
+                    "<iq type=\"set\" to=\"\" id=\""
+                        + StringUtils.randomString(5)
+                        + "-X\"><query xmlns=\"\"></query></iq>");
+            }
+        });
+        menu.add(menuItem);
+
+        menuItem = new JMenuItem("Presence");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                adhocMessages.setText(
+                    "<presence to=\"\" id=\"" + StringUtils.randomString(5) + "-X\"/>");
+            }
+        });
+        menu.add(menuItem);
+        menu.addSeparator();
+
+        menuItem = new JMenuItem("Send");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (!"".equals(adhocMessages.getText())) {
+                    AdHocPacket packetToSend = new AdHocPacket(adhocMessages.getText());
+                    connection.sendPacket(packetToSend);
+                }
+            }
+        });
+        menu.add(menuItem);
+
+        menuItem = new JMenuItem("Clear");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                adhocMessages.setText(null);
+            }
+        });
+        menu.add(menuItem);
+
+        // Add listener to the text area so the popup menu can come up.
+        adhocMessages.addMouseListener(new PopupListener(menu));
+    }
+
+    private void addInformationPanel() {
+        // Create UI elements for connection information.
+        JPanel informationPanel = new JPanel();
+        informationPanel.setLayout(new BorderLayout());
+
+        // Add the Host information
+        JPanel connPanel = new JPanel();
+        connPanel.setLayout(new GridBagLayout());
+        connPanel.setBorder(BorderFactory.createTitledBorder("Connection information"));
+
+        JLabel label = new JLabel("Host: ");
+        label.setMinimumSize(new java.awt.Dimension(150, 14));
+        label.setMaximumSize(new java.awt.Dimension(150, 14));
+        connPanel.add(
+            label,
+            new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
+        JFormattedTextField field = new JFormattedTextField(connection.getHost());
+        field.setMinimumSize(new java.awt.Dimension(150, 20));
+        field.setMaximumSize(new java.awt.Dimension(150, 20));
+        field.setEditable(false);
+        field.setBorder(null);
+        connPanel.add(
+            field,
+            new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
+
+        // Add the Port information
+        label = new JLabel("Port: ");
+        label.setMinimumSize(new java.awt.Dimension(150, 14));
+        label.setMaximumSize(new java.awt.Dimension(150, 14));
+        connPanel.add(
+            label,
+            new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
+        field = new JFormattedTextField(new Integer(connection.getPort()));
+        field.setMinimumSize(new java.awt.Dimension(150, 20));
+        field.setMaximumSize(new java.awt.Dimension(150, 20));
+        field.setEditable(false);
+        field.setBorder(null);
+        connPanel.add(
+            field,
+            new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
+
+        // Add the connection's User information
+        label = new JLabel("User: ");
+        label.setMinimumSize(new java.awt.Dimension(150, 14));
+        label.setMaximumSize(new java.awt.Dimension(150, 14));
+        connPanel.add(
+            label,
+            new GridBagConstraints(0, 2, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
+        userField = new JFormattedTextField();
+        userField.setMinimumSize(new java.awt.Dimension(150, 20));
+        userField.setMaximumSize(new java.awt.Dimension(150, 20));
+        userField.setEditable(false);
+        userField.setBorder(null);
+        connPanel.add(
+            userField,
+            new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
+
+        // Add the connection's creationTime information
+        label = new JLabel("Creation time: ");
+        label.setMinimumSize(new java.awt.Dimension(150, 14));
+        label.setMaximumSize(new java.awt.Dimension(150, 14));
+        connPanel.add(
+            label,
+            new GridBagConstraints(0, 3, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
+        field = new JFormattedTextField(new SimpleDateFormat("yyyy.MM.dd hh:mm:ss aaa"));
+        field.setMinimumSize(new java.awt.Dimension(150, 20));
+        field.setMaximumSize(new java.awt.Dimension(150, 20));
+        field.setValue(creationTime);
+        field.setEditable(false);
+        field.setBorder(null);
+        connPanel.add(
+            field,
+            new GridBagConstraints(1, 3, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
+
+        // Add the connection's creationTime information
+        label = new JLabel("Status: ");
+        label.setMinimumSize(new java.awt.Dimension(150, 14));
+        label.setMaximumSize(new java.awt.Dimension(150, 14));
+        connPanel.add(
+            label,
+            new GridBagConstraints(0, 4, 1, 1, 0.0, 0.0, 21, 0, new Insets(0, 0, 0, 0), 0, 0));
+        statusField = new JFormattedTextField();
+        statusField.setMinimumSize(new java.awt.Dimension(150, 20));
+        statusField.setMaximumSize(new java.awt.Dimension(150, 20));
+        statusField.setValue("Active");
+        statusField.setEditable(false);
+        statusField.setBorder(null);
+        connPanel.add(
+            statusField,
+            new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, 10, 2, new Insets(0, 0, 0, 0), 0, 0));
+        // Add the connection panel to the information panel
+        informationPanel.add(connPanel, BorderLayout.NORTH);
+
+        // Add the Number of sent packets information
+        JPanel packetsPanel = new JPanel();
+        packetsPanel.setLayout(new GridLayout(1, 1));
+        packetsPanel.setBorder(BorderFactory.createTitledBorder("Transmitted Packets"));
+
+        statisticsTable =
+            new DefaultTableModel(new Object[][] { { "IQ", new Integer(0), new Integer(0)}, {
+                "Message", new Integer(0), new Integer(0)
+                }, {
+                "Presence", new Integer(0), new Integer(0)
+                }, {
+                "Other", new Integer(0), new Integer(0)
+                }, {
+                "Total", new Integer(0), new Integer(0)
+                }
+        }, new Object[] { "Type", "Received", "Sent" }) {
+            public boolean isCellEditable(int rowIndex, int mColIndex) {
+                return false;
+            }
+        };
+        JTable table = new JTable(statisticsTable);
+        // Allow only single a selection
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        packetsPanel.add(new JScrollPane(table));
+
+        // Add the packets panel to the information panel
+        informationPanel.add(packetsPanel, BorderLayout.CENTER);
+
+        tabbedPane.add("Information", new JScrollPane(informationPanel));
+        tabbedPane.setToolTipTextAt(4, "Information and statistics about the debugged connection");
+    }
+
+    public void userHasLogged(String user) {
+        userField.setText(user);
+        EnhancedDebuggerWindow.userHasLogged(this, user);
+        // Add the connection listener to the connection so that the debugger can be notified
+        // whenever the connection is closed.
+        connection.addConnectionListener(connListener);
+    }
+
+    public Reader getReader() {
+        return reader;
+    }
+
+    public Writer getWriter() {
+        return writer;
+    }
+
+    public PacketListener getReaderListener() {
+        return packetReaderListener;
+    }
+
+    public PacketListener getWriterListener() {
+        return packetWriterListener;
+    }
+
+    /**
+     * Updates the statistics table
+     */
+    private void updateStatistics() {
+        statisticsTable.setValueAt(new Integer(receivedIQPackets), 0, 1);
+        statisticsTable.setValueAt(new Integer(sentIQPackets), 0, 2);
+
+        statisticsTable.setValueAt(new Integer(receivedMessagePackets), 1, 1);
+        statisticsTable.setValueAt(new Integer(sentMessagePackets), 1, 2);
+
+        statisticsTable.setValueAt(new Integer(receivedPresencePackets), 2, 1);
+        statisticsTable.setValueAt(new Integer(sentPresencePackets), 2, 2);
+
+        statisticsTable.setValueAt(new Integer(receivedOtherPackets), 3, 1);
+        statisticsTable.setValueAt(new Integer(sentOtherPackets), 3, 2);
+
+        statisticsTable.setValueAt(new Integer(receivedPackets), 4, 1);
+        statisticsTable.setValueAt(new Integer(sentPackets), 4, 2);
+    }
+
+    /**
+     * Adds the received packet detail to the messages table.
+     * 
+     * @param dateFormatter the SimpleDateFormat to use to format Dates
+     * @param packet the read packet to add to the table
+     */
+    private void addReadPacketToTable(SimpleDateFormat dateFormatter, Packet packet) {
+        String messageType = null;
+        String from = packet.getFrom();
+        String type = "";
+        Icon packetTypeIcon;
+        receivedPackets++;
+        if (packet instanceof IQ) {
+            packetTypeIcon = iqPacketIcon;
+            messageType = "IQ Received (class=" + packet.getClass().getName() + ")";
+            type = ((IQ) packet).getType().toString();
+            receivedIQPackets++;
+        }
+        else if (packet instanceof Message) {
+            packetTypeIcon = messagePacketIcon;
+            messageType = "Message Received";
+            type = ((Message) packet).getType().toString();
+            receivedMessagePackets++;
+        }
+        else if (packet instanceof Presence) {
+            packetTypeIcon = presencePacketIcon;
+            messageType = "Presence Received";
+            type = ((Presence) packet).getType().toString();
+            receivedPresencePackets++;
+        }
+        else {
+            packetTypeIcon = unknownPacketTypeIcon;
+            messageType = packet.getClass().getName() + " Received";
+            receivedOtherPackets++;
+        }
+
+        messagesTable.addRow(
+            new Object[] {
+                formatXML(packet.toXML()),
+                dateFormatter.format(new Date()),
+                packetReceivedIcon,
+                packetTypeIcon,
+                messageType,
+                packet.getPacketID(),
+                type,
+                "",
+                from });
+        // Update the statistics table
+        updateStatistics();
+    }
+
+    /**
+     * Adds the sent packet detail to the messages table.
+     * 
+     * @param dateFormatter the SimpleDateFormat to use to format Dates
+     * @param packet the sent packet to add to the table
+     */
+    private void addSentPacketToTable(SimpleDateFormat dateFormatter, Packet packet) {
+        String messageType = null;
+        String to = packet.getTo();
+        String type = "";
+        Icon packetTypeIcon;
+        sentPackets++;
+        if (packet instanceof IQ) {
+            packetTypeIcon = iqPacketIcon;
+            messageType = "IQ Sent (class=" + packet.getClass().getName() + ")";
+            type = ((IQ) packet).getType().toString();
+            sentIQPackets++;
+        }
+        else if (packet instanceof Message) {
+            packetTypeIcon = messagePacketIcon;
+            messageType = "Message Sent";
+            type = ((Message) packet).getType().toString();
+            sentMessagePackets++;
+        }
+        else if (packet instanceof Presence) {
+            packetTypeIcon = presencePacketIcon;
+            messageType = "Presence Sent";
+            type = ((Presence) packet).getType().toString();
+            sentPresencePackets++;
+        }
+        else {
+            packetTypeIcon = unknownPacketTypeIcon;
+            messageType = packet.getClass().getName() + " Sent";
+            sentOtherPackets++;
+        }
+
+        messagesTable.addRow(
+            new Object[] {
+                formatXML(packet.toXML()),
+                dateFormatter.format(new Date()),
+                packetSentIcon,
+                packetTypeIcon,
+                messageType,
+                packet.getPacketID(),
+                type,
+                to,
+                "" });
+
+        // Update the statistics table
+        updateStatistics();
+    }
+
+    private String formatXML(String str) {
+        try {
+            // Use a Transformer for output
+            TransformerFactory tFactory = TransformerFactory.newInstance();
+            // Surround this setting in a try/catch for compatibility with Java 1.4. This setting is required
+            // for Java 1.5
+            try {
+                tFactory.setAttribute("indent-number", new Integer(2));
+            }
+            catch (IllegalArgumentException e) {}
+            Transformer transformer = tFactory.newTransformer();
+            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+
+            // Transform the requested string into a nice formatted XML string
+            StreamSource source = new StreamSource(new StringReader(str));
+            StringWriter sw = new StringWriter();
+            StreamResult result = new StreamResult(sw);
+            transformer.transform(source, result);
+            return sw.toString();
+
+        }
+        catch (TransformerConfigurationException tce) {
+            // Error generated by the parser
+            System.out.println("\n** Transformer Factory error");
+            System.out.println("   " + tce.getMessage());
+
+            // Use the contained exception, if any
+            Throwable x = tce;
+            if (tce.getException() != null)
+                x = tce.getException();
+            x.printStackTrace();
+
+        }
+        catch (TransformerException te) {
+            // Error generated by the parser
+            System.out.println("\n** Transformation error");
+            System.out.println("   " + te.getMessage());
+
+            // Use the contained exception, if any
+            Throwable x = te;
+            if (te.getException() != null)
+                x = te.getException();
+            x.printStackTrace();
+
+        }
+        return str;
+    }
+    
+    /**
+     * Returns true if the debugger's connection with the server is up and running.
+     *
+     * @return true if the connection with the server is active.
+     */
+    boolean isConnectionActive() {
+        return connection.isConnected();
+    }
+
+    /**
+     * Stops debugging the connection. Removes any listener on the connection.
+     *
+     */
+    void cancel() {
+        connection.removeConnectionListener(connListener);
+        connection.removePacketListener(packetReaderListener);
+        connection.removePacketWriterListener(packetWriterListener);
+        ((ObservableReader)reader).removeReaderListener(readerListener);
+        ((ObservableWriter)writer).removeWriterListener(writerListener);
+        messagesTable = null;
+    }
+
+    /**
+     * An ad-hoc packet is like any regular packet but with the exception that it's intention is 
+     * to be used only <b>to send packets</b>.<p>
+     * 
+     * The whole text to send must be passed to the constructor. This implies that the client of 
+     * this class is responsible for sending a valid text to the constructor. 
+     * 
+     */
+    private class AdHocPacket extends Packet {
+
+        private String text;
+
+        /**
+         * Create a new AdHocPacket with the text to send. The passed text must be a valid text to 
+         * send to the server, no validation will be done on the passed text.
+         * 
+         * @param text the whole text of the packet to send 
+         */
+        public AdHocPacket(String text) {
+            this.text = text;
+        }
+
+        public String toXML() {
+            return text;
+        }
+
+    }
+
+    /**
+     * Listens for debug window popup dialog events.
+     */
+    private class PopupListener extends MouseAdapter {
+        JPopupMenu popup;
+
+        PopupListener(JPopupMenu popupMenu) {
+            popup = popupMenu;
+        }
+
+        public void mousePressed(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        private void maybeShowPopup(MouseEvent e) {
+            if (e.isPopupTrigger()) {
+                popup.show(e.getComponent(), e.getX(), e.getY());
+            }
+        }
+    }
+
+    private class SelectionListener implements ListSelectionListener {
+        JTable table;
+
+        // It is necessary to keep the table since it is not possible
+        // to determine the table from the event's source
+        SelectionListener(JTable table) {
+            this.table = table;
+        }
+        public void valueChanged(ListSelectionEvent e) {
+            if (table.getSelectedRow() == -1) {
+                // Clear the messageTextArea since there is none packet selected 
+                messageTextArea.setText(null);
+            }
+            else {
+                // Set the detail of the packet in the messageTextArea 
+                messageTextArea.setText(
+                    (String) table.getModel().getValueAt(table.getSelectedRow(), 0));
+                // Scroll up to the top
+                messageTextArea.setCaretPosition(0);
+            }
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java
new file mode 100644
index 000000000..54127b529
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java
@@ -0,0 +1,348 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.debugger;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.net.*;
+import java.util.*;
+
+import javax.swing.*;
+
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.provider.ProviderManager;
+
+/**
+ * The EnhancedDebuggerWindow is the main debug window that will show all the EnhancedDebuggers. 
+ * For each connection to debug there will be an EnhancedDebugger that will be shown in the 
+ * EnhancedDebuggerWindow.<p>
+ * 
+ * This class also provides information about Smack like for example the Smack version and the
+ * installed providers. 
+ * 
+ * @author Gaston Dombiak
+ */
+class EnhancedDebuggerWindow {
+
+    private static EnhancedDebuggerWindow instance;
+
+    private static ImageIcon connectionCreatedIcon;
+    private static ImageIcon connectionActiveIcon;
+    private static ImageIcon connectionClosedIcon;
+    private static ImageIcon connectionClosedOnErrorIcon;
+    
+    {
+        URL url;
+        
+        url =
+            Thread.currentThread().getContextClassLoader().getResource(
+                "images/trafficlight_off.png");
+        if (url != null) {
+            connectionCreatedIcon = new ImageIcon(url);
+        }
+        url =
+            Thread.currentThread().getContextClassLoader().getResource(
+                "images/trafficlight_green.png");
+        if (url != null) {
+            connectionActiveIcon = new ImageIcon(url);
+        }
+        url =
+            Thread.currentThread().getContextClassLoader().getResource(
+                "images/trafficlight_red.png");
+        if (url != null) {
+            connectionClosedIcon = new ImageIcon(url);
+        }
+        url = Thread.currentThread().getContextClassLoader().getResource("images/warning.png");
+        if (url != null) {
+            connectionClosedOnErrorIcon = new ImageIcon(url);
+        }
+
+    }
+
+    private JFrame frame = null;
+    private JTabbedPane tabbedPane = null;
+    private java.util.List debuggers = new ArrayList();
+
+    private EnhancedDebuggerWindow() {
+    }
+
+    /**
+     * Returns the unique EnhancedDebuggerWindow instance available in the system. 
+     * 
+     * @return the unique EnhancedDebuggerWindow instance 
+     */
+    private static EnhancedDebuggerWindow getInstance() {
+        if (instance == null) {
+            instance = new EnhancedDebuggerWindow();
+        }
+        return instance;
+    }
+
+    /**
+     * Adds the new specified debugger to the list of debuggers to show in the main window.
+     * 
+     * @param debugger the new debugger to show in the debug window
+     */
+    synchronized static void addDebugger(EnhancedDebugger debugger) {
+        getInstance().showNewDebugger(debugger);
+    }
+
+    /**
+     * Shows the new debugger in the debug window. 
+     * 
+     * @param debugger the new debugger to show
+     */
+    private void showNewDebugger(EnhancedDebugger debugger) {
+        if (frame == null) {
+            createDebug();
+        }
+        debugger.tabbedPane.setName("Connection_" + tabbedPane.getComponentCount());
+        tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1);
+        tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon);
+        frame.setTitle(
+            "Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1));
+        // Keep the added debugger for later access
+        debuggers.add(debugger);
+    }
+
+    /**
+     * Notification that a user has logged in to the server. A new title will be set 
+     * to the tab of the given debugger. 
+     * 
+     * @param debugger the debugger whose connection logged in to the server
+     * @param user the user@host/resource that has just logged in
+     */
+    synchronized static void userHasLogged(EnhancedDebugger debugger, String user) {
+        int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane);
+        getInstance().tabbedPane.setTitleAt(
+            index,
+            user);
+        getInstance().tabbedPane.setIconAt(
+            index,
+            connectionActiveIcon);
+    }
+
+    /**
+     * Notification that the connection was properly closed.
+     *
+     * @param debugger the debugger whose connection was properly closed.
+     */
+    synchronized static void connectionClosed(EnhancedDebugger debugger) {
+        getInstance().tabbedPane.setIconAt(
+            getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane),
+            connectionClosedIcon);
+    }
+
+    /**
+     * Notification that the connection was closed due to an exception.
+     *
+     * @param debugger the debugger whose connection was closed due to an exception.
+     * @param e the exception.
+     */
+    synchronized static void connectionClosedOnError(EnhancedDebugger debugger, Exception e) {
+        int index = getInstance().tabbedPane.indexOfComponent(debugger.tabbedPane);
+        getInstance().tabbedPane.setToolTipTextAt(
+            index,
+            "Connection closed due to the exception: " + e.getMessage());
+        getInstance().tabbedPane.setIconAt(
+            index,
+            connectionClosedOnErrorIcon);
+    }
+
+    /**
+     * Creates the main debug window that provides information about Smack and also shows
+     * a tab panel for each connection that is being debugged. 
+     */
+    private void createDebug() {
+
+        frame = new JFrame("Smack Debug Window");
+
+        // Add listener for window closing event 
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent evt) {
+                rootWindowClosing(evt);
+            }
+        });
+
+        // We'll arrange the UI into tabs. The last tab contains Smack's information.
+        // All the connection debugger tabs will be shown before the Smack info tab. 
+        tabbedPane = new JTabbedPane();
+
+        // Create the Smack info panel 
+        JPanel informationPanel = new JPanel();
+        informationPanel.setLayout(new BoxLayout(informationPanel, BoxLayout.Y_AXIS));
+
+        // Add the Smack version label
+        JPanel versionPanel = new JPanel();
+        versionPanel.setLayout(new BoxLayout(versionPanel, BoxLayout.X_AXIS));
+        versionPanel.setMaximumSize(new Dimension(2000, 31));
+        versionPanel.add(new JLabel(" Smack version: "));
+        JFormattedTextField field = new JFormattedTextField(SmackConfiguration.getVersion());
+        field.setEditable(false);
+        field.setBorder(null);
+        versionPanel.add(field);
+        informationPanel.add(versionPanel);
+
+        // Add the list of installed IQ Providers
+        JPanel iqProvidersPanel = new JPanel();
+        iqProvidersPanel.setLayout(new GridLayout(1, 1));
+        iqProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed IQ Providers"));
+        Vector providers = new Vector();
+        for (Iterator it = ProviderManager.getIQProviders(); it.hasNext();) {
+            Object provider = it.next();
+            if (provider.getClass() == Class.class) {
+                providers.add(((Class) provider).getName());
+            }
+            else {
+                providers.add(provider.getClass().getName());
+            }
+        }
+        // Sort the collection of providers
+        Collections.sort(providers);
+        JList list = new JList(providers);
+        iqProvidersPanel.add(new JScrollPane(list));
+        informationPanel.add(iqProvidersPanel);
+
+        // Add the list of installed Extension Providers
+        JPanel extensionProvidersPanel = new JPanel();
+        extensionProvidersPanel.setLayout(new GridLayout(1, 1));
+        extensionProvidersPanel.setBorder(BorderFactory.createTitledBorder("Installed Extension Providers"));
+        providers = new Vector();
+        for (Iterator it = ProviderManager.getExtensionProviders(); it.hasNext();) {
+            Object provider = it.next();
+            if (provider.getClass() == Class.class) {
+                providers.add(((Class) provider).getName());
+            }
+            else {
+                providers.add(provider.getClass().getName());
+            }
+        }
+        // Sort the collection of providers
+        Collections.sort(providers);
+        list = new JList(providers);
+        extensionProvidersPanel.add(new JScrollPane(list));
+        informationPanel.add(extensionProvidersPanel);
+
+        tabbedPane.add("Smack Info", informationPanel);
+
+        // Add pop-up menu.
+        JPopupMenu menu = new JPopupMenu();
+        // Add a menu item that allows to close the current selected tab
+        JMenuItem menuItem = new JMenuItem("Close");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                // Remove the selected tab pane if it's not the Smack info pane
+                if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) {
+                    int index = tabbedPane.getSelectedIndex();
+                    // Notify to the debugger to stop debugging
+                    EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index);
+                    debugger.cancel();
+                    // Remove the debugger from the root window
+                    tabbedPane.remove(debugger.tabbedPane);
+                    debuggers.remove(debugger);
+                    // Update the root window title
+                    frame.setTitle(
+                        "Smack Debug Window -- Total connections: "
+                            + (tabbedPane.getComponentCount() - 1));
+                }
+            }
+        });
+        menu.add(menuItem);
+        // Add a menu item that allows to close all the tabs that have their connections closed
+        menuItem = new JMenuItem("Close All Not Active");
+        menuItem.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                ArrayList debuggersToRemove = new ArrayList();
+                // Remove all the debuggers of which their connections are no longer valid
+                for (int index=0; index < tabbedPane.getComponentCount()-1; index++) {
+                    EnhancedDebugger debugger = (EnhancedDebugger)debuggers.get(index);
+                    if (!debugger.isConnectionActive()) {
+                        // Notify to the debugger to stop debugging
+                        debugger.cancel();
+                        debuggersToRemove.add(debugger);
+                    }
+                }
+                for (Iterator it=debuggersToRemove.iterator(); it.hasNext();) {
+                    EnhancedDebugger debugger = (EnhancedDebugger)it.next();
+                    // Remove the debugger from the root window
+                    tabbedPane.remove(debugger.tabbedPane);
+                    debuggers.remove(debugger);
+                }
+                // Update the root window title
+                frame.setTitle(
+                    "Smack Debug Window -- Total connections: "
+                        + (tabbedPane.getComponentCount() - 1));
+            }
+        });
+        menu.add(menuItem);
+        // Add listener to the text area so the popup menu can come up.
+        tabbedPane.addMouseListener(new PopupListener(menu));
+
+        frame.getContentPane().add(tabbedPane);
+
+        frame.setSize(650, 400);
+        frame.setVisible(true);
+
+    }
+
+    /**
+     * Notification that the root window is closing. Stop listening for received and 
+     * transmitted packets in all the debugged connections.
+     * 
+     * @param evt the event that indicates that the root window is closing 
+     */
+    public void rootWindowClosing(WindowEvent evt) {
+        // Notify to all the debuggers to stop debugging
+        for (Iterator it = debuggers.iterator(); it.hasNext();) {
+            EnhancedDebugger debugger = (EnhancedDebugger)it.next();
+            debugger.cancel();
+        }
+        // Release any reference to the debuggers
+        debuggers.removeAll(debuggers);
+        // Release the default instance
+        instance = null;
+    }
+
+    /**
+     * Listens for debug window popup dialog events.
+     */
+    private class PopupListener extends MouseAdapter {
+        JPopupMenu popup;
+
+        PopupListener(JPopupMenu popupMenu) {
+            popup = popupMenu;
+        }
+
+        public void mousePressed(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        public void mouseReleased(MouseEvent e) {
+            maybeShowPopup(e);
+        }
+
+        private void maybeShowPopup(MouseEvent e) {
+            if (e.isPopupTrigger()) {
+                popup.show(e.getComponent(), e.getX(), e.getY());
+            }
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html
new file mode 100644
index 000000000..8ea20e0ac
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/debugger/package.html
@@ -0,0 +1 @@
+<body>Smack optional Debuggers.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java
new file mode 100644
index 000000000..9300dc6f7
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Affiliate.java
@@ -0,0 +1,98 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smackx.packet.MUCAdmin;
+import org.jivesoftware.smackx.packet.MUCOwner;
+
+/**
+ * Represents an affiliation of a user to a given room. The affiliate's information will always have
+ * the bare jid of the real user and its affiliation. If the affiliate is an occupant of the room
+ * then we will also have information about the role and nickname of the user in the room.
+ *
+ * @author Gaston Dombiak
+ */
+public class Affiliate {
+    // Fields that must have a value
+    private String jid;
+    private String affiliation;
+
+    // Fields that may have a value
+    private String role;
+    private String nick;
+
+    Affiliate(MUCOwner.Item item) {
+        super();
+        this.jid = item.getJid();
+        this.affiliation = item.getAffiliation();
+        this.role = item.getRole();
+        this.nick = item.getNick();
+    }
+
+    Affiliate(MUCAdmin.Item item) {
+        super();
+        this.jid = item.getJid();
+        this.affiliation = item.getAffiliation();
+        this.role = item.getRole();
+        this.nick = item.getNick();
+    }
+
+    /**
+     * Returns the bare JID of the affiliated user. This information will always be available.
+     *
+     * @return the bare JID of the affiliated user.
+     */
+    public String getJid() {
+        return jid;
+    }
+
+    /**
+     * Returns the affiliation of the afffiliated user. Possible affiliations are: "owner", "admin",
+     * "member", "outcast". This information will always be available.
+     *
+     * @return the affiliation of the afffiliated user.
+     */
+    public String getAffiliation() {
+        return affiliation;
+    }
+
+    /**
+     * Returns the current role of the affiliated user if the user is currently in the room.
+     * If the user is not present in the room then the answer will be null.
+     *
+     * @return the current role of the affiliated user in the room or null if the user is not in
+     *         the room.
+     */
+    public String getRole() {
+        return role;
+    }
+
+    /**
+     * Returns the current nickname of the affiliated user if the user is currently in the room.
+     * If the user is not present in the room then the answer will be null.
+     *
+     * @return the current nickname of the affiliated user in the room or null if the user is not in
+     *         the room.
+     */
+    public String getNick() {
+        return nick;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java
new file mode 100644
index 000000000..625655fbb
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java
@@ -0,0 +1,79 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * Default implementation of the ParticipantStatusListener interface.<p>
+ *
+ * This class does not provide any behavior by default. It just avoids having
+ * to implement all the inteface methods if the user is only interested in implementing
+ * some of the methods.
+ * 
+ * @author Gaston Dombiak
+ */
+public class DefaultParticipantStatusListener implements ParticipantStatusListener {
+
+    public void joined(String participant) {
+    }
+
+    public void left(String participant) {
+    }
+
+    public void kicked(String participant) {
+    }
+
+    public void voiceGranted(String participant) {
+    }
+
+    public void voiceRevoked(String participant) {
+    }
+
+    public void banned(String participant) {
+    }
+
+    public void membershipGranted(String participant) {
+    }
+
+    public void membershipRevoked(String participant) {
+    }
+
+    public void moderatorGranted(String participant) {
+    }
+
+    public void moderatorRevoked(String participant) {
+    }
+
+    public void ownershipGranted(String participant) {
+    }
+
+    public void ownershipRevoked(String participant) {
+    }
+
+    public void adminGranted(String participant) {
+    }
+
+    public void adminRevoked(String participant) {
+    }
+
+    public void nicknameChanged(String nickname) {
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java
new file mode 100644
index 000000000..1075d6ca0
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java
@@ -0,0 +1,70 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * Default implementation of the UserStatusListener interface.<p>
+ *
+ * This class does not provide any behavior by default. It just avoids having
+ * to implement all the inteface methods if the user is only interested in implementing
+ * some of the methods.
+ * 
+ * @author Gaston Dombiak
+ */
+public class DefaultUserStatusListener implements UserStatusListener {
+
+    public void kicked(String actor, String reason) {
+    }
+
+    public void voiceGranted() {
+    }
+
+    public void voiceRevoked() {
+    }
+
+    public void banned(String actor, String reason) {
+    }
+
+    public void membershipGranted() {
+    }
+
+    public void membershipRevoked() {
+    }
+
+    public void moderatorGranted() {
+    }
+
+    public void moderatorRevoked() {
+    }
+
+    public void ownershipGranted() {
+    }
+
+    public void ownershipRevoked() {
+    }
+
+    public void adminGranted() {
+    }
+
+    public void adminRevoked() {
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java
new file mode 100644
index 000000000..a5edc6fc8
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/DiscussionHistory.java
@@ -0,0 +1,173 @@
+/**
+ * $RCSfile$
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import java.util.Date;
+
+import org.jivesoftware.smackx.packet.MUCInitialPresence;
+
+/**
+ * The DiscussionHistory class controls the number of characters or messages to receive
+ * when entering a room. The room will decide the amount of history to return if you don't 
+ * specify a DiscussionHistory while joining a room.<p>
+ * 
+ * You can use some or all of these variable to control the amount of history to receive:   
+ * <ul>
+ *  <li>maxchars -> total number of characters to receive in the history.
+ *  <li>maxstanzas -> total number of messages to receive in the history.
+ *  <li>seconds -> only the messages received in the last "X" seconds will be included in the 
+ * history.
+ *  <li>since -> only the messages received since the datetime specified will be included in 
+ * the history.
+ * </ul>
+ * 
+ * Note: Setting maxchars to 0 indicates that the user requests to receive no history.
+ * 
+ * @author Gaston Dombiak
+ */
+public class DiscussionHistory {
+
+    private int maxChars = -1;
+    private int maxStanzas = -1;
+    private int seconds = -1;
+    private Date since;
+
+    /**
+     * Returns the total number of characters to receive in the history.
+     * 
+     * @return total number of characters to receive in the history.
+     */
+    public int getMaxChars() {
+        return maxChars;
+    }
+
+    /**
+     * Returns the total number of messages to receive in the history.
+     * 
+     * @return the total number of messages to receive in the history.
+     */
+    public int getMaxStanzas() {
+        return maxStanzas;
+    }
+
+    /**
+     * Returns the number of seconds to use to filter the messages received during that time. 
+     * In other words, only the messages received in the last "X" seconds will be included in 
+     * the history.
+     * 
+     * @return the number of seconds to use to filter the messages received during that time.
+     */
+    public int getSeconds() {
+        return seconds;
+    }
+
+    /**
+     * Returns the since date to use to filter the messages received during that time. 
+     * In other words, only the messages received since the datetime specified will be 
+     * included in the history.
+     * 
+     * @return the since date to use to filter the messages received during that time.
+     */
+    public Date getSince() {
+        return since;
+    }
+
+    /**
+     * Sets the total number of characters to receive in the history.
+     * 
+     * @param maxChars the total number of characters to receive in the history.
+     */
+    public void setMaxChars(int maxChars) {
+        this.maxChars = maxChars;
+    }
+
+    /**
+     * Sets the total number of messages to receive in the history.
+     * 
+     * @param maxStanzas the total number of messages to receive in the history.
+     */
+    public void setMaxStanzas(int maxStanzas) {
+        this.maxStanzas = maxStanzas;
+    }
+
+    /**
+     * Sets the number of seconds to use to filter the messages received during that time. 
+     * In other words, only the messages received in the last "X" seconds will be included in 
+     * the history.
+     * 
+     * @param seconds the number of seconds to use to filter the messages received during 
+     * that time.
+     */
+    public void setSeconds(int seconds) {
+        this.seconds = seconds;
+    }
+
+    /**
+     * Sets the since date to use to filter the messages received during that time. 
+     * In other words, only the messages received since the datetime specified will be 
+     * included in the history.
+     * 
+     * @param since the since date to use to filter the messages received during that time.
+     */
+    public void setSince(Date since) {
+        this.since = since;
+    }
+
+    /**
+     * Returns true if the history has been configured with some values.
+     * 
+     * @return true if the history has been configured with some values.
+     */
+    private boolean isConfigured() {
+        return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null;
+    }
+
+    /**
+     * Returns the History that manages the amount of discussion history provided on entering a 
+     * room.
+     * 
+     * @return the History that manages the amount of discussion history provided on entering a 
+     * room.
+     */
+    MUCInitialPresence.History getMUCHistory() {
+        // Return null if the history was not properly configured  
+        if (!isConfigured()) {
+            return null;
+        }
+        
+        MUCInitialPresence.History mucHistory = new MUCInitialPresence.History();
+        if (maxChars > -1) {
+            mucHistory.setMaxChars(maxChars);
+        }
+        if (maxStanzas > -1) {
+            mucHistory.setMaxStanzas(maxStanzas);
+        }
+        if (seconds > -1) {
+            mucHistory.setSeconds(seconds);
+        }
+        if (since != null) {
+            mucHistory.setSince(since);
+        }
+        return mucHistory;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java
new file mode 100644
index 000000000..51905a346
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/HostedRoom.java
@@ -0,0 +1,65 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smackx.packet.DiscoverItems;
+
+/**
+ * Hosted rooms by a chat service may be discovered if they are configured to appear in the room
+ * directory . The information that may be discovered is the XMPP address of the room and the room
+ * name. The address of the room may be used for obtaining more detailed information
+ * {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.XMPPConnection, String)}
+ * or could be used for joining the room
+ * {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.XMPPConnection, String)}
+ * and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}.
+ *
+ * @author Gaston Dombiak
+ */
+public class HostedRoom {
+
+    private String jid;
+
+    private String name;
+
+    public HostedRoom(DiscoverItems.Item item) {
+        super();
+        jid = item.getEntityID();
+        name = item.getName();
+    }
+
+    /**
+     * Returns the XMPP address of the hosted room by the chat service. This address may be used
+     * when creating a <code>MultiUserChat</code> when joining a room.
+     *
+     * @return the XMPP address of the hosted room by the chat service.
+     */
+    public String getJid() {
+        return jid;
+    }
+
+    /**
+     * Returns the name of the room.
+     *
+     * @return the name of the room.
+     */
+    public String getName() {
+        return name;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java
new file mode 100644
index 000000000..eeb814e7a
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationListener.java
@@ -0,0 +1,49 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.Message;
+
+/**
+ * A listener that is fired anytime an invitation to join a MUC room is received.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface InvitationListener {
+
+    /**
+     * Called when the an invitation to join a MUC room is received.<p>
+     * 
+     * If the room is password-protected, the invitee will receive a password to use to join
+     * the room. If the room is members-only, the the invitee may be added to the member list.
+     * 
+     * @param conn the XMPPConnection that received the invitation.
+     * @param room the room that invitation refers to.
+     * @param inviter the inviter that sent the invitation. (e.g. crone1@shakespeare.lit).
+     * @param reason the reason why the inviter sent the invitation.
+     * @param password the password to use when joining the room.
+     * @param message the message used by the inviter to send the invitation.
+     */
+    public abstract void invitationReceived(XMPPConnection conn, String room, String inviter, String reason,
+                                            String password, Message message);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java
new file mode 100644
index 000000000..81ae0ab55
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/InvitationRejectionListener.java
@@ -0,0 +1,38 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * A listener that is fired anytime an invitee declines or rejects an invitation.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface InvitationRejectionListener {
+
+    /**
+     * Called when the invitee declines the invitation.
+     * 
+     * @param invitee the invitee that declined the invitation. (e.g. hecate@shakespeare.lit).
+     * @param reason the reason why the invitee declined the invitation.
+     */
+    public abstract void invitationDeclined(String invitee, String reason);
+    
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java
new file mode 100644
index 000000000..fc53a5bb8
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/MultiUserChat.java
@@ -0,0 +1,2572 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.NodeInformationProvider;
+import org.jivesoftware.smackx.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.packet.*;
+
+/**
+ * A MultiUserChat is a conversation that takes place among many users in a virtual
+ * room. A room could have many occupants with different affiliation and roles.
+ * Possible affiliatons are "owner", "admin", "member", and "outcast". Possible roles 
+ * are "moderator", "participant", and "visitor". Each role and affiliation guarantees
+ * different privileges (e.g. Send messages to all occupants, Kick participants and visitors,
+ * Grant voice, Edit member list, etc.). 
+ *
+ * @author Gaston Dombiak
+ */
+public class MultiUserChat {
+
+    private final static String discoNamespace = "http://jabber.org/protocol/muc";
+    private final static String discoNode = "http://jabber.org/protocol/muc#rooms";
+
+    private static Map joinedRooms = new WeakHashMap();
+
+    private XMPPConnection connection;
+    private String room;
+    private String subject;
+    private String nickname = null;
+    private boolean joined = false;
+    private Map occupantsMap = new HashMap();
+
+    private List invitationRejectionListeners = new ArrayList();
+    private List subjectUpdatedListeners = new ArrayList();
+    private List userStatusListeners = new ArrayList();
+    private List participantStatusListeners = new ArrayList();
+
+    private PacketFilter presenceFilter;
+    private PacketListener presenceListener;
+    private PacketFilter subjectFilter;
+    private PacketListener subjectListener;
+    private PacketFilter messageFilter;
+    private PacketFilter declinesFilter;
+    private PacketListener declinesListener;
+    private PacketCollector messageCollector;
+    private List connectionListeners = new ArrayList();
+
+    static {
+        XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
+            public void connectionEstablished(final XMPPConnection connection) {
+                // Set on every established connection that this client supports the Multi-User 
+                // Chat protocol. This information will be used when another client tries to 
+                // discover whether this client supports MUC or not.
+                ServiceDiscoveryManager.getInstanceFor(connection).addFeature(discoNamespace);
+                // Set the NodeInformationProvider that will provide information about the
+                // joined rooms whenever a disco request is received 
+                ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(
+                    discoNode,
+                    new NodeInformationProvider() {
+                        public Iterator getNodeItems() {
+                            ArrayList answer = new ArrayList();
+                            Iterator rooms=MultiUserChat.getJoinedRooms(connection);
+                            while (rooms.hasNext()) {
+                                answer.add(new DiscoverItems.Item((String)rooms.next()));
+                            }
+                            return answer.iterator(); 
+                        }
+                    });
+            }
+        });
+    }
+
+    /**
+     * Creates a new multi user chat with the specified connection and room name. Note: no
+     * information is sent to or received from the server until you attempt to
+     * {@link #join(String) join} the chat room. On some server implementations,
+     * the room will not be created until the first person joins it.<p>
+     *
+     * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com
+     * for the XMPP server example.com). You must ensure that the room address you're
+     * trying to connect to includes the proper chat sub-domain.
+     *
+     * @param connection the XMPP connection.
+     * @param room the name of the room in the form "roomName@service", where
+     *      "service" is the hostname at which the multi-user chat
+     *      service is running. Make sure to provide a valid JID.
+     */
+    public MultiUserChat(XMPPConnection connection, String room) {
+        this.connection = connection;
+        this.room = room.toLowerCase();
+        init();
+    }
+
+    /**
+     * Returns true if the specified user supports the Multi-User Chat protocol.
+     *
+     * @param connection the connection to use to perform the service discovery.
+     * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com.
+     * @return a boolean indicating whether the specified user supports the MUC protocol.
+     */
+    public static boolean isServiceEnabled(XMPPConnection connection, String user) {
+        try {
+            DiscoverInfo result =
+                ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(user);
+            return result.containsFeature(discoNamespace);
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    /**
+     * Returns an Iterator on the rooms where the user has joined using a given connection.
+     * The Iterator will contain Strings where each String represents a room 
+     * (e.g. room@muc.jabber.org).
+     * 
+     * @param connection the connection used to join the rooms.
+     * @return an Iterator on the rooms where the user has joined using a given connection.
+     */
+    private static Iterator getJoinedRooms(XMPPConnection connection) {
+        ArrayList rooms = (ArrayList)joinedRooms.get(connection);
+        if (rooms != null) {
+            return rooms.iterator();
+        }
+        // Return an iterator on an empty collection (i.e. the user never joined a room) 
+        return new ArrayList().iterator();
+    }
+    
+    /**
+     * Returns an Iterator on the rooms where the requested user has joined. The Iterator will 
+     * contain Strings where each String represents a room (e.g. room@muc.jabber.org).
+     * 
+     * @param connection the connection to use to perform the service discovery.
+     * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com.
+     * @return an Iterator on the rooms where the requested user has joined.
+     */
+    public static Iterator getJoinedRooms(XMPPConnection connection, String user) {
+        try {
+            ArrayList answer = new ArrayList();
+            // Send the disco packet to the user
+            DiscoverItems result =
+                ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(user, discoNode);
+            // Collect the entityID for each returned item
+            for (Iterator items=result.getItems(); items.hasNext();) {
+                answer.add(((DiscoverItems.Item)items.next()).getEntityID());
+            }
+            return answer.iterator();
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            // Return an iterator on an empty collection 
+            return new ArrayList().iterator();
+        }
+    }
+
+    /**
+     * Returns the discovered information of a given room whithout actually having to join the room.
+     * The server will provide information only for rooms that are public.
+     *
+     * @param connection the XMPP connection to use for discovering information about the room.
+     * @param room the name of the room in the form "roomName@service" of which we want to discover
+     *        its information.
+     * @return the discovered information of a given room whithout actually having to join the room.
+     * @throws XMPPException if an error occured while trying to discover information of a room.
+     */
+    public static RoomInfo getRoomInfo(XMPPConnection connection, String room)
+            throws XMPPException {
+        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(room);
+        return new RoomInfo(info);
+    }
+
+    /**
+     * Returns a collection with the XMPP addresses of the Multi-User Chat services.
+     *
+     * @param connection the XMPP connection to use for discovering Multi-User Chat services.
+     * @return a collection with the XMPP addresses of the Multi-User Chat services.
+     * @throws XMPPException if an error occured while trying to discover MUC services.
+     */
+    public static Collection getServiceNames(XMPPConnection connection) throws XMPPException {
+        List answer = new ArrayList();
+        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+        DiscoverItems items = discoManager.discoverItems(connection.getHost());
+        for (Iterator it = items.getItems(); it.hasNext();) {
+            DiscoverItems.Item item = (DiscoverItems.Item) it.next();
+            DiscoverInfo info = discoManager.discoverInfo(item.getEntityID());
+            if (info.containsFeature("http://jabber.org/protocol/muc")) {
+                answer.add(item.getEntityID());
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * Returns a collection of HostedRooms where each HostedRoom has the XMPP address of the room
+     * and the room's name. Once discovered the rooms hosted by a chat service it is possible to
+     * discover more detailed room information or join the room.
+     *
+     * @param connection the XMPP connection to use for discovering hosted rooms by the MUC service.
+     * @param serviceName the service that is hosting the rooms to discover.
+     * @return a collection of HostedRooms.
+     * @throws XMPPException if an error occured while trying to discover the information.
+     */
+    public static Collection getHostedRooms(XMPPConnection connection, String serviceName)
+            throws XMPPException {
+        List answer = new ArrayList();
+        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
+        DiscoverItems items = discoManager.discoverItems(serviceName);
+        for (Iterator it = items.getItems(); it.hasNext();) {
+            DiscoverItems.Item item = (DiscoverItems.Item) it.next();
+            answer.add(new HostedRoom(item));
+        }
+        return answer;
+    }
+
+    /**
+     * Returns the name of the room this MultiUserChat object represents.
+     *
+     * @return the multi user chat room name.
+     */
+    public String getRoom() {
+        return room;
+    }
+
+    /**
+     * Creates the room according to some default configuration, assign the requesting user 
+     * as the room owner, and add the owner to the room but not allow anyone else to enter 
+     * the room (effectively "locking" the room). The requesting user will join the room
+     * under the specified nickname as soon as the room has been created.<p>
+     * 
+     * To create an "Instant Room", that means a room with some default configuration that is 
+     * available for immediate access, the room's owner should send an empty form after creating 
+     * the room. {@link #sendConfigurationForm(Form)}<p>
+     *   
+     * To create a "Reserved Room", that means a room manually configured by the room creator 
+     * before anyone is allowed to enter, the room's owner should complete and send a form after 
+     * creating the room. Once the completed configutation form is sent to the server, the server  
+     * will unlock the room. {@link #sendConfigurationForm(Form)}
+     * 
+     * @param nickname the nickname to use.
+     * @throws XMPPException if the room couldn't be created for some reason
+     *          (e.g. room already exists; user already joined to an existant room or
+     *          405 error if the user is not allowed to create the room) 
+     */
+    public synchronized void create(String nickname) throws XMPPException {
+        if (nickname == null || nickname.equals("")) {
+            throw new IllegalArgumentException("Nickname must not be null or blank.");
+        }
+        // If we've already joined the room, leave it before joining under a new
+        // nickname.
+        if (joined) {
+            throw new IllegalStateException("Creation failed - User already joined the room.");
+        }
+        // We create a room by sending a presence packet to room@service/nick
+        // and signal support for MUC. The owner will be automatically logged into the room.
+        Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
+        joinPresence.setTo(room + "/" + nickname);
+        // Indicate the the client supports MUC          
+        joinPresence.addExtension(new MUCInitialPresence());
+
+        // Wait for a presence packet back from the server.
+        PacketFilter responseFilter =
+            new AndFilter(
+                new FromMatchesFilter(room + "/" + nickname),
+                new PacketTypeFilter(Presence.class));
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send create & join packet.
+        connection.sendPacket(joinPresence);
+        // Wait up to a certain number of seconds for a reply.
+        Presence presence =
+            (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (presence == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (presence.getError() != null) {
+            throw new XMPPException(presence.getError());
+        }
+        // Whether the room existed before or was created, the user has joined the room
+        this.nickname = nickname;
+        joined = true;
+        userHasJoined();
+
+        // Look for confirmation of room creation from the server
+        MUCUser mucUser = getMUCUserExtension(presence);
+        if (mucUser != null && mucUser.getStatus() != null) {
+            if ("201".equals(mucUser.getStatus().getCode())) {
+                // Room was created and the user has joined the room
+                return;
+            }
+        }
+        // We need to leave the room since it seems that the room already existed
+        leave();
+        throw new XMPPException("Creation failed - Missing acknowledge of room creation.");
+    }
+
+    /**
+     * Joins the chat room using the specified nickname. If already joined
+     * using another nickname, this method will first leave the room and then
+     * re-join using the new nickname. The default timeout of Smack for a reply
+     * from the group chat server that the join succeeded will be used. After
+     * joining the room, the room will decide the amount of history to send. 
+     *
+     * @param nickname the nickname to use.
+     * @throws XMPPException if an error occurs joining the room. In particular, a 
+     *      401 error can occur if no password was provided and one is required; or a 
+     *      403 error can occur if the user is banned; or a 
+     *      404 error can occur if the room does not exist or is locked; or a 
+     *      407 error can occur if user is not on the member list; or a 
+     *      409 error can occur if someone is already in the group chat with the same nickname.
+     */
+    public void join(String nickname) throws XMPPException {
+        join(nickname, null, null, SmackConfiguration.getPacketReplyTimeout());
+    }
+
+    /**
+     * Joins the chat room using the specified nickname and password. If already joined
+     * using another nickname, this method will first leave the room and then
+     * re-join using the new nickname. The default timeout of Smack for a reply
+     * from the group chat server that the join succeeded will be used. After
+     * joining the room, the room will decide the amount of history to send.<p> 
+     * 
+     * A password is required when joining password protected rooms. If the room does
+     * not require a password there is no need to provide one.
+     *
+     * @param nickname the nickname to use.
+     * @param password the password to use.
+     * @throws XMPPException if an error occurs joining the room. In particular, a 
+     *      401 error can occur if no password was provided and one is required; or a 
+     *      403 error can occur if the user is banned; or a 
+     *      404 error can occur if the room does not exist or is locked; or a 
+     *      407 error can occur if user is not on the member list; or a 
+     *      409 error can occur if someone is already in the group chat with the same nickname.
+     */
+    public void join(String nickname, String password) throws XMPPException {
+        join(nickname, password, null, SmackConfiguration.getPacketReplyTimeout());
+    }
+
+    /**
+     * Joins the chat room using the specified nickname and password. If already joined
+     * using another nickname, this method will first leave the room and then
+     * re-join using the new nickname.<p>
+     * 
+     * To control the amount of history to receive while joining a room you will need to provide
+     * a configured DiscussionHistory object.<p>
+     * 
+     * A password is required when joining password protected rooms. If the room does
+     * not require a password there is no need to provide one.<p>
+     * 
+     * If the room does not already exist when the user seeks to enter it, the server will
+     * decide to create a new room or not. 
+     * 
+     * @param nickname the nickname to use.
+     * @param password the password to use.
+     * @param history the amount of discussion history to receive while joining a room.
+     * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds).
+     * @throws XMPPException if an error occurs joining the room. In particular, a 
+     *      401 error can occur if no password was provided and one is required; or a 
+     *      403 error can occur if the user is banned; or a 
+     *      404 error can occur if the room does not exist or is locked; or a 
+     *      407 error can occur if user is not on the member list; or a 
+     *      409 error can occur if someone is already in the group chat with the same nickname.
+     */
+    public synchronized void join(
+        String nickname,
+        String password,
+        DiscussionHistory history,
+        long timeout)
+        throws XMPPException {
+        if (nickname == null || nickname.equals("")) {
+            throw new IllegalArgumentException("Nickname must not be null or blank.");
+        }
+        // If we've already joined the room, leave it before joining under a new
+        // nickname.
+        if (joined) {
+            leave();
+        }
+        // We join a room by sending a presence packet where the "to"
+        // field is in the form "roomName@service/nickname"
+        Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
+        joinPresence.setTo(room + "/" + nickname);
+
+        // Indicate the the client supports MUC          
+        MUCInitialPresence mucInitialPresence = new MUCInitialPresence();
+        if (password != null) {
+            mucInitialPresence.setPassword(password);
+        }
+        if (history != null) {
+            mucInitialPresence.setHistory(history.getMUCHistory());
+        }
+        joinPresence.addExtension(mucInitialPresence);
+
+        // Wait for a presence packet back from the server.
+        PacketFilter responseFilter =
+            new AndFilter(
+                new FromMatchesFilter(room + "/" + nickname),
+                new PacketTypeFilter(Presence.class));
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send join packet.
+        connection.sendPacket(joinPresence);
+        // Wait up to a certain number of seconds for a reply.
+        Presence presence = (Presence) response.nextResult(timeout);
+        // Stop queuing results
+        response.cancel();
+
+        if (presence == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (presence.getError() != null) {
+            throw new XMPPException(presence.getError());
+        }
+        this.nickname = nickname;
+        joined = true;
+        userHasJoined();
+    }
+
+    /**
+     * Returns true if currently in the multi user chat (after calling the {@link
+     * #join(String)} method).
+     *
+     * @return true if currently in the multi user chat room.
+     */
+    public boolean isJoined() {
+        return joined;
+    }
+
+    /**
+     * Leave the chat room.
+     */
+    public synchronized void leave() {
+        // If not joined already, do nothing.
+        if (!joined) {
+            return;
+        }
+        // We leave a room by sending a presence packet where the "to"
+        // field is in the form "roomName@service/nickname"
+        Presence leavePresence = new Presence(Presence.Type.UNAVAILABLE);
+        leavePresence.setTo(room + "/" + nickname);
+        connection.sendPacket(leavePresence);
+        // Reset occupant information.
+        occupantsMap = new HashMap();
+        nickname = null;
+        joined = false;
+        userHasLeft();
+    }
+
+    /**
+     * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if 
+     * no configuration is possible. The configuration form allows to set the room's language, 
+     * enable logging, specify room's type, etc..  
+     * 
+     * @return the Form that contains the fields to complete together with the instrucions or 
+     * <tt>null</tt> if no configuration is possible.
+     * @throws XMPPException if an error occurs asking the configuration form for the room.
+     */
+    public Form getConfigurationForm() throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.GET);
+
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Request the configuration form to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+        return Form.getFormFrom(answer);
+    }
+
+    /**
+     * Sends the completed configuration form to the server. The room will be configured
+     * with the new settings defined in the form. If the form is empty then the server
+     * will create an instant room (will use default configuration).
+     * 
+     * @param form the form with the new settings.
+     * @throws XMPPException if an error occurs setting the new rooms' configuration.
+     */
+    public void sendConfigurationForm(Form form) throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        iq.addExtension(form.getDataFormToSend());
+
+        // Filter packets looking for an answer from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the completed configuration form to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    /**
+     * Returns the room's registration form that an unaffiliated user, can use to become a member 
+     * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the 
+     * privilege to register members and allow only room admins to add new members.<p>
+     * 
+     * If the user requesting registration requirements is not allowed to register with the room 
+     * (e.g. because that privilege has been restricted), the room will return a "Not Allowed" 
+     * error to the user (error code 405).
+     * 
+     * @return the registration Form that contains the fields to complete together with the 
+     * instrucions or <tt>null</tt> if no registration is possible.
+     * @throws XMPPException if an error occurs asking the registration form for the room or a 
+     * 405 error if the user is not allowed to register with the room.
+     */
+    public Form getRegistrationForm() throws XMPPException {
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.GET);
+        reg.setTo(room);
+
+        PacketFilter filter =
+            new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+        return Form.getFormFrom(result);
+    }
+
+    /**
+     * Sends the completed registration form to the server. After the user successfully submits 
+     * the form, the room may queue the request for review by the room admins or may immediately 
+     * add the user to the member list by changing the user's affiliation from "none" to "member.<p>
+     * 
+     * If the desired room nickname is already reserved for that room, the room will return a 
+     * "Conflict" error to the user (error code 409). If the room does not support registration, 
+     * it will return a "Service Unavailable" error to the user (error code 503).
+     * 
+     * @param form the completed registration form.
+     * @throws XMPPException if an error occurs submitting the registration form. In particular, a 
+     *      409 error can occur if the desired room nickname is already reserved for that room; 
+     *      or a 503 error can occur if the room does not support registration.
+     */
+    public void sendRegistrationForm(Form form) throws XMPPException {
+        Registration reg = new Registration();
+        reg.setType(IQ.Type.SET);
+        reg.setTo(room);
+        reg.addExtension(form.getDataFormToSend());
+
+        PacketFilter filter =
+            new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class));
+        PacketCollector collector = connection.createPacketCollector(filter);
+        connection.sendPacket(reg);
+        IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        collector.cancel();
+        if (result == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (result.getType() == IQ.Type.ERROR) {
+            throw new XMPPException(result.getError());
+        }
+    }
+
+    /**
+     * Sends a request to the server to destroy the room. The sender of the request
+     * should be the room's owner. If the sender of the destroy request is not the room's owner
+     * then the server will answer a "Forbidden" error (403).
+     * 
+     * @param reason the reason for the room destruction.
+     * @param alternateJID the JID of an alternate location.
+     * @throws XMPPException if an error occurs while trying to destroy the room.
+     *      An error can occur which will be wrapped by an XMPPException --
+     *      XMPP error code 403. The error code can be used to present more
+     *      appropiate error messages to end-users.
+     */
+    public void destroy(String reason, String alternateJID) throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+
+        // Create the reason for the room destruction
+        MUCOwner.Destroy destroy = new MUCOwner.Destroy();
+        destroy.setReason(reason);
+        destroy.setJid(alternateJID);
+        iq.setDestroy(destroy);
+
+        // Wait for a presence packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the room destruction request.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+        // Reset occupant information.
+        occupantsMap = new HashMap();
+        nickname = null;
+        joined = false;
+        userHasLeft();
+    }
+
+    /**
+     * Invites another user to the room in which one is an occupant. The invitation 
+     * will be sent to the room which in turn will forward the invitation to the invitee.<p>
+     * 
+     * If the room is password-protected, the invitee will receive a password to use to join
+     * the room. If the room is members-only, the the invitee may be added to the member list.
+     *
+     * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit)
+     * @param reason the reason why the user is being invited.
+     */
+    public void invite(String user, String reason) {
+        invite(new Message(), user, reason);
+    }
+
+    /**
+     * Invites another user to the room in which one is an occupant using a given Message. The invitation
+     * will be sent to the room which in turn will forward the invitation to the invitee.<p>
+     *
+     * If the room is password-protected, the invitee will receive a password to use to join
+     * the room. If the room is members-only, the the invitee may be added to the member list.
+     *
+     * @param message the message to use for sending the invitation.
+     * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit)
+     * @param reason the reason why the user is being invited.
+     */
+    public void invite(Message message, String user, String reason) {
+        // TODO listen for 404 error code when inviter supplies a non-existent JID
+        message.setTo(room);
+
+        // Create the MUCUser packet that will include the invitation
+        MUCUser mucUser = new MUCUser();
+        MUCUser.Invite invite = new MUCUser.Invite();
+        invite.setTo(user);
+        invite.setReason(reason);
+        mucUser.setInvite(invite);
+        // Add the MUCUser packet that includes the invitation to the message
+        message.addExtension(mucUser);
+
+        connection.sendPacket(message);
+    }
+
+    /**
+     * Informs the sender of an invitation that the invitee declines the invitation. The rejection 
+     * will be sent to the room which in turn will forward the rejection to the inviter.
+     * 
+     * @param conn the connection to use for sending the rejection.
+     * @param room the room that sent the original invitation.
+     * @param inviter the inviter of the declined invitation.
+     * @param reason the reason why the invitee is declining the invitation.
+     */
+    public static void decline(XMPPConnection conn, String room, String inviter, String reason) {
+        Message message = new Message(room);
+
+        // Create the MUCUser packet that will include the rejection
+        MUCUser mucUser = new MUCUser();
+        MUCUser.Decline decline = new MUCUser.Decline();
+        decline.setTo(inviter);
+        decline.setReason(reason);
+        mucUser.setDecline(decline);
+        // Add the MUCUser packet that includes the rejection
+        message.addExtension(mucUser);
+
+        conn.sendPacket(message);
+    }
+
+    /**
+     * Adds a listener to invitation notifications. The listener will be fired anytime 
+     * an invitation is received.
+     *
+     * @param conn the connection where the listener will be applied.
+     * @param listener an invitation listener.
+     */
+    public static void addInvitationListener(XMPPConnection conn, InvitationListener listener) {
+        InvitationsMonitor.getInvitationsMonitor(conn).addInvitationListener(listener);
+    }
+
+    /**
+     * Removes a listener to invitation notifications. The listener will be fired anytime 
+     * an invitation is received.
+     *
+     * @param conn the connection where the listener was applied.
+     * @param listener an invitation listener.
+     */
+    public static void removeInvitationListener(XMPPConnection conn, InvitationListener listener) {
+        InvitationsMonitor.getInvitationsMonitor(conn).removeInvitationListener(listener);
+    }
+
+    /**
+     * Adds a listener to invitation rejections notifications. The listener will be fired anytime 
+     * an invitation is declined.
+     *
+     * @param listener an invitation rejection listener.
+     */
+    public void addInvitationRejectionListener(InvitationRejectionListener listener) {
+        synchronized (invitationRejectionListeners) {
+            if (!invitationRejectionListeners.contains(listener)) {
+                invitationRejectionListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from invitation rejections notifications. The listener will be fired 
+     * anytime an invitation is declined.
+     *
+     * @param listener an invitation rejection listener.
+     */
+    public void removeInvitationRejectionListener(InvitationRejectionListener listener) {
+        synchronized (invitationRejectionListeners) {
+            invitationRejectionListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Fires invitation rejection listeners.
+     */
+    private void fireInvitationRejectionListeners(String invitee, String reason) {
+        InvitationRejectionListener[] listeners = null;
+        synchronized (invitationRejectionListeners) {
+            listeners = new InvitationRejectionListener[invitationRejectionListeners.size()];
+            invitationRejectionListeners.toArray(listeners);
+        }
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].invitationDeclined(invitee, reason);
+        }
+    }
+    
+    /**
+     * Adds a listener to subject change notifications. The listener will be fired anytime 
+     * the room's subject changes.
+     *
+     * @param listener a subject updated listener.
+     */
+    public void addSubjectUpdatedListener(SubjectUpdatedListener listener) {
+        synchronized (subjectUpdatedListeners) {
+            if (!subjectUpdatedListeners.contains(listener)) {
+                subjectUpdatedListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener from subject change notifications. The listener will be fired 
+     * anytime the room's subject changes.
+     *
+     * @param listener a subject updated listener.
+     */
+    public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) {
+        synchronized (subjectUpdatedListeners) {
+            subjectUpdatedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Fires subject updated listeners.
+     */
+    private void fireSubjectUpdatedListeners(String subject, String from) {
+        SubjectUpdatedListener[] listeners = null;
+        synchronized (subjectUpdatedListeners) {
+            listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()];
+            subjectUpdatedListeners.toArray(listeners);
+        }
+        for (int i = 0; i < listeners.length; i++) {
+            listeners[i].subjectUpdated(subject, from);
+        }
+    }
+
+    /**
+     * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room 
+     * or the room does not have a subject yet. In case the room has a subject, as soon as the 
+     * user joins the room a message with the current room's subject will be received.<p>
+     * 
+     * To be notified every time the room's subject change you should add a listener
+     * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p>
+     * 
+     * To change the room's subject use {@link #changeSubject(String)}.
+     *
+     * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the 
+     * room does not have a subject yet.
+     */
+    public String getSubject() {
+        return subject;
+    }
+
+    /**
+     * Returns the reserved room nickname for the user in the room. A user may have a reserved 
+     * nickname, for example through explicit room registration or database integration. In such 
+     * cases it may be desirable for the user to discover the reserved nickname before attempting 
+     * to enter the room.
+     *
+     * @return the reserved room nickname or <tt>null</tt> if none.
+     */
+    public String getReservedNickname() {
+        try {
+            DiscoverInfo result =
+                ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(
+                    room,
+                    "x-roomuser-item");
+            // Look for an Identity that holds the reserved nickname and return its name 
+            for (Iterator identities = result.getIdentities(); identities.hasNext();) {
+                DiscoverInfo.Identity identity = (DiscoverInfo.Identity) identities.next();
+                return identity.getName();
+            }
+            // If no Identity was found then the user does not have a reserved room nickname
+            return null;
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Returns the nickname that was used to join the room, or <tt>null</tt> if not
+     * currently joined.
+     *
+     * @return the nickname currently being used.
+     */
+    public String getNickname() {
+        return nickname;
+    }
+
+    /**
+     * Changes the occupant's nickname to a new nickname within the room. Each room occupant
+     * will receive two presence packets. One of type "unavailable" for the old nickname and one 
+     * indicating availability for the new nickname. The unavailable presence will contain the new 
+     * nickname and an appropriate status code (namely 303) as extended presence information. The 
+     * status code 303 indicates that the occupant is changing his/her nickname.
+     * 
+     * @param nickname the new nickname within the room.
+     * @throws XMPPException if the new nickname is already in use by another occupant.
+     */
+    public void changeNickname(String nickname) throws XMPPException {
+        if (nickname == null || nickname.equals("")) {
+            throw new IllegalArgumentException("Nickname must not be null or blank.");
+        }
+        // Check that we already have joined the room before attempting to change the
+        // nickname.
+        if (!joined) {
+            throw new IllegalStateException("Must be logged into the room to change nickname.");
+        }
+        // We change the nickname by sending a presence packet where the "to"
+        // field is in the form "roomName@service/nickname"
+        // We don't have to signal the MUC support again
+        Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
+        joinPresence.setTo(room + "/" + nickname);
+
+        // Wait for a presence packet back from the server.
+        PacketFilter responseFilter =
+            new AndFilter(
+                new FromMatchesFilter(room + "/" + nickname),
+                new PacketTypeFilter(Presence.class));
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send join packet.
+        connection.sendPacket(joinPresence);
+        // Wait up to a certain number of seconds for a reply.
+        Presence presence =
+            (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (presence == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (presence.getError() != null) {
+            throw new XMPPException(presence.getError());
+        }
+        this.nickname = nickname;
+    }
+
+    /**
+     * Changes the occupant's availability status within the room. The presence type
+     * will remain available but with a new status that describes the presence update and
+     * a new presence mode (e.g. Extended away).
+     * 
+     * @param status a text message describing the presence update.
+     * @param mode the mode type for the presence update.
+     */
+    public void changeAvailabilityStatus(String status, Presence.Mode mode) {
+        if (nickname == null || nickname.equals("")) {
+            throw new IllegalArgumentException("Nickname must not be null or blank.");
+        }
+        // Check that we already have joined the room before attempting to change the
+        // availability status.
+        if (!joined) {
+            throw new IllegalStateException(
+                "Must be logged into the room to change the " + "availability status.");
+        }
+        // We change the availability status by sending a presence packet to the room with the
+        // new presence status and mode
+        Presence joinPresence = new Presence(Presence.Type.AVAILABLE);
+        joinPresence.setStatus(status);
+        joinPresence.setMode(mode);
+        joinPresence.setTo(room + "/" + nickname);
+
+        // Send join packet.
+        connection.sendPacket(joinPresence);
+    }
+
+    /**
+     * Kicks a visitor or participant from the room. The kicked occupant will receive a presence
+     * of type "unavailable" including a status code 307 and optionally along with the reason 
+     * (if provided) and the bare JID of the user who initiated the kick. After the occupant
+     * was kicked from the room, the rest of the occupants will receive a presence of type
+     * "unavailable". The presence will include a status code 307 which means that the occupant
+     * was kicked from the room. 
+     * 
+     * @param nickname the nickname of the participant or visitor to kick from the room
+     * (e.g. "john").
+     * @param reason the reason why the participant or visitor is being kicked from the room.
+     * @throws XMPPException if an error occurs kicking the occupant. In particular, a
+     *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
+     *      was intended to be kicked (i.e. Not Allowed error); or a
+     *      403 error can occur if the occupant that intended to kick another occupant does
+     *      not have kicking privileges (i.e. Forbidden error); or a
+     *      400 error can occur if the provided nickname is not present in the room.
+     */
+    public void kickParticipant(String nickname, String reason) throws XMPPException {
+        changeRole(nickname, "none", reason);
+    }
+
+    /**
+     * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage
+     * who does and does not have "voice" in the room. To have voice means that a room occupant
+     * is able to send messages to the room occupants.
+     * 
+     * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john").
+     * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a 
+     *      403 error can occur if the occupant that intended to grant voice is not
+     *      a moderator in this room (i.e. Forbidden error); or a
+     *      400 error can occur if the provided nickname is not present in the room.
+     */
+    public void grantVoice(Collection nicknames) throws XMPPException {
+        changeRole(nicknames, "participant");
+    }
+
+    /**
+     * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage
+     * who does and does not have "voice" in the room. To have voice means that a room occupant
+     * is able to send messages to the room occupants.
+     *
+     * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john").
+     * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a
+     *      403 error can occur if the occupant that intended to grant voice is not
+     *      a moderator in this room (i.e. Forbidden error); or a
+     *      400 error can occur if the provided nickname is not present in the room.
+     */
+    public void grantVoice(String nickname) throws XMPPException {
+        changeRole(nickname, "participant", null);
+    }
+
+    /**
+     * Revokes voice from participants in the room. In a moderated room, a moderator may want to
+     * revoke an occupant's privileges to speak. To have voice means that a room occupant
+     * is able to send messages to the room occupants.
+     * 
+     * @param nicknames the nicknames of the participants to revoke voice (e.g. "john").
+     * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a
+     *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
+     *      was tried to revoke his voice (i.e. Not Allowed error); or a
+     *      400 error can occur if the provided nickname is not present in the room.
+     */
+    public void revokeVoice(Collection nicknames) throws XMPPException {
+        changeRole(nicknames, "visitor");
+    }
+
+    /**
+     * Revokes voice from a participant in the room. In a moderated room, a moderator may want to
+     * revoke an occupant's privileges to speak. To have voice means that a room occupant
+     * is able to send messages to the room occupants.
+     *
+     * @param nickname the nickname of the participant to revoke voice (e.g. "john").
+     * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a
+     *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
+     *      was tried to revoke his voice (i.e. Not Allowed error); or a
+     *      400 error can occur if the provided nickname is not present in the room.
+     */
+    public void revokeVoice(String nickname) throws XMPPException {
+        changeRole(nickname, "visitor", null);
+    }
+
+    /**
+     * Bans users from the room. An admin or owner of the room can ban users from a room. This
+     * means that the banned user will no longer be able to join the room unless the ban has been 
+     * removed. If the banned user was present in the room then he/she will be removed from the 
+     * room and notified that he/she was banned along with the reason (if provided) and the bare 
+     * XMPP user ID of the user who initiated the ban. 
+     * 
+     * @param jids the bare XMPP user IDs of the users to ban.
+     * @throws XMPPException if an error occurs banning a user. In particular, a
+     *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
+     *      was tried to be banned (i.e. Not Allowed error).
+     */
+    public void banUsers(Collection jids) throws XMPPException {
+        changeAffiliationByAdmin(jids, "outcast");
+    }
+
+    /**
+     * Bans a user from the room. An admin or owner of the room can ban users from a room. This
+     * means that the banned user will no longer be able to join the room unless the ban has been
+     * removed. If the banned user was present in the room then he/she will be removed from the
+     * room and notified that he/she was banned along with the reason (if provided) and the bare
+     * XMPP user ID of the user who initiated the ban.
+     *
+     * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org").
+     * @param reason the reason why the user was banned.
+     * @throws XMPPException if an error occurs banning a user. In particular, a
+     *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
+     *      was tried to be banned (i.e. Not Allowed error).
+     */
+    public void banUser(String jid, String reason) throws XMPPException {
+        changeAffiliationByAdmin(jid, "outcast", reason);
+    }
+
+    /**
+     * Grants membership to other users. Only administrators are able to grant membership. A user
+     * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
+     * that a user cannot enter without being on the member list). 
+     * 
+     * @param jids the XMPP user IDs of the users to grant membership.
+     * @throws XMPPException if an error occurs granting membership to a user.
+     */
+    public void grantMembership(Collection jids) throws XMPPException {
+        changeAffiliationByAdmin(jids, "member");
+    }
+
+    /**
+     * Grants membership to a user. Only administrators are able to grant membership. A user
+     * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
+     * that a user cannot enter without being on the member list).
+     *
+     * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs granting membership to a user.
+     */
+    public void grantMembership(String jid) throws XMPPException {
+        changeAffiliationByAdmin(jid, "member", null);
+    }
+
+    /**
+     * Revokes users' membership. Only administrators are able to revoke membership. A user
+     * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
+     * that a user cannot enter without being on the member list). If the user is in the room and 
+     * the room is of type members-only then the user will be removed from the room.
+     * 
+     * @param jids the bare XMPP user IDs of the users to revoke membership.
+     * @throws XMPPException if an error occurs revoking membership to a user.
+     */
+    public void revokeMembership(Collection jids) throws XMPPException {
+        changeAffiliationByAdmin(jids, "none");
+    }
+
+    /**
+     * Revokes a user's membership. Only administrators are able to revoke membership. A user
+     * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
+     * that a user cannot enter without being on the member list). If the user is in the room and
+     * the room is of type members-only then the user will be removed from the room.
+     *
+     * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs revoking membership to a user.
+     */
+    public void revokeMembership(String jid) throws XMPPException {
+        changeAffiliationByAdmin(jid, "none", null);
+    }
+
+    /**
+     * Grants moderator privileges to participants or visitors. Room administrators may grant
+     * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite 
+     * other users, modify room's subject plus all the partcipants privileges.
+     *  
+     * @param nicknames the nicknames of the occupants to grant moderator privileges.
+     * @throws XMPPException if an error occurs granting moderator privileges to a user.
+     */
+    public void grantModerator(Collection nicknames) throws XMPPException {
+        changeRole(nicknames, "moderator");
+    }
+
+    /**
+     * Grants moderator privileges to a participant or visitor. Room administrators may grant
+     * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite
+     * other users, modify room's subject plus all the partcipants privileges.
+     *
+     * @param nickname the nickname of the occupant to grant moderator privileges.
+     * @throws XMPPException if an error occurs granting moderator privileges to a user.
+     */
+    public void grantModerator(String nickname) throws XMPPException {
+        changeRole(nickname, "moderator", null);
+    }
+
+    /**
+     * Revokes moderator privileges from other users. The occupant that loses moderator
+     * privileges will become a participant. Room administrators may revoke moderator privileges
+     * only to occupants whose affiliation is member or none. This means that an administrator is 
+     * not allowed to revoke moderator privileges from other room administrators or owners.
+     *  
+     * @param nicknames the nicknames of the occupants to revoke moderator privileges.
+     * @throws XMPPException if an error occurs revoking moderator privileges from a user.
+     */
+    public void revokeModerator(Collection nicknames) throws XMPPException {
+        changeRole(nicknames, "participant");
+    }
+
+    /**
+     * Revokes moderator privileges from another user. The occupant that loses moderator
+     * privileges will become a participant. Room administrators may revoke moderator privileges
+     * only to occupants whose affiliation is member or none. This means that an administrator is
+     * not allowed to revoke moderator privileges from other room administrators or owners.
+     *
+     * @param nickname the nickname of the occupant to revoke moderator privileges.
+     * @throws XMPPException if an error occurs revoking moderator privileges from a user.
+     */
+    public void revokeModerator(String nickname) throws XMPPException {
+        changeRole(nickname, "participant", null);
+    }
+
+    /**
+     * Grants ownership privileges to other users. Room owners may grant ownership privileges.
+     * Some room implementations will not allow to grant ownership privileges to other users.
+     * An owner is allowed to change defining room features as well as perform all administrative
+     * functions.
+     *
+     * @param jids the collection of bare XMPP user IDs of the users to grant ownership.
+     * @throws XMPPException if an error occurs granting ownership privileges to a user.
+     */
+    public void grantOwnership(Collection jids) throws XMPPException {
+        changeAffiliationByOwner(jids, "owner");
+    }
+
+    /**
+     * Grants ownership privileges to another user. Room owners may grant ownership privileges. 
+     * Some room implementations will not allow to grant ownership privileges to other users.
+     * An owner is allowed to change defining room features as well as perform all administrative 
+     * functions.
+     *  
+     * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs granting ownership privileges to a user.
+     */
+    public void grantOwnership(String jid) throws XMPPException {
+        changeAffiliationByOwner(jid, "owner");
+    }
+
+    /**
+     * Revokes ownership privileges from other users. The occupant that loses ownership
+     * privileges will become an administrator. Room owners may revoke ownership privileges.
+     * Some room implementations will not allow to grant ownership privileges to other users.
+     *
+     * @param jids the bare XMPP user IDs of the users to revoke ownership.
+     * @throws XMPPException if an error occurs revoking ownership privileges from a user.
+     */
+    public void revokeOwnership(Collection jids) throws XMPPException {
+        changeAffiliationByOwner(jids, "admin");
+    }
+
+    /**
+     * Revokes ownership privileges from another user. The occupant that loses ownership 
+     * privileges will become an administrator. Room owners may revoke ownership privileges. 
+     * Some room implementations will not allow to grant ownership privileges to other users.
+     *  
+     * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs revoking ownership privileges from a user.
+     */
+    public void revokeOwnership(String jid) throws XMPPException {
+        changeAffiliationByOwner(jid, "admin");
+    }
+
+    /**
+     * Grants administrator privileges to other users. Room owners may grant administrator
+     * privileges to a member or unaffiliated user. An administrator is allowed to perform
+     * administrative functions such as banning users and edit moderator list.
+     *
+     * @param jids the bare XMPP user IDs of the users to grant administrator privileges.
+     * @throws XMPPException if an error occurs granting administrator privileges to a user.
+     */
+    public void grantAdmin(Collection jids) throws XMPPException {
+        changeAffiliationByOwner(jids, "admin");
+    }
+
+    /**
+     * Grants administrator privileges to another user. Room owners may grant administrator 
+     * privileges to a member or unaffiliated user. An administrator is allowed to perform 
+     * administrative functions such as banning users and edit moderator list. 
+     *  
+     * @param jid the bare XMPP user ID of the user to grant administrator privileges 
+     * (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs granting administrator privileges to a user.
+     */
+    public void grantAdmin(String jid) throws XMPPException {
+        changeAffiliationByOwner(jid, "admin");
+    }
+
+    /**
+     * Revokes administrator privileges from users. The occupant that loses administrator
+     * privileges will become a member. Room owners may revoke administrator privileges from 
+     * a member or unaffiliated user. 
+     *  
+     * @param jids the bare XMPP user IDs of the user to revoke administrator privileges.
+     * @throws XMPPException if an error occurs revoking administrator privileges from a user.
+     */
+    public void revokeAdmin(Collection jids) throws XMPPException {
+        changeAffiliationByOwner(jids, "member");
+    }
+
+    /**
+     * Revokes administrator privileges from a user. The occupant that loses administrator
+     * privileges will become a member. Room owners may revoke administrator privileges from
+     * a member or unaffiliated user.
+     *
+     * @param jid the bare XMPP user ID of the user to revoke administrator privileges
+     * (e.g. "user@host.org").
+     * @throws XMPPException if an error occurs revoking administrator privileges from a user.
+     */
+    public void revokeAdmin(String jid) throws XMPPException {
+        changeAffiliationByOwner(jid, "member");
+    }
+
+    private void changeAffiliationByOwner(String jid, String affiliation) throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        // Set the new affiliation.
+        MUCOwner.Item item = new MUCOwner.Item(affiliation);
+        item.setJid(jid);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    private void changeAffiliationByOwner(Collection jids, String affiliation)
+            throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        for (Iterator it=jids.iterator(); it.hasNext();) {
+            // Set the new affiliation.
+            MUCOwner.Item item = new MUCOwner.Item(affiliation);
+            item.setJid((String) it.next());
+            iq.addItem(item);
+        }
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    private void changeAffiliationByAdmin(String jid, String affiliation, String reason)
+            throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        // Set the new affiliation.
+        MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null);
+        item.setJid(jid);
+        item.setReason(reason);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    private void changeAffiliationByAdmin(Collection jids, String affiliation)
+            throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        for (Iterator it=jids.iterator(); it.hasNext();) {
+            // Set the new affiliation.
+            MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null);
+            item.setJid((String) it.next());
+            iq.addItem(item);
+        }
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    private void changeRole(String nickname, String role, String reason) throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        // Set the new role.
+        MUCAdmin.Item item = new MUCAdmin.Item(null, role);
+        item.setNick(nickname);
+        item.setReason(reason);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    private void changeRole(Collection nicknames, String role) throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.SET);
+        for (Iterator it=nicknames.iterator(); it.hasNext();) {
+            // Set the new role.
+            MUCAdmin.Item item = new MUCAdmin.Item(null, role);
+            item.setNick((String) it.next());
+            iq.addItem(item);
+        }
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the change request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    /**
+     * Returns the number of occupants in the group chat.<p>
+     *
+     * Note: this value will only be accurate after joining the group chat, and
+     * may fluctuate over time. If you query this value directly after joining the
+     * group chat it may not be accurate, as it takes a certain amount of time for
+     * the server to send all presence packets to this client.
+     *
+     * @return the number of occupants in the group chat.
+     */
+    public int getOccupantsCount() {
+        synchronized (occupantsMap) {
+            return occupantsMap.size();
+        }
+    }
+
+    /**
+     * Returns an Iterator (of Strings) for the list of fully qualified occupants
+     * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser".
+     * Typically, a client would only display the nickname of the occupant. To
+     * get the nickname from the fully qualified name, use the
+     * {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method.
+     * Note: this value will only be accurate after joining the group chat, and may
+     * fluctuate over time.
+     *
+     * @return an Iterator for the occupants in the group chat.
+     */
+    public Iterator getOccupants() {
+        synchronized (occupantsMap) {
+            return Collections.unmodifiableList(new ArrayList(occupantsMap.keySet())).iterator();
+        }
+    }
+
+    /**
+     * Returns the presence info for a particular user, or <tt>null</tt> if the user
+     * is not in the room.<p>
+     * 
+     * @param user the room occupant to search for his presence. The format of user must
+     * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch).
+     * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable
+     *      or if no presence information is available.
+     */
+    public Presence getOccupantPresence(String user) {
+        return (Presence) occupantsMap.get(user);
+    }
+
+    /**
+     * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the
+     * user is not in the room. The Occupant object may include information such as full
+     * JID of the user as well as the role and affiliation of the user in the room.<p>
+     *
+     * @param user the room occupant to search for his presence. The format of user must
+     * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch).
+     * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room).
+     */
+    public Occupant getOccupant(String user) {
+        Presence presence = (Presence) occupantsMap.get(user);
+        if (presence != null) {
+            return new Occupant(presence);
+        }
+        return null;
+    }
+
+    /**
+     * Adds a packet listener that will be notified of any new Presence packets
+     * sent to the group chat. Using a listener is a suitable way to know when the list
+     * of occupants should be re-loaded due to any changes.
+     *
+     * @param listener a packet listener that will be notified of any presence packets
+     *      sent to the group chat.
+     */
+    public void addParticipantListener(PacketListener listener) {
+        connection.addPacketListener(listener, presenceFilter);
+        connectionListeners.add(listener);
+    }
+
+    /**
+     * Remoces a packet listener that was being notified of any new Presence packets
+     * sent to the group chat.
+     *
+     * @param listener a packet listener that was being notified of any presence packets
+     *      sent to the group chat.
+     */
+    public void removeParticipantListener(PacketListener listener) {
+        connection.removePacketListener(listener);
+        connectionListeners.remove(listener);
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> with the room owners.
+     *
+     * @return a collection of <code>Affiliate</code> with the room owners.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getOwners() throws XMPPException {
+        return getAffiliatesByOwner("owner");
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> with the room administrators.
+     *
+     * @return a collection of <code>Affiliate</code> with the room administrators.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getAdmins() throws XMPPException {
+        return getAffiliatesByOwner("admin");
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> with the room members.
+     *
+     * @return a collection of <code>Affiliate</code> with the room members.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getMembers() throws XMPPException {
+        return getAffiliatesByAdmin("member");
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> with the room outcasts.
+     *
+     * @return a collection of <code>Affiliate</code> with the room outcasts.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getOutcasts() throws XMPPException {
+        return getAffiliatesByAdmin("outcast");
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> that have the specified room affiliation
+     * sending a request in the owner namespace.
+     *
+     * @param affiliation the affiliation of the users in the room.
+     * @return a collection of <code>Affiliate</code> that have the specified room affiliation.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    private Collection getAffiliatesByOwner(String affiliation) throws XMPPException {
+        MUCOwner iq = new MUCOwner();
+        iq.setTo(room);
+        iq.setType(IQ.Type.GET);
+        // Set the specified affiliation. This may request the list of owners/admins/members/outcasts.
+        MUCOwner.Item item = new MUCOwner.Item(affiliation);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        MUCOwner answer = (MUCOwner) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+        // Get the list of affiliates from the server's answer
+        List affiliates = new ArrayList();
+        for (Iterator it = answer.getItems(); it.hasNext();) {
+            affiliates.add(new Affiliate((MUCOwner.Item) it.next()));
+        }
+        return affiliates;
+    }
+
+    /**
+     * Returns a collection of <code>Affiliate</code> that have the specified room affiliation
+     * sending a request in the admin namespace.
+     *
+     * @param affiliation the affiliation of the users in the room.
+     * @return a collection of <code>Affiliate</code> that have the specified room affiliation.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    private Collection getAffiliatesByAdmin(String affiliation) throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.GET);
+        // Set the specified affiliation. This may request the list of owners/admins/members/outcasts.
+        MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+        // Get the list of affiliates from the server's answer
+        List affiliates = new ArrayList();
+        for (Iterator it = answer.getItems(); it.hasNext();) {
+            affiliates.add(new Affiliate((MUCAdmin.Item) it.next()));
+        }
+        return affiliates;
+    }
+
+    /**
+     * Returns a collection of <code>Occupant</code> with the room moderators.
+     *
+     * @return a collection of <code>Occupant</code> with the room moderators.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getModerators() throws XMPPException {
+        return getOccupants("moderator");
+    }
+
+    /**
+     * Returns a collection of <code>Occupant</code> with the room participants.
+     *
+     * @return a collection of <code>Occupant</code> with the room participants.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    public Collection getParticipants() throws XMPPException {
+        return getOccupants("participant");
+    }
+
+    /**
+     * Returns a collection of <code>Occupant</code> that have the specified room role.
+     *
+     * @param role the role of the occupant in the room.
+     * @return a collection of <code>Occupant</code> that have the specified room role.
+     * @throws XMPPException if an error occured while performing the request to the server or you
+     *         don't have enough privileges to get this information.
+     */
+    private Collection getOccupants(String role) throws XMPPException {
+        MUCAdmin iq = new MUCAdmin();
+        iq.setTo(room);
+        iq.setType(IQ.Type.GET);
+        // Set the specified role. This may request the list of moderators/participants.
+        MUCAdmin.Item item = new MUCAdmin.Item(null, role);
+        iq.addItem(item);
+
+        // Wait for a response packet back from the server.
+        PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID());
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send the request to the server.
+        connection.sendPacket(iq);
+        // Wait up to a certain number of seconds for a reply.
+        MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+        // Get the list of participants from the server's answer
+        List participants = new ArrayList();
+        for (Iterator it = answer.getItems(); it.hasNext();) {
+            participants.add(new Occupant((MUCAdmin.Item) it.next()));
+        }
+        return participants;
+    }
+
+    /**
+     * Sends a message to the chat room.
+     *
+     * @param text the text of the message to send.
+     * @throws XMPPException if sending the message fails.
+     */
+    public void sendMessage(String text) throws XMPPException {
+        Message message = new Message(room, Message.Type.GROUP_CHAT);
+        message.setBody(text);
+        connection.sendPacket(message);
+    }
+
+    /**
+     * Returns a new Chat for sending private messages to a given room occupant.
+     * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server
+     * service will change the 'from' address to the sender's room JID and delivering the message 
+     * to the intended recipient's full JID.
+     *
+     * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul').
+     * @return new Chat for sending private messages to a given room occupant.
+     */
+    public Chat createPrivateChat(String occupant) {
+        return new Chat(connection, occupant);
+    }
+
+    /**
+     * Creates a new Message to send to the chat room.
+     *
+     * @return a new Message addressed to the chat room.
+     */
+    public Message createMessage() {
+        return new Message(room, Message.Type.GROUP_CHAT);
+    }
+
+    /**
+     * Sends a Message to the chat room.
+     *
+     * @param message the message.
+     * @throws XMPPException if sending the message fails.
+     */
+    public void sendMessage(Message message) throws XMPPException {
+        connection.sendPacket(message);
+    }
+
+    /**
+    * Polls for and returns the next message, or <tt>null</tt> if there isn't
+    * a message immediately available. This method provides significantly different
+    * functionalty than the {@link #nextMessage()} method since it's non-blocking.
+    * In other words, the method call will always return immediately, whereas the
+    * nextMessage method will return only when a message is available (or after
+    * a specific timeout).
+    *
+    * @return the next message if one is immediately available and
+    *      <tt>null</tt> otherwise.
+    */
+    public Message pollMessage() {
+        return (Message) messageCollector.pollResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a message is available.
+     *
+     * @return the next message.
+     */
+    public Message nextMessage() {
+        return (Message) messageCollector.nextResult();
+    }
+
+    /**
+     * Returns the next available message in the chat. The method call will block
+     * (not return) until a packet is available or the <tt>timeout</tt> has elapased.
+     * If the timeout elapses without a result, <tt>null</tt> will be returned.
+     *
+     * @param timeout the maximum amount of time to wait for the next message.
+     * @return the next message, or <tt>null</tt> if the timeout elapses without a
+     *      message becoming available.
+     */
+    public Message nextMessage(long timeout) {
+        return (Message) messageCollector.nextResult(timeout);
+    }
+
+    /**
+     * Adds a packet listener that will be notified of any new messages in the
+     * group chat. Only "group chat" messages addressed to this group chat will
+     * be delivered to the listener. If you wish to listen for other packets
+     * that may be associated with this group chat, you should register a
+     * PacketListener directly with the XMPPConnection with the appropriate
+     * PacketListener.
+     *
+     * @param listener a packet listener.
+     */
+    public void addMessageListener(PacketListener listener) {
+        connection.addPacketListener(listener, messageFilter);
+        connectionListeners.add(listener);
+    }
+
+    /**
+     * Removes a packet listener that was being notified of any new messages in the
+     * multi user chat. Only "group chat" messages addressed to this multi user chat were
+     * being delivered to the listener.
+     *
+     * @param listener a packet listener.
+     */
+    public void removeMessageListener(PacketListener listener) {
+        connection.removePacketListener(listener);
+        connectionListeners.remove(listener);
+    }
+
+    /**
+     * Changes the subject within the room. As a default, only users with a role of "moderator" 
+     * are allowed to change the subject in a room. Although some rooms may be configured to 
+     * allow a mere participant or even a visitor to change the subject.
+     * 
+     * @param subject the new room's subject to set. 
+     * @throws XMPPException if someone without appropriate privileges attempts to change the 
+     *          room subject will throw an error with code 403 (i.e. Forbidden)
+     */
+    public void changeSubject(final String subject) throws XMPPException {
+        Message message = new Message(room, Message.Type.GROUP_CHAT);
+        message.setSubject(subject);
+        // Wait for an error or confirmation message back from the server.
+        PacketFilter responseFilter =
+            new AndFilter(
+                new FromMatchesFilter(room),
+                new PacketTypeFilter(Message.class));
+        responseFilter = new AndFilter(responseFilter, new PacketFilter() {
+            public boolean accept(Packet packet) {
+                Message msg = (Message) packet;
+                return subject.equals(msg.getSubject());
+            }
+        });
+        PacketCollector response = connection.createPacketCollector(responseFilter);
+        // Send change subject packet.
+        connection.sendPacket(message);
+        // Wait up to a certain number of seconds for a reply.
+        Message answer =
+            (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        response.cancel();
+
+        if (answer == null) {
+            throw new XMPPException("No response from server.");
+        }
+        else if (answer.getError() != null) {
+            throw new XMPPException(answer.getError());
+        }
+    }
+
+    /**
+     * Notification message that the user has joined the room. 
+     */
+    private synchronized void userHasJoined() {
+        // Update the list of joined rooms through this connection
+        ArrayList rooms = (ArrayList)joinedRooms.get(connection);
+        if (rooms == null) {
+            rooms = new ArrayList();
+            joinedRooms.put(connection, rooms);
+        }
+        rooms.add(room);
+    }
+    
+    /**
+     * Notification message that the user has left the room. 
+     */
+    private synchronized void userHasLeft() {
+        // Update the list of joined rooms through this connection
+        ArrayList rooms = (ArrayList)joinedRooms.get(connection);
+        if (rooms == null) {
+            return;
+        }
+        rooms.remove(room);
+    }
+    
+    /**
+     * Returns the MUCUser packet extension included in the packet or <tt>null</tt> if none.
+     * 
+     * @param packet the packet that may include the MUCUser extension.
+     * @return the MUCUser found in the packet.
+     */
+    private MUCUser getMUCUserExtension(Packet packet) {
+        if (packet != null) {
+            // Get the MUC User extension
+            return (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user");
+        }
+        return null;
+    }
+
+    /**
+     * Adds a listener that will be notified of changes in your status in the room 
+     * such as the user being kicked, banned, or granted admin permissions. 
+     *
+     * @param listener a user status listener.
+     */
+    public void addUserStatusListener(UserStatusListener listener) {
+        synchronized (userStatusListeners) {
+            if (!userStatusListeners.contains(listener)) {
+                userStatusListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener that was being notified of changes in your status in the room 
+     * such as the user being kicked, banned, or granted admin permissions. 
+     *
+     * @param listener a user status listener.
+     */
+    public void removeUserStatusListener(UserStatusListener listener) {
+        synchronized (userStatusListeners) {
+            userStatusListeners.remove(listener);
+        }
+    }
+
+    private void fireUserStatusListeners(String methodName, Object[] params) {
+        UserStatusListener[] listeners = null;
+        synchronized (userStatusListeners) {
+            listeners = new UserStatusListener[userStatusListeners.size()];
+            userStatusListeners.toArray(listeners);
+        }
+        // Get the classes of the method parameters
+        Class[] paramClasses = new Class[params.length];
+        for (int i = 0; i < params.length; i++) {
+            paramClasses[i] = params[i].getClass();
+        }
+        try {
+            // Get the method to execute based on the requested methodName and parameters classes
+            Method method = UserStatusListener.class.getDeclaredMethod(methodName, paramClasses);
+            for (int i = 0; i < listeners.length; i++) {
+                method.invoke(listeners[i], params);
+            }
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Adds a listener that will be notified of changes in occupants status in the room
+     * such as the user being kicked, banned, or granted admin permissions. 
+     *
+     * @param listener a participant status listener.
+     */
+    public void addParticipantStatusListener(ParticipantStatusListener listener) {
+        synchronized (participantStatusListeners) {
+            if (!participantStatusListeners.contains(listener)) {
+                participantStatusListeners.add(listener);
+            }
+        }
+    }
+
+    /**
+     * Removes a listener that was being notified of changes in occupants status in the room
+     * such as the user being kicked, banned, or granted admin permissions. 
+     *
+     * @param listener a participant status listener.
+     */
+    public void removeParticipantStatusListener(ParticipantStatusListener listener) {
+        synchronized (participantStatusListeners) {
+            participantStatusListeners.remove(listener);
+        }
+    }
+
+    private void fireParticipantStatusListeners(String methodName, String param) {
+        ParticipantStatusListener[] listeners = null;
+        synchronized (participantStatusListeners) {
+            listeners = new ParticipantStatusListener[participantStatusListeners.size()];
+            participantStatusListeners.toArray(listeners);
+        }
+        try {
+            // Get the method to execute based on the requested methodName and parameter
+            Method method =
+                ParticipantStatusListener.class.getDeclaredMethod(
+                    methodName,
+                    new Class[] { String.class });
+            for (int i = 0; i < listeners.length; i++) {
+                method.invoke(listeners[i], new Object[] {param});
+            }
+        } catch (NoSuchMethodException e) {
+            e.printStackTrace();
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    private void init() {
+        // Create a collector for incoming messages.
+        messageFilter =
+            new AndFilter(
+                new FromMatchesFilter(room),
+                new MessageTypeFilter(Message.Type.GROUP_CHAT));
+        messageFilter = new AndFilter(messageFilter, new PacketFilter() {
+            public boolean accept(Packet packet) {
+                Message msg = (Message) packet;
+                return msg.getBody() != null;
+            }
+        });
+        messageCollector = connection.createPacketCollector(messageFilter);
+
+        // Create a listener for subject updates.
+        subjectFilter =
+            new AndFilter(
+                new FromMatchesFilter(room),
+                new MessageTypeFilter(Message.Type.GROUP_CHAT));
+        subjectFilter = new AndFilter(subjectFilter, new PacketFilter() {
+            public boolean accept(Packet packet) {
+                Message msg = (Message) packet;
+                return msg.getSubject() != null;
+            }
+        });
+        subjectListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message msg = (Message) packet;
+                // Update the room subject
+                subject = msg.getSubject();
+                // Fire event for subject updated listeners
+                fireSubjectUpdatedListeners(
+                    msg.getSubject(),
+                    msg.getFrom());
+                
+            }
+        };
+        connection.addPacketListener(subjectListener, subjectFilter);
+
+        // Create a listener for all presence updates.
+        presenceFilter =
+            new AndFilter(new FromMatchesFilter(room), new PacketTypeFilter(Presence.class));
+        presenceListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Presence presence = (Presence) packet;
+                String from = presence.getFrom();
+                String myRoomJID = room + "/" + nickname;
+                boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
+                if (presence.getType() == Presence.Type.AVAILABLE) {
+                    Presence oldPresence;
+                    synchronized (occupantsMap) {
+                        oldPresence = (Presence)occupantsMap.get(from);
+                        occupantsMap.put(from, presence);
+                    }
+                    if (oldPresence != null) {
+                        // Get the previous occupant's affiliation & role
+                        MUCUser mucExtension = getMUCUserExtension(oldPresence);
+                        String oldAffiliation = mucExtension.getItem().getAffiliation(); 
+                        String oldRole = mucExtension.getItem().getRole();
+                        // Get the new occupant's affiliation & role
+                        mucExtension = getMUCUserExtension(presence);
+                        String newAffiliation = mucExtension.getItem().getAffiliation(); 
+                        String newRole = mucExtension.getItem().getRole();
+                        // Fire role modification events
+                        checkRoleModifications(oldRole, newRole, isUserStatusModification, from);
+                        // Fire affiliation modification events
+                        checkAffiliationModifications(
+                            oldAffiliation,
+                            newAffiliation,
+                            isUserStatusModification,
+                            from);
+                    }
+                    else {
+                        // A new occupant has joined the room
+                        if (!isUserStatusModification) {
+                            fireParticipantStatusListeners("joined", from);
+                        }
+                    }
+                }
+                else if (presence.getType() == Presence.Type.UNAVAILABLE) {
+                    synchronized (occupantsMap) {
+                        occupantsMap.remove(from);
+                    }
+                    MUCUser mucUser = getMUCUserExtension(presence);
+                    if (mucUser != null && mucUser.getStatus() != null) {
+                        // Fire events according to the received presence code
+                        checkPresenceCode(
+                            mucUser.getStatus().getCode(),
+                            presence.getFrom().equals(myRoomJID),
+                            mucUser,
+                            from);
+                    } else {
+                        // An occupant has left the room
+                        if (!isUserStatusModification) {
+                            fireParticipantStatusListeners("left", from);
+                        }
+                    }
+                }
+            }
+        };
+        connection.addPacketListener(presenceListener, presenceFilter);
+
+        // Listens for all messages that include a MUCUser extension and fire the invitation 
+        // rejection listeners if the message includes an invitation rejection.
+        declinesFilter = new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user");
+        declinesListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                // Get the MUC User extension
+                MUCUser mucUser = getMUCUserExtension(packet);
+                // Check if the MUCUser informs that the invitee has declined the invitation
+                if (mucUser.getDecline() != null) {
+                    // Fire event for invitation rejection listeners
+                    fireInvitationRejectionListeners(
+                        mucUser.getDecline().getFrom(),
+                        mucUser.getDecline().getReason());
+                }
+            };
+        };
+        connection.addPacketListener(declinesListener, declinesFilter);
+    }
+
+    /**
+     * Fires notification events if the role of a room occupant has changed. If the occupant that 
+     * changed his role is your occupant then the <code>UserStatusListeners</code> added to this 
+     * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed 
+     * his role is not yours then the <code>ParticipantStatusListeners</code> added to this 
+     * <code>MultiUserChat</code> will be fired. The following table shows the events that will 
+     * be fired depending on the previous and new role of the occupant.
+     * 
+     * <pre>
+     * <table border="1">
+     * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+     * 
+     * <tr><td>None</td><td>Visitor</td><td>--</td></tr>
+     * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr>
+     * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr>
+     *
+     * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr>
+     * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
+     * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
+     * 
+     * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr>
+     * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr>
+     * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr>
+     *
+     * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr>
+     * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr>
+     * <tr><td>Participant</td><td>None</td><td>kicked</td></tr>
+     * </table>
+     * </pre>
+     * 
+     * @param oldRole the previous role of the user in the room before receiving the new presence
+     * @param newRole the new role of the user in the room after receiving the new presence
+     * @param isUserModification whether the received presence is about your user in the room or not
+     * @param from the occupant whose role in the room has changed
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    private void checkRoleModifications(
+        String oldRole,
+        String newRole,
+        boolean isUserModification,
+        String from) {
+        // Voice was granted to a visitor
+        if (("visitor".equals(oldRole) || "none".equals(oldRole))
+            && "participant".equals(newRole)) {
+            if (isUserModification) {
+                fireUserStatusListeners("voiceGranted", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("voiceGranted", from);
+            }
+        }
+        // The participant's voice was revoked from the room
+        else if (
+            "participant".equals(oldRole)
+                && ("visitor".equals(newRole) || "none".equals(newRole))) {
+            if (isUserModification) {
+                fireUserStatusListeners("voiceRevoked", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("voiceRevoked", from);
+            }
+        }
+        // Moderator privileges were granted to a participant
+        if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) {
+            if ("visitor".equals(oldRole) || "none".equals(oldRole)) {
+                if (isUserModification) {
+                    fireUserStatusListeners("voiceGranted", new Object[] {});
+                }
+                else {
+                    fireParticipantStatusListeners("voiceGranted", from);
+                }
+            }
+            if (isUserModification) {
+                fireUserStatusListeners("moderatorGranted", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("moderatorGranted", from);
+            }
+        }
+        // Moderator privileges were revoked from a participant
+        else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) {
+            if ("visitor".equals(newRole) || "none".equals(newRole)) {
+                if (isUserModification) {
+                    fireUserStatusListeners("voiceRevoked", new Object[] {});
+                }
+                else {
+                    fireParticipantStatusListeners("voiceRevoked", from);
+                }
+            }
+            if (isUserModification) {
+                fireUserStatusListeners("moderatorRevoked", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("moderatorRevoked", from);
+            }
+        }
+    }
+
+    /**
+     * Fires notification events if the affiliation of a room occupant has changed. If the 
+     * occupant that changed his affiliation is your occupant then the 
+     * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired. 
+     * On the other hand, if the occupant that changed his affiliation is not yours then the 
+     * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be 
+     * fired. The following table shows the events that will be fired depending on the previous 
+     * and new affiliation of the occupant.
+     * 
+     * <pre>
+     * <table border="1">
+     * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
+     * 
+     * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr>
+     * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr>
+     * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr>
+     * 
+     * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr>
+     * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr>
+     * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr>
+     *
+     * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr>
+     * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr>
+     * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr>
+     * 
+     * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr>
+     * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr>
+     * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr>
+     * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr>
+     * </table>
+     * </pre>
+     * 
+     * @param oldAffiliation the previous affiliation of the user in the room before receiving the 
+     * new presence
+     * @param newAffiliation the new affiliation of the user in the room after receiving the new 
+     * presence
+     * @param isUserModification whether the received presence is about your user in the room or not
+     * @param from the occupant whose role in the room has changed
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    private void checkAffiliationModifications(
+        String oldAffiliation,
+        String newAffiliation,
+        boolean isUserModification,
+        String from) {
+        // First check for revoked affiliation and then for granted affiliations. The idea is to
+        // first fire the "revoke" events and then fire the "grant" events.
+        
+        // The user's ownership to the room was revoked
+        if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("ownershipRevoked", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("ownershipRevoked", from);
+            }
+        }
+        // The user's administrative privileges to the room were revoked
+        else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("adminRevoked", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("adminRevoked", from);
+            }
+        }
+        // The user's membership to the room was revoked
+        else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("membershipRevoked", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("membershipRevoked", from);
+            }
+        }
+
+        // The user was granted ownership to the room
+        if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("ownershipGranted", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("ownershipGranted", from);
+            }
+        }
+        // The user was granted administrative privileges to the room
+        else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("adminGranted", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("adminGranted", from);
+            }
+        }
+        // The user was granted membership to the room
+        else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) {
+            if (isUserModification) {
+                fireUserStatusListeners("membershipGranted", new Object[] {});
+            }
+            else {
+                fireParticipantStatusListeners("membershipGranted", from);
+            }
+        }
+    }
+
+    /**
+     * Fires events according to the received presence code.
+     * 
+     * @param code
+     * @param isUserModification
+     * @param mucUser
+     * @param from
+     */
+    private void checkPresenceCode(
+        String code,
+        boolean isUserModification,
+        MUCUser mucUser,
+        String from) {
+        // Check if an occupant was kicked from the room
+        if ("307".equals(code)) {
+            // Check if this occupant was kicked
+            if (isUserModification) {
+                joined = false;
+
+                fireUserStatusListeners(
+                    "kicked",
+                    new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()});
+
+                // Reset occupant information.
+                occupantsMap = new HashMap();
+                nickname = null;
+                userHasLeft();
+            }
+            else {
+                fireParticipantStatusListeners("kicked", from);
+            }
+        }
+        // A user was banned from the room 
+        else if ("301".equals(code)) {
+            // Check if this occupant was banned
+            if (isUserModification) {
+                joined = false;
+
+                fireUserStatusListeners(
+                    "banned",
+                    new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()});
+
+                // Reset occupant information.
+                occupantsMap = new HashMap();
+                nickname = null;
+                userHasLeft();
+            }
+            else {
+                // TODO Check if we have to send the JID of the banned user
+                fireParticipantStatusListeners("banned", from);
+            }
+        }
+        // A user's membership was revoked from the room 
+        else if ("321".equals(code)) {
+            // Check if this occupant's membership was revoked
+            if (isUserModification) {
+                joined = false;
+
+                fireUserStatusListeners("membershipRevoked", new Object[] {});
+
+                // Reset occupant information.
+                occupantsMap = new HashMap();
+                nickname = null;
+                userHasLeft();
+            }
+        }
+        // A occupant has changed his nickname in the room
+        else if ("303".equals(code)) {
+            fireParticipantStatusListeners("nicknameChanged", mucUser.getItem().getNick());
+        }
+    }
+
+    public void finalize() throws Throwable {
+        super.finalize();
+        try {
+            if (connection != null) {
+                messageCollector.cancel();
+                connection.removePacketListener(subjectListener);
+                connection.removePacketListener(presenceListener);
+                connection.removePacketListener(declinesListener);
+                // Remove all the PacketListeners added to the connection by this chat
+                for (Iterator it=connectionListeners.iterator(); it.hasNext();) {
+                    connection.removePacketListener((PacketListener) it.next());
+                }
+            }
+        }
+        catch (Exception e) {}
+    }
+
+    /**
+     * An InvitationsMonitor monitors a given connection to detect room invitations. Every
+     * time the InvitationsMonitor detects a new invitation it will fire the invitation listeners. 
+     * 
+     * @author Gaston Dombiak
+     */
+    private static class InvitationsMonitor implements ConnectionListener {
+        // We use a WeakHashMap so that the GC can collect the monitor when the
+        // connection is no longer referenced by any object.
+        private static Map monitors = new WeakHashMap();
+
+        private List invitationsListeners = new ArrayList();
+        private XMPPConnection connection;
+        private PacketFilter invitationFilter;
+        private PacketListener invitationPacketListener;
+
+        /**
+         * Returns a new or existing InvitationsMonitor for a given connection.
+         * 
+         * @param conn the connection to monitor for room invitations.
+         * @return a new or existing InvitationsMonitor for a given connection.
+         */
+        public static InvitationsMonitor getInvitationsMonitor(XMPPConnection conn) {
+            synchronized (monitors) {
+                if (!monitors.containsKey(conn)) {
+                    // We need to use a WeakReference because the monitor references the 
+                    // connection and this could prevent the GC from collecting the monitor
+                    // when no other object references the monitor
+                    monitors.put(conn, new WeakReference(new InvitationsMonitor(conn)));
+                }
+                // Return the InvitationsMonitor that monitors the connection
+                return (InvitationsMonitor) ((WeakReference) monitors.get(conn)).get();
+            }
+        }
+
+        /**
+         * Creates a new InvitationsMonitor that will monitor invitations received
+         * on a given connection.
+         * 
+         * @param connection the connection to monitor for possible room invitations
+         */
+        private InvitationsMonitor(XMPPConnection connection) {
+            this.connection = connection;
+        }
+
+        /**
+         * Adds a listener to invitation notifications. The listener will be fired anytime 
+         * an invitation is received.<p>
+         * 
+         * If this is the first monitor's listener then the monitor will be initialized in 
+         * order to start listening to room invitations.
+         * 
+         * @param listener an invitation listener.
+         */
+        public void addInvitationListener(InvitationListener listener) {
+            synchronized (invitationsListeners) {
+                // If this is the first monitor's listener then initialize the listeners 
+                // on the connection to detect room invitations
+                if (invitationsListeners.size() == 0) {
+                    init();
+                }
+                if (!invitationsListeners.contains(listener)) {
+                    invitationsListeners.add(listener);
+                }
+            }
+        }
+
+        /**
+         * Removes a listener to invitation notifications. The listener will be fired anytime 
+         * an invitation is received.<p>
+         * 
+         * If there are no more listeners to notifiy for room invitations then the monitor will
+         * be stopped. As soon as a new listener is added to the monitor, the monitor will resume
+         * monitoring the connection for new room invitations.
+         * 
+         * @param listener an invitation listener.
+         */
+        public void removeInvitationListener(InvitationListener listener) {
+            synchronized (invitationsListeners) {
+                if (invitationsListeners.contains(listener)) {
+                    invitationsListeners.remove(listener);
+                }
+                // If there are no more listeners to notifiy for room invitations
+                // then proceed to cancel/release this monitor 
+                if (invitationsListeners.size() == 0) {
+                    cancel();
+                }
+            }
+        }
+
+        /**
+         * Fires invitation listeners.
+         */
+        private void fireInvitationListeners(String room, String inviter, String reason, String password,
+                                             Message message) {
+            InvitationListener[] listeners = null;
+            synchronized (invitationsListeners) {
+                listeners = new InvitationListener[invitationsListeners.size()];
+                invitationsListeners.toArray(listeners);
+            }
+            for (int i = 0; i < listeners.length; i++) {
+                listeners[i].invitationReceived(connection, room, inviter, reason, password, message);
+            }
+        }
+
+        public void connectionClosed() {
+            cancel();
+        }
+
+        public void connectionClosedOnError(Exception e) {
+            cancel();
+        }
+
+        /**
+         * Initializes the listeners to detect received room invitations and to detect when the 
+         * connection gets closed. As soon as a room invitation is received the invitations 
+         * listeners will be fired. When the connection gets closed the monitor will remove
+         * his listeners on the connection.       
+         */
+        private void init() {
+            // Listens for all messages that include a MUCUser extension and fire the invitation 
+            // listeners if the message includes an invitation.
+            invitationFilter =
+                new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user");
+            invitationPacketListener = new PacketListener() {
+                public void processPacket(Packet packet) {
+                    // Get the MUCUser extension
+                    MUCUser mucUser = 
+                        (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user");
+                    // Check if the MUCUser extension includes an invitation
+                    if (mucUser.getInvite() != null) {
+                        // Fire event for invitation listeners
+                        fireInvitationListeners(packet.getFrom(), mucUser.getInvite().getFrom(),
+                                mucUser.getInvite().getReason(), mucUser.getPassword(), (Message) packet);
+                    }
+                };
+            };
+            connection.addPacketListener(invitationPacketListener, invitationFilter);
+            // Add a listener to detect when the connection gets closed in order to
+            // cancel/release this monitor 
+            connection.addConnectionListener(this);
+        }
+
+        /**
+         * Cancels all the listeners that this InvitationsMonitor has added to the connection.
+         */
+        private void cancel() {
+            connection.removePacketListener(invitationPacketListener);
+            connection.removeConnectionListener(this);
+        }
+
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java
new file mode 100644
index 000000000..e82de84ad
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/Occupant.java
@@ -0,0 +1,104 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smackx.packet.MUCAdmin;
+import org.jivesoftware.smackx.packet.MUCUser;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Represents the information about an occupant in a given room. The information will always have
+ * the affiliation and role of the occupant in the room. The full JID and nickname are optional.
+ *
+ * @author Gaston Dombiak
+ */
+public class Occupant {
+    // Fields that must have a value
+    private String affiliation;
+    private String role;
+    // Fields that may have a value
+    private String jid;
+    private String nick;
+
+    Occupant(MUCAdmin.Item item) {
+        super();
+        this.jid = item.getJid();
+        this.affiliation = item.getAffiliation();
+        this.role = item.getRole();
+        this.nick = item.getNick();
+    }
+
+    Occupant(Presence presence) {
+        super();
+        MUCUser mucUser = (MUCUser) presence.getExtension("x",
+                "http://jabber.org/protocol/muc#user");
+        MUCUser.Item item = mucUser.getItem();
+        this.jid = item.getJid();
+        this.affiliation = item.getAffiliation();
+        this.role = item.getRole();
+        // Get the nickname from the FROM attribute of the presence
+        this.nick = StringUtils.parseResource(presence.getFrom());
+    }
+
+    /**
+     * Returns the full JID of the occupant. If this information was extracted from a presence and
+     * the room is semi or full-anonymous then the answer will be null. On the other hand, if this
+     * information was obtained while maintaining the voice list or the moderator list then we will
+     * always have a full JID.
+     *
+     * @return the full JID of the occupant.
+     */
+    public String getJid() {
+        return jid;
+    }
+
+    /**
+     * Returns the affiliation of the occupant. Possible affiliations are: "owner", "admin",
+     * "member", "outcast". This information will always be available.
+     *
+     * @return the affiliation of the occupant.
+     */
+    public String getAffiliation() {
+        return affiliation;
+    }
+
+    /**
+     * Returns the current role of the occupant in the room. This information will always be
+     * available.
+     *
+     * @return the current role of the occupant in the room.
+     */
+    public String getRole() {
+        return role;
+    }
+
+    /**
+     * Returns the current nickname of the occupant in the room. If this information was extracted
+     * from a presence then the answer will be null.
+     *
+     * @return the current nickname of the occupant in the room or null if this information was
+     *         obtained from a presence.
+     */
+    public String getNick() {
+        return nick;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java
new file mode 100644
index 000000000..2f570a673
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/ParticipantStatusListener.java
@@ -0,0 +1,173 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * A listener that is fired anytime a participant's status in a room is changed, such as the 
+ * user being kicked, banned, or granted admin permissions.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface ParticipantStatusListener {
+
+    /**
+     * Called when a new room occupant has joined the room. Note: Take in consideration that when
+     * you join a room you will receive the list of current occupants in the room. This message will
+     * be sent for each occupant.
+     *
+     * @param participant the participant that has just joined the room
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void joined(String participant);
+
+    /**
+     * Called when a room occupant has left the room on its own. This means that the occupant was
+     * neither kicked nor banned from the room.
+     *
+     * @param participant the participant that has left the room on its own.
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void left(String participant);
+
+    /**
+     * Called when a room participant has been kicked from the room. This means that the kicked 
+     * participant is no longer participating in the room.
+     * 
+     * @param participant the participant that was kicked from the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void kicked(String participant);
+
+    /**
+     * Called when a moderator grants voice to a visitor. This means that the visitor 
+     * can now participate in the moderated room sending messages to all occupants.
+     * 
+     * @param participant the participant that was granted voice in the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void voiceGranted(String participant);
+
+    /**
+     * Called when a moderator revokes voice from a participant. This means that the participant 
+     * in the room was able to speak and now is a visitor that can't send messages to the room 
+     * occupants.
+     * 
+     * @param participant the participant that was revoked voice from the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void voiceRevoked(String participant);
+
+    /**
+     * Called when an administrator or owner banned a participant from the room. This means that 
+     * banned participant will no longer be able to join the room unless the ban has been removed.
+     * 
+     * @param participant the participant that was banned from the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void banned(String participant);
+
+    /**
+     * Called when an administrator grants a user membership to the room. This means that the user 
+     * will be able to join the members-only room.
+     * 
+     * @param participant the participant that was granted membership in the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void membershipGranted(String participant);
+
+    /**
+     * Called when an administrator revokes a user membership to the room. This means that the 
+     * user will not be able to join the members-only room.
+     * 
+     * @param participant the participant that was revoked membership from the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void membershipRevoked(String participant);
+
+    /**
+     * Called when an administrator grants moderator privileges to a user. This means that the user 
+     * will be able to kick users, grant and revoke voice, invite other users, modify room's 
+     * subject plus all the partcipants privileges.
+     * 
+     * @param participant the participant that was granted moderator privileges in the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void moderatorGranted(String participant);
+
+    /**
+     * Called when an administrator revokes moderator privileges from a user. This means that the 
+     * user will no longer be able to kick users, grant and revoke voice, invite other users, 
+     * modify room's subject plus all the partcipants privileges.
+     * 
+     * @param participant the participant that was revoked moderator privileges in the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void moderatorRevoked(String participant);
+
+    /**
+     * Called when an owner grants a user ownership on the room. This means that the user 
+     * will be able to change defining room features as well as perform all administrative 
+     * functions.
+     * 
+     * @param participant the participant that was granted ownership on the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void ownershipGranted(String participant);
+
+    /**
+     * Called when an owner revokes a user ownership on the room. This means that the user 
+     * will no longer be able to change defining room features as well as perform all 
+     * administrative functions.
+     * 
+     * @param participant the participant that was revoked ownership on the room 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void ownershipRevoked(String participant);
+
+    /**
+     * Called when an owner grants administrator privileges to a user. This means that the user 
+     * will be able to perform administrative functions such as banning users and edit moderator 
+     * list.
+     * 
+     * @param participant the participant that was granted administrator privileges 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void adminGranted(String participant);
+
+    /**
+     * Called when an owner revokes administrator privileges from a user. This means that the user 
+     * will no longer be able to perform administrative functions such as banning users and edit 
+     * moderator list.
+     * 
+     * @param participant the participant that was revoked administrator privileges 
+     * (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void adminRevoked(String participant);
+
+    /**
+     * Called when a participant changed his/her nickname in the room. The new participant's 
+     * nickname will be informed with the next available presence.
+     * 
+     * @param nickname the old nickname that the participant decided to change.
+     */
+    public abstract void nicknameChanged(String nickname);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java
new file mode 100644
index 000000000..694dd38d8
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/RoomInfo.java
@@ -0,0 +1,184 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.Form;
+
+/**
+ * Represents the room information that was discovered using Service Discovery. It's possible to
+ * obtain information about a room before joining the room but only for rooms that are public (i.e.
+ * rooms that may be discovered).
+ *
+ * @author Gaston Dombiak
+ */
+public class RoomInfo {
+
+    /**
+     * JID of the room. The node of the JID is commonly used as the ID of the room or name.
+     */
+    private String room;
+    /**
+     * Description of the room.
+     */
+    private String description = "";
+    /**
+     * Last known subject of the room.
+     */
+    private String subject = "";
+    /**
+     * Current number of occupants in the room.
+     */
+    private int occupantsCount = -1;
+    /**
+     * A room is considered members-only if an invitation is required in order to enter the room.
+     * Any user that is not a member of the room won't be able to join the room unless the user
+     * decides to register with the room (thus becoming a member).
+     */
+    private boolean membersOnly;
+    /**
+     * Moderated rooms enable only participants to speak. Users that join the room and aren't
+     * participants can't speak (they are just visitors).
+     */
+    private boolean moderated;
+    /**
+     * Every presence packet can include the JID of every occupant unless the owner deactives this
+     * configuration.
+     */
+    private boolean nonanonymous;
+    /**
+     * Indicates if users must supply a password to join the room.
+     */
+    private boolean passwordProtected;
+    /**
+     * Persistent rooms are saved to the database to make sure that rooms configurations can be
+     * restored in case the server goes down.
+     */
+    private boolean persistent;
+
+    RoomInfo(DiscoverInfo info) {
+        super();
+        this.room = info.getFrom();
+        // Get the information based on the discovered features
+        this.membersOnly = info.containsFeature("muc_membersonly");
+        this.moderated = info.containsFeature("muc_moderated");
+        this.nonanonymous = info.containsFeature("muc_nonanonymous");
+        this.passwordProtected = info.containsFeature("muc_passwordprotected");
+        this.persistent = info.containsFeature("muc_persistent");
+        // Get the information based on the discovered extended information
+        Form form = Form.getFormFrom(info);
+        if (form != null) {
+            this.description =
+                    (String) form.getField("muc#roominfo_description").getValues().next();
+            this.subject = (String) form.getField("muc#roominfo_subject").getValues().next();
+            this.occupantsCount =
+                    Integer.parseInt((String) form.getField("muc#roominfo_occupants").getValues()
+                    .next());
+        }
+    }
+
+    /**
+     * Returns the JID of the room whose information was discovered.
+     *
+     * @return the JID of the room whose information was discovered.
+     */
+    public String getRoom() {
+        return room;
+    }
+
+    /**
+     * Returns the discovered description of the room.
+     *
+     * @return the discovered description of the room.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Returns the discovered subject of the room. The subject may be empty if the room does not
+     * have a subject.
+     *
+     * @return the discovered subject of the room.
+     */
+    public String getSubject() {
+        return subject;
+    }
+
+    /**
+     * Returns the discovered number of occupants that are currently in the room. If this
+     * information was not discovered (i.e. the server didn't send it) then a value of -1 will be
+     * returned.
+     *
+     * @return the number of occupants that are currently in the room or -1 if that information was
+     * not provided by the server.
+     */
+    public int getOccupantsCount() {
+        return occupantsCount;
+    }
+
+    /**
+     * Returns true if the room has restricted the access so that only members may enter the room.
+     *
+     * @return true if the room has restricted the access so that only members may enter the room.
+     */
+    public boolean isMembersOnly() {
+        return membersOnly;
+    }
+
+    /**
+     * Returns true if the room enabled only participants to speak. Occupants with a role of
+     * visitor won't be able to speak in the room.
+     *
+     * @return true if the room enabled only participants to speak.
+     */
+    public boolean isModerated() {
+        return moderated;
+    }
+
+    /**
+     * Returns true if presence packets will include the JID of every occupant.
+     *
+     * @return true if presence packets will include the JID of every occupant.
+     */
+    public boolean isNonanonymous() {
+        return nonanonymous;
+    }
+
+    /**
+     * Returns true if users musy provide a valid password in order to join the room.
+     *
+     * @return true if users musy provide a valid password in order to join the room.
+     */
+    public boolean isPasswordProtected() {
+        return passwordProtected;
+    }
+
+    /**
+     * Returns true if the room will persist after the last occupant have left the room.
+     *
+     * @return true if the room will persist after the last occupant have left the room.
+     */
+    public boolean isPersistent() {
+        return persistent;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java
new file mode 100644
index 000000000..a966d7f19
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java
@@ -0,0 +1,38 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * A listener that is fired anytime a MUC room changes its subject.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface SubjectUpdatedListener {
+
+    /**
+     * Called when a MUC room has changed its subject.
+     * 
+     * @param subject the new room's subject.
+     * @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick).
+     */
+    public abstract void subjectUpdated(String subject, String from);
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java b/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java
new file mode 100644
index 000000000..6d243419a
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/UserStatusListener.java
@@ -0,0 +1,127 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+/**
+ * A listener that is fired anytime your participant's status in a room is changed, such as the 
+ * user being kicked, banned, or granted admin permissions.
+ * 
+ * @author Gaston Dombiak
+ */
+public interface UserStatusListener {
+
+    /**
+     * Called when a moderator kicked your user from the room. This means that you are no longer
+     * participanting in the room.
+     * 
+     * @param actor the moderator that kicked your user from the room (e.g. user@host.org).
+     * @param reason the reason provided by the actor to kick you from the room.
+     */
+    public abstract void kicked(String actor, String reason);
+
+    /**
+     * Called when a moderator grants voice to your user. This means that you were a visitor in 
+     * the moderated room before and now you can participate in the room by sending messages to 
+     * all occupants.
+     * 
+     */
+    public abstract void voiceGranted();
+
+    /**
+     * Called when a moderator revokes voice from your user. This means that you were a 
+     * participant in the room able to speak and now you are a visitor that can't send 
+     * messages to the room occupants.
+     * 
+     */
+    public abstract void voiceRevoked();
+
+    /**
+     * Called when an administrator or owner banned your user from the room. This means that you 
+     * will no longer be able to join the room unless the ban has been removed.
+     * 
+     * @param actor the administrator that banned your user (e.g. user@host.org).
+     * @param reason the reason provided by the administrator to banned you.
+     */
+    public abstract void banned(String actor, String reason);
+
+    /**
+     * Called when an administrator grants your user membership to the room. This means that you 
+     * will be able to join the members-only room. 
+     * 
+     */
+    public abstract void membershipGranted();
+
+    /**
+     * Called when an administrator revokes your user membership to the room. This means that you 
+     * will not be able to join the members-only room.
+     * 
+     */
+    public abstract void membershipRevoked();
+
+    /**
+     * Called when an administrator grants moderator privileges to your user. This means that you 
+     * will be able to kick users, grant and revoke voice, invite other users, modify room's 
+     * subject plus all the partcipants privileges.
+     * 
+     */
+    public abstract void moderatorGranted();
+
+    /**
+     * Called when an administrator revokes moderator privileges from your user. This means that 
+     * you will no longer be able to kick users, grant and revoke voice, invite other users, 
+     * modify room's subject plus all the partcipants privileges.
+     * 
+     */
+    public abstract void moderatorRevoked();
+
+    /**
+     * Called when an owner grants to your user ownership on the room. This means that you 
+     * will be able to change defining room features as well as perform all administrative 
+     * functions.
+     * 
+     */
+    public abstract void ownershipGranted();
+
+    /**
+     * Called when an owner revokes from your user ownership on the room. This means that you 
+     * will no longer be able to change defining room features as well as perform all 
+     * administrative functions.
+     * 
+     */
+    public abstract void ownershipRevoked();
+
+    /**
+     * Called when an owner grants administrator privileges to your user. This means that you 
+     * will be able to perform administrative functions such as banning users and edit moderator 
+     * list.
+     * 
+     */
+    public abstract void adminGranted();
+
+    /**
+     * Called when an owner revokes administrator privileges from your user. This means that you 
+     * will no longer be able to perform administrative functions such as banning users and edit 
+     * moderator list.
+     * 
+     */
+    public abstract void adminRevoked();
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html
new file mode 100644
index 000000000..dcfaeaace
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/muc/package.html
@@ -0,0 +1 @@
+<body>Classes and Interfaces that implement Multi-User Chat (MUC).</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/package.html
new file mode 100644
index 000000000..d574a2a4d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/package.html
@@ -0,0 +1 @@
+<body>Smack extensions API.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java
new file mode 100644
index 000000000..82b2939cc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DataForm.java
@@ -0,0 +1,296 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.FormField;
+
+/**
+ * Represents a form that could be use for gathering data as well as for reporting data
+ * returned from a search.
+ *
+ * @author Gaston Dombiak
+ */
+public class DataForm implements PacketExtension {
+
+    private String type;
+    private String title;
+    private List instructions = new ArrayList();
+    private ReportedData reportedData;
+    private List items = new ArrayList();
+    private List fields = new ArrayList();
+    
+    public DataForm(String type) {
+        this.type = type;
+    }
+    
+    /**
+     * Returns the meaning of the data within the context. The data could be part of a form
+     * to fill out, a form submission or data results.<p>
+     * 
+     * Possible form types are:
+     * <ul>
+     *  <li>form -> This packet contains a form to fill out. Display it to the user (if your 
+     * program can).</li>
+     *  <li>submit -> The form is filled out, and this is the data that is being returned from 
+     * the form.</li>
+     *  <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
+     *  <li>result -> Data results being returned from a search, or some other query.</li>
+     * </ul>
+     * 
+     * @return the form's type.
+     */
+    public String getType() {
+        return type; 
+    }
+    
+    /**
+     * Returns the description of the data. It is similar to the title on a web page or an X 
+     * window.  You can put a <title/> on either a form to fill out, or a set of data results.
+     * 
+     * @return description of the data.
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * Returns an Iterator for the list of instructions that explain how to fill out the form and 
+     * what the form is about. The dataform could include multiple instructions since each 
+     * instruction could not contain newlines characters. Join the instructions together in order 
+     * to show them to the user.    
+     * 
+     * @return an Iterator for the list of instructions that explain how to fill out the form.
+     */
+    public Iterator getInstructions() {
+        synchronized (instructions) {
+            return Collections.unmodifiableList(new ArrayList(instructions)).iterator();
+        }
+    }
+
+    /**
+     * Returns the fields that will be returned from a search.
+     * 
+     * @return fields that will be returned from a search.
+     */
+    public ReportedData getReportedData() {
+        return reportedData;
+    }
+
+    /**
+     * Returns an Iterator for the items returned from a search.
+     *
+     * @return an Iterator for the items returned from a search.
+     */
+    public Iterator getItems() {
+        synchronized (items) {
+            return Collections.unmodifiableList(new ArrayList(items)).iterator();
+        }
+    }
+
+    /**
+     * Returns an Iterator for the fields that are part of the form.
+     *
+     * @return an Iterator for the fields that are part of the form.
+     */
+    public Iterator getFields() {
+        synchronized (fields) {
+            return Collections.unmodifiableList(new ArrayList(fields)).iterator();
+        }
+    }
+
+    public String getElementName() {
+        return "x";
+    }
+
+    public String getNamespace() {
+        return "jabber:x:data";
+    }
+
+    /**
+     * Sets the description of the data. It is similar to the title on a web page or an X window.
+     * You can put a <title/> on either a form to fill out, or a set of data results.
+     * 
+     * @param title description of the data.
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * Sets the list of instructions that explain how to fill out the form and what the form is 
+     * about. The dataform could include multiple instructions since each instruction could not 
+     * contain newlines characters. 
+     * 
+     * @param instructions list of instructions that explain how to fill out the form.
+     */
+    public void setInstructions(List instructions) {
+        this.instructions = instructions;
+    }
+
+    /**
+     * Sets the fields that will be returned from a search.
+     * 
+     * @param reportedData the fields that will be returned from a search.
+     */
+    public void setReportedData(ReportedData reportedData) {
+        this.reportedData = reportedData;
+    }
+
+    /**
+     * Adds a new field as part of the form.
+     * 
+     * @param field the field to add to the form.
+     */
+    public void addField(FormField field) {
+        synchronized (fields) {
+            fields.add(field);
+        }
+    }
+    
+    /**
+     * Adds a new instruction to the list of instructions that explain how to fill out the form 
+     * and what the form is about. The dataform could include multiple instructions since each 
+     * instruction could not contain newlines characters. 
+     * 
+     * @param instruction the new instruction that explain how to fill out the form.
+     */
+    public void addInstruction(String instruction) {
+        synchronized (instructions) {
+            instructions.add(instruction);
+        }
+    }
+
+    /**
+     * Adds a new item returned from a search.
+     * 
+     * @param item the item returned from a search.
+     */
+    public void addItem(Item item) {
+        synchronized (items) {
+            items.add(item);
+        }
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\" type=\"" + getType() +"\">");
+        if (getTitle() != null) {
+            buf.append("<title>").append(getTitle()).append("</title>");
+        }
+        for (Iterator it=getInstructions(); it.hasNext();) {
+            buf.append("<instructions>").append(it.next()).append("</instructions>");
+        }
+        // Append the list of fields returned from a search
+        if (getReportedData() != null) {
+            buf.append(getReportedData().toXML());
+        }
+        // Loop through all the items returned from a search and append them to the string buffer
+        for (Iterator i = getItems(); i.hasNext();) {
+            Item item = (Item) i.next();
+            buf.append(item.toXML());
+        }
+        // Loop through all the form fields and append them to the string buffer
+        for (Iterator i = getFields(); i.hasNext();) {
+            FormField field = (FormField) i.next();
+            buf.append(field.toXML());
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * 
+     * Represents the fields that will be returned from a search. This information is useful when 
+     * you try to use the jabber:iq:search namespace to return dynamic form information.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class ReportedData {
+        private List fields = new ArrayList();
+        
+        public ReportedData(List fields) {
+            this.fields = fields;
+        }
+        
+        /**
+         * Returns the fields returned from a search.
+         * 
+         * @return the fields returned from a search.
+         */
+        public Iterator getFields() {
+            return Collections.unmodifiableList(new ArrayList(fields)).iterator();
+        }
+        
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<reported>");
+            // Loop through all the form items and append them to the string buffer
+            for (Iterator i = getFields(); i.hasNext();) {
+                FormField field = (FormField) i.next();
+                buf.append(field.toXML());
+            }
+            buf.append("</reported>");
+            return buf.toString();
+        }
+    }
+    
+    /**
+     * 
+     * Represents items of reported data.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Item {
+        private List fields = new ArrayList();
+        
+        public Item(List fields) {
+            this.fields = fields;
+        }
+        
+        /**
+         * Returns the fields that define the data that goes with the item.
+         * 
+         * @return the fields that define the data that goes with the item.
+         */
+        public Iterator getFields() {
+            return Collections.unmodifiableList(new ArrayList(fields)).iterator();
+        }
+        
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item>");
+            // Loop through all the form items and append them to the string buffer
+            for (Iterator i = getFields(); i.hasNext();) {
+                FormField field = (FormField) i.next();
+                buf.append(field.toXML());
+            }
+            buf.append("</item>");
+            return buf.toString();
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java
new file mode 100644
index 000000000..c000d2eed
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DefaultPrivateData.java
@@ -0,0 +1,137 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * Default implementation of the PrivateData interface. Unless a PrivateDataProvider
+ * is registered with the PrivateDataManager class, instances of this class will be
+ * returned when getting private data.<p>
+ *
+ * This class provides a very simple representation of an XML sub-document. Each element
+ * is a key in a Map with its CDATA being the value. For example, given the following
+ * XML sub-document:
+ *
+ * <pre>
+ * &lt;foo xmlns="http://bar.com"&gt;
+ *     &lt;color&gt;blue&lt;/color&gt;
+ *     &lt;food&gt;pizza&lt;/food&gt;
+ * &lt;/foo&gt;</pre>
+ *
+ * In this case, getValue("color") would return "blue", and getValue("food") would
+ * return "pizza". This parsing mechanism mechanism is very simplistic and will not work
+ * as desired in all cases (for example, if some of the elements have attributes. In those
+ * cases, a custom {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used.
+ *
+ * @author Matt Tucker
+ */
+public class DefaultPrivateData implements PrivateData {
+
+    private String elementName;
+    private String namespace;
+    private Map map;
+
+    /**
+     * Creates a new generic private data object.
+     *
+     * @param elementName the name of the element of the XML sub-document.
+     * @param namespace the namespace of the element.
+     */
+    public DefaultPrivateData(String elementName, String namespace) {
+        this.elementName = elementName;
+        this.namespace = namespace;
+    }
+
+     /**
+     * Returns the XML element name of the private data sub-packet root element.
+     *
+     * @return the XML element name of the packet extension.
+     */
+    public String getElementName() {
+        return elementName;
+    }
+
+    /**
+     * Returns the XML namespace of the private data sub-packet root element.
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return namespace;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">");
+        for (Iterator i=getNames(); i.hasNext(); ) {
+            String name = (String)i.next();
+            String value = getValue(name);
+            buf.append("<").append(name).append(">");
+            buf.append(value);
+            buf.append("</").append(name).append(">");
+        }
+        buf.append("</").append(elementName).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * Returns an Iterator for the names that can be used to get
+     * values of the private data.
+     *
+     * @return an Iterator for the names.
+     */
+    public synchronized Iterator getNames() {
+        if (map == null) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator();
+    }
+
+    /**
+     * Returns a value given a name.
+     *
+     * @param name the name.
+     * @return the value.
+     */
+    public synchronized String getValue(String name) {
+        if (map == null) {
+            return null;
+        }
+        return (String)map.get(name);
+    }
+
+    /**
+     * Sets a value given the name.
+     *
+     * @param name the name.
+     * @param value the value.
+     */
+    public synchronized void setValue(String name, String value) {
+        if (map == null) {
+            map = new HashMap();
+        }
+        map.put(name, value);
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java
new file mode 100644
index 000000000..e9e90331a
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DelayInformation.java
@@ -0,0 +1,142 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents timestamp information about data stored for later delivery. A DelayInformation will 
+ * always includes the timestamp when the packet was originally sent and may include more 
+ * information such as the JID of the entity that originally sent the packet as well as the reason
+ * for the dealy.<p>
+ * 
+ * For more information see <a href="http://www.jabber.org/jeps/jep-0091.html">JEP-91</a>.
+ * 
+ * @author Gaston Dombiak
+ */
+public class DelayInformation implements PacketExtension {
+
+    public static SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+    /**
+     * New date format based on JEP-82 that some clients may use when sending delayed dates.
+     * JEP-91 is using a SHOULD other servers or clients may be using this format instead of the
+     * old UTC format.
+     */
+    public static SimpleDateFormat NEW_UTC_FORMAT =
+            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
+    static {
+        UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
+        NEW_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    private Date stamp;
+    private String from;
+    private String reason;
+
+    /**
+     * Creates a new instance with the specified timestamp. 
+     */
+    public DelayInformation(Date stamp) {
+        super();
+        this.stamp = stamp;
+    }
+
+    /**
+     * Returns the JID of the entity that originally sent the packet or that delayed the 
+     * delivery of the packet or <tt>null</tt> if this information is not available.
+     * 
+     * @return the JID of the entity that originally sent the packet or that delayed the 
+     *         delivery of the packet.
+     */
+    public String getFrom() {
+        return from;
+    }
+
+    /**
+     * Sets the JID of the entity that originally sent the packet or that delayed the 
+     * delivery of the packet or <tt>null</tt> if this information is not available.
+     * 
+     * @param from the JID of the entity that originally sent the packet.
+     */
+    public void setFrom(String from) {
+        this.from = from;
+    }
+
+    /**
+     * Returns the timstamp when the packet was originally sent. The returned Date is 
+     * be understood as UTC.
+     * 
+     * @return the timstamp when the packet was originally sent.
+     */
+    public Date getStamp() {
+        return stamp;
+    }
+
+    /**
+     * Returns a natural-language description of the reason for the delay or <tt>null</tt> if 
+     * this information is not available.
+     * 
+     * @return a natural-language description of the reason for the delay or <tt>null</tt>.
+     */
+    public String getReason() {
+        return reason;
+    }
+
+    /**
+     * Sets a natural-language description of the reason for the delay or <tt>null</tt> if 
+     * this information is not available.
+     * 
+     * @param reason a natural-language description of the reason for the delay or <tt>null</tt>.
+     */
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getElementName() {
+        return "x";
+    }
+
+    public String getNamespace() {
+        return "jabber:x:delay";
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+                "\"");
+        buf.append(" stamp=\"").append(UTC_FORMAT.format(stamp)).append("\"");
+        if (from != null && from.length() > 0) {
+            buf.append(" from=\"").append(from).append("\"");
+        }
+        buf.append(">");
+        if (reason != null && reason.length() > 0) {
+            buf.append(reason);
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java
new file mode 100644
index 000000000..0694a0ceb
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverInfo.java
@@ -0,0 +1,268 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information 
+ * to/from other XMPP entities.<p> 
+ * 
+ * The received information may contain one or more identities of the requested XMPP entity, and 
+ * a list of supported features by the requested XMPP entity.
+ *
+ * @author Gaston Dombiak
+ */
+public class DiscoverInfo extends IQ {
+
+    private List features = new ArrayList();
+    private List identities = new ArrayList();
+    private String node;
+
+    /**
+     * Adds a new feature to the discovered information.
+     *
+     * @param feature the discovered feature
+     */
+    public void addFeature(String feature) {
+        addFeature(new DiscoverInfo.Feature(feature));
+    }
+
+    private void addFeature(Feature feature) {
+        synchronized (features) {
+            features.add(feature);
+        }
+    }
+
+    /**
+     * Returns the discovered features of an XMPP entity.
+     *
+     * @return an Iterator on the discovered features of an XMPP entity
+     */
+    Iterator getFeatures() {
+        synchronized (features) {
+            return Collections.unmodifiableList(new ArrayList(features)).iterator();
+        }
+    }
+
+    /**
+     * Adds a new identity of the requested entity to the discovered information.
+     * 
+     * @param identity the discovered entity's identity
+     */
+    public void addIdentity(Identity identity) {
+        synchronized (identities) {
+            identities.add(identity);
+        }
+    }
+
+    /**
+     * Returns the discovered identities of an XMPP entity.
+     * 
+     * @return an Iterator on the discoveted identities 
+     */
+    public Iterator getIdentities() {
+        synchronized (identities) {
+            return Collections.unmodifiableList(new ArrayList(identities)).iterator();
+        }
+    }
+
+    /**
+     * Returns the node attribute that supplements the 'jid' attribute. A node is merely 
+     * something that is associated with a JID and for which the JID can provide information.<p> 
+     * 
+     * Node attributes SHOULD be used only when trying to provide or query information which 
+     * is not directly addressable.
+     *
+     * @return the node attribute that supplements the 'jid' attribute
+     */
+    public String getNode() {
+        return node;
+    }
+
+    /**
+     * Sets the node attribute that supplements the 'jid' attribute. A node is merely 
+     * something that is associated with a JID and for which the JID can provide information.<p> 
+     * 
+     * Node attributes SHOULD be used only when trying to provide or query information which 
+     * is not directly addressable.
+     * 
+     * @param node the node attribute that supplements the 'jid' attribute
+     */
+    public void setNode(String node) {
+        this.node = node;
+    }
+
+    /**
+     * Returns true if the specified feature is part of the discovered information.
+     * 
+     * @param feature the feature to check
+     * @return true if the requestes feature has been discovered
+     */
+    public boolean containsFeature(String feature) {
+        for (Iterator it = getFeatures(); it.hasNext();) {
+            if (feature.equals(((DiscoverInfo.Feature) it.next()).getVar()))
+                return true;
+        }
+        return false;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"http://jabber.org/protocol/disco#info\"");
+        if (getNode() != null) {
+            buf.append(" node=\"");
+            buf.append(getNode());
+            buf.append("\"");
+        }
+        buf.append(">");
+        synchronized (identities) {
+            for (int i = 0; i < identities.size(); i++) {
+                Identity identity = (Identity) identities.get(i);
+                buf.append(identity.toXML());
+            }
+        }
+        synchronized (features) {
+            for (int i = 0; i < features.size(); i++) {
+                Feature feature = (Feature) features.get(i);
+                buf.append(feature.toXML());
+            }
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</query>");
+        return buf.toString();
+    }
+
+    /**
+     * Represents the identity of a given XMPP entity. An entity may have many identities but all
+     * the identities SHOULD have the same name.<p>
+     * 
+     * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
+     * in order to get the official registry of values for the <i>category</i> and <i>type</i> 
+     * attributes.
+     * 
+     */
+    public static class Identity {
+
+        private String category;
+        private String name;
+        private String type;
+
+        /**
+         * Creates a new identity for an XMPP entity.
+         * 
+         * @param category the entity's category.
+         * @param name the entity's name.
+         */
+        public Identity(String category, String name) {
+            this.category = category;
+            this.name = name;
+        }
+
+        /**
+         * Returns the entity's category. To get the official registry of values for the 
+         * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 
+         *
+         * @return the entity's category.
+         */
+        public String getCategory() {
+            return category;
+        }
+
+        /**
+         * Returns the identity's name.
+         *
+         * @return the identity's name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Returns the entity's type. To get the official registry of values for the 
+         * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 
+         *
+         * @return the entity's type.
+         */
+        public String getType() {
+            return type;
+        }
+
+        /**
+         * Sets the entity's type. To get the official registry of values for the 
+         * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a> 
+         *
+         * @param type the identity's type.
+         */
+        public void setType(String type) {
+            this.type = type;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<identity category=\"").append(category).append("\"");
+            buf.append(" name=\"").append(name).append("\"");
+            if (type != null) {
+                buf.append(" type=\"").append(type).append("\"");
+            }
+            buf.append("/>");
+            return buf.toString();
+        }
+    }
+
+    /**
+     * Represents the features offered by the item. This information helps requestors determine 
+     * what actions are possible with regard to this item (registration, search, join, etc.) 
+     * as well as specific feature types of interest, if any (e.g., for the purpose of feature 
+     * negotiation).
+     */
+    public static class Feature {
+
+        private String variable;
+
+        /**
+         * Creates a new feature offered by an XMPP entity or item.
+         * 
+         * @param variable the feature's variable.
+         */
+        public Feature(String variable) {
+            this.variable = variable;
+        }
+
+        /**
+         * Returns the feature's variable.
+         *
+         * @return the feature's variable.
+         */
+        public String getVar() {
+            return variable;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<feature var=\"").append(variable).append("\"/>");
+            return buf.toString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java
new file mode 100644
index 000000000..0c264aea9
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/DiscoverItems.java
@@ -0,0 +1,235 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items 
+ * associated with XMPP entities.<p>
+ * 
+ * The items could also be queried in order to discover if they contain items inside. Some items 
+ * may be addressable by its JID and others may require to be addressed by a JID and a node name.
+ *
+ * @author Gaston Dombiak
+ */
+public class DiscoverItems extends IQ {
+
+    private List items = new ArrayList();
+    private String node;
+
+    /**
+     * Adds a new item to the discovered information.
+     * 
+     * @param item the discovered entity's item
+     */
+    public void addItem(Item item) {
+        synchronized (items) {
+            items.add(item);
+        }
+    }
+
+    /**
+     * Returns the discovered items of the queried XMPP entity. 
+     *
+     * @return an Iterator on the discovered entity's items
+     */
+    public Iterator getItems() {
+        synchronized (items) {
+            return Collections.unmodifiableList(new ArrayList(items)).iterator();
+        }
+    }
+
+    /**
+     * Returns the node attribute that supplements the 'jid' attribute. A node is merely 
+     * something that is associated with a JID and for which the JID can provide information.<p> 
+     * 
+     * Node attributes SHOULD be used only when trying to provide or query information which 
+     * is not directly addressable.
+     *
+     * @return the node attribute that supplements the 'jid' attribute
+     */
+    public String getNode() {
+        return node;
+    }
+
+    /**
+     * Sets the node attribute that supplements the 'jid' attribute. A node is merely 
+     * something that is associated with a JID and for which the JID can provide information.<p> 
+     * 
+     * Node attributes SHOULD be used only when trying to provide or query information which 
+     * is not directly addressable.
+     * 
+     * @param node the node attribute that supplements the 'jid' attribute
+     */
+    public void setNode(String node) {
+        this.node = node;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"http://jabber.org/protocol/disco#items\"");
+        if (getNode() != null) {
+            buf.append(" node=\"");
+            buf.append(getNode());
+            buf.append("\"");
+        }
+        buf.append(">");
+        synchronized (items) {
+            for (int i = 0; i < items.size(); i++) {
+                Item item = (Item) items.get(i);
+                buf.append(item.toXML());
+            }
+        }
+        buf.append("</query>");
+        return buf.toString();
+    }
+
+    /**
+     * An item is associated with an XMPP Entity, usually thought of a children of the parent 
+     * entity and normally are addressable as a JID.<p> 
+     * 
+     * An item associated with an entity may not be addressable as a JID. In order to handle 
+     * such items, Service Discovery uses an optional 'node' attribute that supplements the 
+     * 'jid' attribute.
+     */
+    public static class Item {
+
+        /**
+         * Request to create or update the item.
+         */
+        public static final String UPDATE_ACTION = "update";
+
+        /**
+         * Request to remove the item.
+         */
+        public static final String REMOVE_ACTION = "remove";
+
+        private String entityID;
+        private String name;
+        private String node;
+        private String action;
+
+        /**
+         * Create a new Item associated with a given entity.
+         * 
+         * @param entityID the id of the entity that contains the item
+         */
+        public Item(String entityID) {
+            this.entityID = entityID;
+        }
+
+        /**
+         * Returns the entity's ID.
+         *
+         * @return the entity's ID.
+         */
+        public String getEntityID() {
+            return entityID;
+        }
+
+        /**
+         * Returns the entity's name.
+         *
+         * @return the entity's name.
+         */
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the entity's name.
+         *
+         * @param name the entity's name.
+         */
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        /**
+         * Returns the node attribute that supplements the 'jid' attribute. A node is merely 
+         * something that is associated with a JID and for which the JID can provide information.<p> 
+         * 
+         * Node attributes SHOULD be used only when trying to provide or query information which 
+         * is not directly addressable.
+         *
+         * @return the node attribute that supplements the 'jid' attribute
+         */
+        public String getNode() {
+            return node;
+        }
+
+        /**
+         * Sets the node attribute that supplements the 'jid' attribute. A node is merely 
+         * something that is associated with a JID and for which the JID can provide information.<p> 
+         * 
+         * Node attributes SHOULD be used only when trying to provide or query information which 
+         * is not directly addressable.
+         * 
+         * @param node the node attribute that supplements the 'jid' attribute
+         */
+        public void setNode(String node) {
+            this.node = node;
+        }
+
+        /**
+         * Returns the action that specifies the action being taken for this item. Possible action 
+         * values are: "update" and "remove". Update should either create a new entry if the node 
+         * and jid combination does not already exist, or simply update an existing entry. If 
+         * "remove" is used as the action, the item should be removed from persistent storage.
+         *  
+         * @return the action being taken for this item
+         */
+        public String getAction() {
+            return action;
+        }
+
+        /**
+         * Sets the action that specifies the action being taken for this item. Possible action 
+         * values are: "update" and "remove". Update should either create a new entry if the node 
+         * and jid combination does not already exist, or simply update an existing entry. If 
+         * "remove" is used as the action, the item should be removed from persistent storage.
+         * 
+         * @param action the action being taken for this item
+         */
+        public void setAction(String action) {
+            this.action = action;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item jid=\"").append(entityID).append("\"");
+            if (name != null) {
+                buf.append(" name=\"").append(name).append("\"");
+            }
+            if (node != null) {
+                buf.append(" node=\"").append(node).append("\"");
+            }
+            if (action != null) {
+                buf.append(" action=\"").append(action).append("\"");
+            }
+            buf.append("/>");
+            return buf.toString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java
new file mode 100644
index 000000000..f1e877296
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCAdmin.java
@@ -0,0 +1,234 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+import java.util.*;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * IQ packet that serves for kicking users, granting and revoking voice, banning users, 
+ * modifying the ban list, granting and revoking membership and granting and revoking 
+ * moderator privileges. All these operations are scoped by the 
+ * 'http://jabber.org/protocol/muc#admin' namespace.
+ * 
+ * @author Gaston Dombiak
+ */
+public class MUCAdmin extends IQ {
+
+    private List items = new ArrayList();
+
+    /**
+     * Returns an Iterator for item childs that holds information about roles, affiliation, 
+     * jids and nicks.
+     * 
+     * @return an Iterator for item childs that holds information about roles, affiliation,
+     *          jids and nicks.
+     */
+    public Iterator getItems() {
+        synchronized (items) {
+            return Collections.unmodifiableList(new ArrayList(items)).iterator();
+        }
+    }
+
+    /**
+     * Adds an item child that holds information about roles, affiliation, jids and nicks.
+     * 
+     * @param item the item child that holds information about roles, affiliation, jids and nicks.
+     */
+    public void addItem(Item item) {
+        synchronized (items) {
+            items.add(item);
+        }
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"http://jabber.org/protocol/muc#admin\">");
+        synchronized (items) {
+            for (int i = 0; i < items.size(); i++) {
+                Item item = (Item) items.get(i);
+                buf.append(item.toXML());
+            }
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</query>");
+        return buf.toString();
+    }
+
+    /**
+     * Item child that holds information about roles, affiliation, jids and nicks.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Item {
+        private String actor;
+        private String reason;
+        private String affiliation;
+        private String jid;
+        private String nick;
+        private String role;
+        
+        /**
+         * Creates a new item child. 
+         * 
+         * @param affiliation the actor's affiliation to the room
+         * @param role the privilege level of an occupant within a room.
+         */
+        public Item(String affiliation, String role) {
+            this.affiliation = affiliation;
+            this.role = role;
+        }
+        
+        /**
+         * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @return the JID of an occupant in the room that was kicked or banned.
+         */
+        public String getActor() {
+            return actor;
+        }
+
+        /**
+         * Returns the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         *  
+         * @return the reason for the item child.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent 
+         * association or connection with a room. The possible affiliations are "owner", "admin", 
+         * "member", and "outcast" (naturally it is also possible to have no affiliation). An 
+         * affiliation lasts across a user's visits to a room.
+         * 
+         * @return the actor's affiliation to the room
+         */
+        public String getAffiliation() {
+            return affiliation;
+        }
+
+        /**
+         * Returns the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item. 
+         * 
+         * @return the room JID by which an occupant is identified within the room.
+         */
+        public String getJid() {
+            return jid;
+        }
+
+        /**
+         * Returns the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.  
+         * 
+         * @return the new nickname of an occupant that is changing his/her nickname.
+         */
+        public String getNick() {
+            return nick;
+        }
+
+        /**
+         * Returns the temporary position or privilege level of an occupant within a room. The 
+         * possible roles are "moderator", "participant", and "visitor" (it is also possible to 
+         * have no defined role). A role lasts only for the duration of an occupant's visit to 
+         * a room. 
+         * 
+         * @return the privilege level of an occupant within a room.
+         */
+        public String getRole() {
+            return role;
+        }
+
+        /**
+         * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+         */
+        public void setActor(String actor) {
+            this.actor = actor;
+        }
+
+        /**
+         * Sets the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         * 
+         * @param reason the reason why a user (occupant) was kicked or banned.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        /**
+         * Sets the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item.
+         *  
+         * @param jid the JID by which an occupant is identified within a room.
+         */
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        /**
+         * Sets the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.
+         *   
+         * @param nick the new nickname of an occupant that is changing his/her nickname.
+         */
+        public void setNick(String nick) {
+            this.nick = nick;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item");
+            if (getAffiliation() != null) {
+                buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+            }
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getNick() != null) {
+                buf.append(" nick=\"").append(getNick()).append("\"");
+            }
+            if (getRole() != null) {
+                buf.append(" role=\"").append(getRole()).append("\"");
+            }
+            if (getReason() == null && getActor() == null) {
+                buf.append("/>");
+            }
+            else {
+                buf.append(">");
+                if (getReason() != null) {
+                    buf.append("<reason>").append(getReason()).append("</reason>");
+                }
+                if (getActor() != null) {
+                    buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+                }
+                buf.append("</item>");
+            }
+            return buf.toString();
+        }
+    };
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java
new file mode 100644
index 000000000..88ba7f29b
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCInitialPresence.java
@@ -0,0 +1,223 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents extended presence information whose sole purpose is to signal the ability of 
+ * the occupant to speak the MUC protocol when joining a room. If the room requires a password 
+ * then the MUCInitialPresence should include one.<p>
+ * 
+ * The amount of discussion history provided on entering a room (perhaps because the 
+ * user is on a low-bandwidth connection or is using a small-footprint client) could be managed by
+ * setting a configured History instance to the MUCInitialPresence instance. 
+ * @see MUCInitialPresence#setHistory(MUCInitialPresence.History).
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCInitialPresence implements PacketExtension {
+
+    private String password;
+    private History history; 
+
+    public String getElementName() {
+        return "x";
+    }
+
+    public String getNamespace() {
+        return "http://jabber.org/protocol/muc";
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        if (getPassword() != null) {
+            buf.append("<password>").append(getPassword()).append("</password>");
+        }
+        if (getHistory() != null) {
+            buf.append(getHistory().toXML());
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * Returns the history that manages the amount of discussion history provided on 
+     * entering a room.
+     * 
+     * @return the history that manages the amount of discussion history provided on 
+     * entering a room.
+     */
+    public History getHistory() {
+        return history;
+    }
+
+    /**
+     * Returns the password to use when the room requires a password.
+     * 
+     * @return the password to use when the room requires a password.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the History that manages the amount of discussion history provided on 
+     * entering a room.
+     * 
+     * @param history that manages the amount of discussion history provided on 
+     * entering a room.
+     */
+    public void setHistory(History history) {
+        this.history = history;
+    }
+
+    /**
+     * Sets the password to use when the room requires a password.
+     * 
+     * @param password the password to use when the room requires a password.
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * The History class controls the number of characters or messages to receive
+     * when entering a room.
+     * 
+     * @author Gaston Dombiak
+     */
+    public static class History {
+
+        private int maxChars = -1;
+        private int maxStanzas = -1; 
+        private int seconds = -1; 
+        private Date since; 
+
+        /**
+         * Returns the total number of characters to receive in the history.
+         * 
+         * @return total number of characters to receive in the history.
+         */
+        public int getMaxChars() {
+            return maxChars;
+        }
+
+        /**
+         * Returns the total number of messages to receive in the history.
+         * 
+         * @return the total number of messages to receive in the history.
+         */
+        public int getMaxStanzas() {
+            return maxStanzas;
+        }
+
+        /**
+         * Returns the number of seconds to use to filter the messages received during that time. 
+         * In other words, only the messages received in the last "X" seconds will be included in 
+         * the history.
+         * 
+         * @return the number of seconds to use to filter the messages received during that time.
+         */
+        public int getSeconds() {
+            return seconds;
+        }
+
+        /**
+         * Returns the since date to use to filter the messages received during that time. 
+         * In other words, only the messages received since the datetime specified will be 
+         * included in the history.
+         * 
+         * @return the since date to use to filter the messages received during that time.
+         */
+        public Date getSince() {
+            return since;
+        }
+
+        /**
+         * Sets the total number of characters to receive in the history.
+         * 
+         * @param maxChars the total number of characters to receive in the history.
+         */
+        public void setMaxChars(int maxChars) {
+            this.maxChars = maxChars;
+        }
+
+        /**
+         * Sets the total number of messages to receive in the history.
+         * 
+         * @param maxStanzas the total number of messages to receive in the history.
+         */
+        public void setMaxStanzas(int maxStanzas) {
+            this.maxStanzas = maxStanzas;
+        }
+
+        /**
+         * Sets the number of seconds to use to filter the messages received during that time. 
+         * In other words, only the messages received in the last "X" seconds will be included in 
+         * the history.
+         * 
+         * @param seconds the number of seconds to use to filter the messages received during 
+         * that time.
+         */
+        public void setSeconds(int seconds) {
+            this.seconds = seconds;
+        }
+
+        /**
+         * Sets the since date to use to filter the messages received during that time. 
+         * In other words, only the messages received since the datetime specified will be 
+         * included in the history.
+         * 
+         * @param since the since date to use to filter the messages received during that time.
+         */
+        public void setSince(Date since) {
+            this.since = since;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<history");
+            if (getMaxChars() != -1) {
+                buf.append(" maxchars=\"").append(getMaxChars()).append("\"");
+            }
+            if (getMaxStanzas() != -1) {
+                buf.append(" maxstanzas=\"").append(getMaxStanzas()).append("\"");
+            }
+            if (getSeconds() != -1) {
+                buf.append(" seconds=\"").append(getSeconds()).append("\"");
+            }
+            if (getSince() != null) {
+                SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+                utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+                buf.append(" since=\"").append(utcFormat.format(getSince())).append("\"");
+            }
+            buf.append("/>");
+            return buf.toString();
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java
new file mode 100644
index 000000000..626746884
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCOwner.java
@@ -0,0 +1,339 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+import java.util.*;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * IQ packet that serves for granting and revoking ownership privileges, granting 
+ * and revoking administrative privileges and destroying a room. All these operations 
+ * are scoped by the 'http://jabber.org/protocol/muc#owner' namespace.
+ * 
+ * @author Gaston Dombiak
+ */
+public class MUCOwner extends IQ {
+
+    private List items = new ArrayList();
+    private Destroy destroy;
+
+    /**
+     * Returns an Iterator for item childs that holds information about affiliation, 
+     * jids and nicks.
+     * 
+     * @return an Iterator for item childs that holds information about affiliation,
+     *          jids and nicks.
+     */
+    public Iterator getItems() {
+        synchronized (items) {
+            return Collections.unmodifiableList(new ArrayList(items)).iterator();
+        }
+    }
+
+    /**
+     * Returns a request to the server to destroy a room. The sender of the request
+     * should be the room's owner. If the sender of the destroy request is not the room's owner
+     * then the server will answer a "Forbidden" error.
+     * 
+     * @return a request to the server to destroy a room.
+     */
+    public Destroy getDestroy() {
+        return destroy;
+    }
+
+    /**
+     * Sets a request to the server to destroy a room. The sender of the request
+     * should be the room's owner. If the sender of the destroy request is not the room's owner
+     * then the server will answer a "Forbidden" error.
+     * 
+     * @param destroy the request to the server to destroy a room.
+     */
+    public void setDestroy(Destroy destroy) {
+        this.destroy = destroy;
+    }
+
+    /**
+     * Adds an item child that holds information about affiliation, jids and nicks.
+     * 
+     * @param item the item child that holds information about affiliation, jids and nicks.
+     */
+    public void addItem(Item item) {
+        synchronized (items) {
+            items.add(item);
+        }
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"http://jabber.org/protocol/muc#owner\">");
+        synchronized (items) {
+            for (int i = 0; i < items.size(); i++) {
+                Item item = (Item) items.get(i);
+                buf.append(item.toXML());
+            }
+        }
+        if (getDestroy() != null) {
+            buf.append(getDestroy().toXML());
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</query>");
+        return buf.toString();
+    }
+
+    /**
+     * Item child that holds information about affiliation, jids and nicks.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Item {
+        
+        private String actor;
+        private String reason;
+        private String affiliation;
+        private String jid;
+        private String nick;
+        private String role;
+
+        /**
+         * Creates a new item child. 
+         * 
+         * @param affiliation the actor's affiliation to the room
+         */
+        public Item(String affiliation) {
+            this.affiliation = affiliation;
+        }
+        
+        /**
+         * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @return the JID of an occupant in the room that was kicked or banned.
+         */
+        public String getActor() {
+            return actor;
+        }
+
+        /**
+         * Returns the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         *  
+         * @return the reason for the item child.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent 
+         * association or connection with a room. The possible affiliations are "owner", "admin", 
+         * "member", and "outcast" (naturally it is also possible to have no affiliation). An 
+         * affiliation lasts across a user's visits to a room.
+         * 
+         * @return the actor's affiliation to the room
+         */
+        public String getAffiliation() {
+            return affiliation;
+        }
+
+        /**
+         * Returns the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item. 
+         * 
+         * @return the room JID by which an occupant is identified within the room.
+         */
+        public String getJid() {
+            return jid;
+        }
+
+        /**
+         * Returns the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.  
+         * 
+         * @return the new nickname of an occupant that is changing his/her nickname.
+         */
+        public String getNick() {
+            return nick;
+        }
+
+        /**
+         * Returns the temporary position or privilege level of an occupant within a room. The
+         * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+         * have no defined role). A role lasts only for the duration of an occupant's visit to
+         * a room.
+         *
+         * @return the privilege level of an occupant within a room.
+         */
+        public String getRole() {
+            return role;
+        }
+
+        /**
+         * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+         */
+        public void setActor(String actor) {
+            this.actor = actor;
+        }
+
+        /**
+         * Sets the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         * 
+         * @param reason the reason why a user (occupant) was kicked or banned.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        /**
+         * Sets the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item.
+         *  
+         * @param jid the JID by which an occupant is identified within a room.
+         */
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        /**
+         * Sets the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.
+         *   
+         * @param nick the new nickname of an occupant that is changing his/her nickname.
+         */
+        public void setNick(String nick) {
+            this.nick = nick;
+        }
+
+        /**
+         * Sets the temporary position or privilege level of an occupant within a room. The
+         * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+         * have no defined role). A role lasts only for the duration of an occupant's visit to
+         * a room.
+         *
+         * @param role the new privilege level of an occupant within a room.
+         */
+        public void setRole(String role) {
+            this.role = role;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item");
+            if (getAffiliation() != null) {
+                buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+            }
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getNick() != null) {
+                buf.append(" nick=\"").append(getNick()).append("\"");
+            }
+            if (getRole() != null) {
+                buf.append(" role=\"").append(getRole()).append("\"");
+            }
+            if (getReason() == null && getActor() == null) {
+                buf.append("/>");
+            }
+            else {
+                buf.append(">");
+                if (getReason() != null) {
+                    buf.append("<reason>").append(getReason()).append("</reason>");
+                }
+                if (getActor() != null) {
+                    buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+                }
+                buf.append("</item>");
+            }
+            return buf.toString();
+        }
+    };
+
+    /**
+     * Represents a request to the server to destroy a room. The sender of the request
+     * should be the room's owner. If the sender of the destroy request is not the room's owner
+     * then the server will answer a "Forbidden" error.
+     * 
+     * @author Gaston Dombiak
+     */
+    public static class Destroy {
+        private String reason;
+        private String jid;
+        
+        
+        /**
+         * Returns the JID of an alternate location since the current room is being destroyed.
+         * 
+         * @return the JID of an alternate location.
+         */
+        public String getJid() {
+            return jid;
+        }
+
+        /**
+         * Returns the reason for the room destruction.
+         * 
+         * @return the reason for the room destruction.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Sets the JID of an alternate location since the current room is being destroyed.
+         * 
+         * @param jid the JID of an alternate location.
+         */
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        /**
+         * Sets the reason for the room destruction.
+         * 
+         * @param reason the reason for the room destruction.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<destroy");
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getReason() == null) {
+                buf.append("/>");
+            }
+            else {
+                buf.append(">");
+                if (getReason() != null) {
+                    buf.append("<reason>").append(getReason()).append("</reason>");
+                }
+                buf.append("</destroy>");
+            }
+            return buf.toString();
+        }
+
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java
new file mode 100644
index 000000000..7e84cea6b
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MUCUser.java
@@ -0,0 +1,627 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents extended presence information about roles, affiliations, full JIDs, 
+ * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace.
+ * 
+ * @author Gaston Dombiak
+ */
+public class MUCUser implements PacketExtension {
+
+    private Invite invite;
+    private Decline decline;
+    private Item item;
+    private String password;
+    private Status status;
+    private Destroy destroy;
+
+    public String getElementName() {
+        return "x";
+    }
+
+    public String getNamespace() {
+        return "http://jabber.org/protocol/muc#user";
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        if (getInvite() != null) {
+            buf.append(getInvite().toXML());
+        }
+        if (getDecline() != null) {
+            buf.append(getDecline().toXML());
+        }
+        if (getItem() != null) {
+            buf.append(getItem().toXML());
+        }
+        if (getPassword() != null) {
+            buf.append("<password>").append(getPassword()).append("</password>");
+        }
+        if (getStatus() != null) {
+            buf.append(getStatus().toXML());
+        }
+        if (getDestroy() != null) {
+            buf.append(getDestroy().toXML());
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * Returns the invitation for another user to a room. The sender of the invitation
+     * must be an occupant of the room. The invitation will be sent to the room which in turn
+     * will forward the invitation to the invitee.
+     *  
+     * @return an invitation for another user to a room.
+     */
+    public Invite getInvite() {
+        return invite;
+    }
+
+    /**
+     * Returns the rejection to an invitation from another user to a room. The rejection will be 
+     * sent to the room which in turn will forward the refusal to the inviter.
+     *  
+     * @return a rejection to an invitation from another user to a room.
+     */
+    public Decline getDecline() {
+        return decline;
+    }
+
+    /**
+     * Returns the item child that holds information about roles, affiliation, jids and nicks.
+     * 
+     * @return an item child that holds information about roles, affiliation, jids and nicks.
+     */
+    public Item getItem() {
+        return item;
+    }
+
+    /**
+     * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is 
+     * a room that a user cannot enter without first providing the correct password.
+     * 
+     * @return the password to use to enter Password-Protected Room.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Returns the status which holds a code that assists in presenting notification messages.
+     * 
+     * @return the status which holds a code that assists in presenting notification messages.
+     */
+    public Status getStatus() {
+        return status;
+    }
+
+    /**
+     * Returns the notification that the room has been destroyed. After a room has been destroyed,
+     * the room occupants will receive a Presence packet of type 'unavailable' with the reason for 
+     * the room destruction if provided by the room owner.
+     *  
+     * @return a notification that the room has been destroyed.
+     */
+    public Destroy getDestroy() {
+        return destroy;
+    }
+    
+    /**
+     * Sets the invitation for another user to a room. The sender of the invitation
+     * must be an occupant of the room. The invitation will be sent to the room which in turn
+     * will forward the invitation to the invitee.
+     * 
+     * @param invite the invitation for another user to a room.
+     */
+    public void setInvite(Invite invite) {
+        this.invite = invite;
+    }
+
+    /**
+     * Sets the rejection to an invitation from another user to a room. The rejection will be 
+     * sent to the room which in turn will forward the refusal to the inviter.
+     *  
+     * @param decline the rejection to an invitation from another user to a room.
+     */
+    public void setDecline(Decline decline) {
+        this.decline = decline;
+    }
+
+    /**
+     * Sets the item child that holds information about roles, affiliation, jids and nicks.
+     * 
+     * @param item the item child that holds information about roles, affiliation, jids and nicks.
+     */
+    public void setItem(Item item) {
+        this.item = item;
+    }
+
+    /**
+     * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is 
+     * a room that a user cannot enter without first providing the correct password.
+     * 
+     * @param string the password to use to enter Password-Protected Room.
+     */
+    public void setPassword(String string) {
+        password = string;
+    }
+
+    /**
+     * Sets the status which holds a code that assists in presenting notification messages.
+     * 
+     * @param status the status which holds a code that assists in presenting notification 
+     * messages.
+     */
+    public void setStatus(Status status) {
+        this.status = status;
+    }
+
+    /**
+     * Sets the notification that the room has been destroyed. After a room has been destroyed,
+     * the room occupants will receive a Presence packet of type 'unavailable' with the reason for 
+     * the room destruction if provided by the room owner.
+     *  
+     * @param destroy the notification that the room has been destroyed.
+     */
+    public void setDestroy(Destroy destroy) {
+        this.destroy = destroy;
+    }
+
+    /**
+     * Represents an invitation for another user to a room. The sender of the invitation
+     * must be an occupant of the room. The invitation will be sent to the room which in turn
+     * will forward the invitation to the invitee. 
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Invite {
+        private String reason;
+        private String from;
+        private String to;
+        
+        /**
+         * Returns the bare JID of the inviter or, optionally, the room JID. (e.g. 
+         * 'crone1@shakespeare.lit/desktop'). 
+         * 
+         * @return the room's occupant that sent the invitation.
+         */
+        public String getFrom() {
+            return from;
+        }
+
+        /**
+         * Returns the message explaining the invitation.
+         * 
+         * @return the message explaining the invitation.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit')
+         * 
+         * @return the bare JID of the invitee.
+         */
+        public String getTo() {
+            return to;
+        }
+
+        /**
+         * Sets the bare JID of the inviter or, optionally, the room JID. (e.g. 
+         * 'crone1@shakespeare.lit/desktop')
+         * 
+         * @param from the bare JID of the inviter or, optionally, the room JID.
+         */
+        public void setFrom(String from) {
+            this.from = from;
+        }
+
+        /**
+         * Sets the message explaining the invitation.
+         * 
+         * @param reason the message explaining the invitation.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        /**
+         * Sets the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit')
+         * 
+         * @param to the bare JID of the invitee.
+         */
+        public void setTo(String to) {
+            this.to = to;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<invite ");
+            if (getTo() != null) {
+                buf.append(" to=\"").append(getTo()).append("\"");
+            }
+            if (getFrom() != null) {
+                buf.append(" from=\"").append(getFrom()).append("\"");
+            }
+            buf.append(">");
+            if (getReason() != null) {
+                buf.append("<reason>").append(getReason()).append("</reason>");
+            }
+            buf.append("</invite>");
+            return buf.toString();
+        }
+    };
+
+    /**
+     * Represents a rejection to an invitation from another user to a room. The rejection will be 
+     * sent to the room which in turn will forward the refusal to the inviter. 
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Decline {
+        private String reason;
+        private String from;
+        private String to;
+        
+        /**
+         * Returns the bare JID of the invitee that rejected the invitation. (e.g. 
+         * 'crone1@shakespeare.lit/desktop'). 
+         * 
+         * @return the bare JID of the invitee that rejected the invitation.
+         */
+        public String getFrom() {
+            return from;
+        }
+
+        /**
+         * Returns the message explaining why the invitation was rejected.
+         * 
+         * @return the message explaining the reason for the rejection.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Returns the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit')
+         * 
+         * @return the bare JID of the inviter.
+         */
+        public String getTo() {
+            return to;
+        }
+
+        /**
+         * Sets the bare JID of the invitee that rejected the invitation. (e.g. 
+         * 'crone1@shakespeare.lit/desktop'). 
+         * 
+         * @param from the bare JID of the invitee that rejected the invitation.
+         */
+        public void setFrom(String from) {
+            this.from = from;
+        }
+
+        /**
+         * Sets the message explaining why the invitation was rejected.
+         * 
+         * @param reason the message explaining the reason for the rejection.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        /**
+         * Sets the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit')
+         * 
+         * @param to the bare JID of the inviter.
+         */
+        public void setTo(String to) {
+            this.to = to;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<decline ");
+            if (getTo() != null) {
+                buf.append(" to=\"").append(getTo()).append("\"");
+            }
+            if (getFrom() != null) {
+                buf.append(" from=\"").append(getFrom()).append("\"");
+            }
+            buf.append(">");
+            if (getReason() != null) {
+                buf.append("<reason>").append(getReason()).append("</reason>");
+            }
+            buf.append("</decline>");
+            return buf.toString();
+        }
+    };
+
+    /**
+     * Item child that holds information about roles, affiliation, jids and nicks.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Item {
+        private String actor;
+        private String reason;
+        private String affiliation;
+        private String jid;
+        private String nick;
+        private String role;
+        
+        /**
+         * Creates a new item child. 
+         * 
+         * @param affiliation the actor's affiliation to the room
+         * @param role the privilege level of an occupant within a room.
+         */
+        public Item(String affiliation, String role) {
+            this.affiliation = affiliation;
+            this.role = role;
+        }
+        
+        /**
+         * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @return the JID of an occupant in the room that was kicked or banned.
+         */
+        public String getActor() {
+            return actor == null ? "" : actor;
+        }
+
+        /**
+         * Returns the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         *  
+         * @return the reason for the item child.
+         */
+        public String getReason() {
+            return reason == null ? "" : reason;
+        }
+
+        /**
+         * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent 
+         * association or connection with a room. The possible affiliations are "owner", "admin", 
+         * "member", and "outcast" (naturally it is also possible to have no affiliation). An 
+         * affiliation lasts across a user's visits to a room.
+         * 
+         * @return the actor's affiliation to the room
+         */
+        public String getAffiliation() {
+            return affiliation;
+        }
+
+        /**
+         * Returns the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item. 
+         * 
+         * @return the room JID by which an occupant is identified within the room.
+         */
+        public String getJid() {
+            return jid;
+        }
+
+        /**
+         * Returns the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.  
+         * 
+         * @return the new nickname of an occupant that is changing his/her nickname.
+         */
+        public String getNick() {
+            return nick;
+        }
+
+        /**
+         * Returns the temporary position or privilege level of an occupant within a room. The 
+         * possible roles are "moderator", "participant", and "visitor" (it is also possible to 
+         * have no defined role). A role lasts only for the duration of an occupant's visit to 
+         * a room. 
+         * 
+         * @return the privilege level of an occupant within a room.
+         */
+        public String getRole() {
+            return role;
+        }
+
+        /**
+         * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+         * 
+         * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+         */
+        public void setActor(String actor) {
+            this.actor = actor;
+        }
+
+        /**
+         * Sets the reason for the item child. The reason is optional and could be used to
+         * explain the reason why a user (occupant) was kicked or banned.
+         * 
+         * @param reason the reason why a user (occupant) was kicked or banned.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        /**
+         * Sets the <room@service/nick> by which an occupant is identified within the context 
+         * of a room. If the room is non-anonymous, the JID will be included in the item.
+         *  
+         * @param jid the JID by which an occupant is identified within a room.
+         */
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        /**
+         * Sets the new nickname of an occupant that is changing his/her nickname. The new 
+         * nickname is sent as part of the unavailable presence.
+         *   
+         * @param nick the new nickname of an occupant that is changing his/her nickname.
+         */
+        public void setNick(String nick) {
+            this.nick = nick;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item");
+            if (getAffiliation() != null) {
+                buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+            }
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getNick() != null) {
+                buf.append(" nick=\"").append(getNick()).append("\"");
+            }
+            if (getRole() != null) {
+                buf.append(" role=\"").append(getRole()).append("\"");
+            }
+            if (getReason() == null && getActor() == null) {
+                buf.append("/>");
+            }
+            else {
+                buf.append(">");
+                if (getReason() != null) {
+                    buf.append("<reason>").append(getReason()).append("</reason>");
+                }
+                if (getActor() != null) {
+                    buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+                }
+                buf.append("</item>");
+            }
+            return buf.toString();
+        }
+    };
+
+    /**
+     * Status code assists in presenting notification messages. The following link provides the 
+     * list of existing error codes (@link http://www.jabber.org/jeps/jep-0045.html#errorstatus).
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Status {
+        private String code; 
+
+        /**
+         * Creates a new instance of Status with the specified code.  
+         * 
+         * @param code the code that uniquely identifies the reason of the error.
+         */
+        public Status(String code) {
+            this.code = code;
+        }
+                
+        /**
+         * Returns the code that uniquely identifies the reason of the error. The code 
+         * assists in presenting notification messages. 
+         * 
+         * @return the code that uniquely identifies the reason of the error.
+         */
+        public String getCode() {
+            return code;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<status code=\"").append(getCode()).append("\"/>");
+            return buf.toString();
+        }
+    };
+
+    /**
+     * Represents a notification that the room has been destroyed. After a room has been destroyed,
+     * the room occupants will receive a Presence packet of type 'unavailable' with the reason for 
+     * the room destruction if provided by the room owner. 
+     * 
+     * @author Gaston Dombiak
+     */
+    public static class Destroy {
+        private String reason;
+        private String jid;
+        
+        
+        /**
+         * Returns the JID of an alternate location since the current room is being destroyed.
+         * 
+         * @return the JID of an alternate location.
+         */
+        public String getJid() {
+            return jid;
+        }
+
+        /**
+         * Returns the reason for the room destruction.
+         * 
+         * @return the reason for the room destruction.
+         */
+        public String getReason() {
+            return reason;
+        }
+
+        /**
+         * Sets the JID of an alternate location since the current room is being destroyed.
+         * 
+         * @param jid the JID of an alternate location.
+         */
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        /**
+         * Sets the reason for the room destruction.
+         * 
+         * @param reason the reason for the room destruction.
+         */
+        public void setReason(String reason) {
+            this.reason = reason;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<destroy");
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getReason() == null) {
+                buf.append("/>");
+            }
+            else {
+                buf.append(">");
+                if (getReason() != null) {
+                    buf.append("<reason>").append(getReason()).append("</reason>");
+                }
+                buf.append("</destroy>");
+            }
+            return buf.toString();
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java
new file mode 100644
index 000000000..65e091897
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/MessageEvent.java
@@ -0,0 +1,334 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents message events relating to the delivery, display, composition and cancellation of 
+ * messages.<p>
+ * 
+ * There are four message events currently defined in this namespace:
+ * <ol>
+ * <li>Offline<br>
+ * Indicates that the message has been stored offline by the intended recipient's server. This 
+ * event is triggered only if the intended recipient's server supports offline storage, has that 
+ * support enabled, and the recipient is offline when the server receives the message for delivery.</li>
+ * 
+ * <li>Delivered<br>
+ * Indicates that the message has been delivered to the recipient. This signifies that the message
+ * has reached the recipient's XMPP client, but does not necessarily mean that the message has 
+ * been displayed. This event is to be raised by the XMPP client.</li>
+ * 
+ * <li>Displayed<br>
+ * Once the message has been received by the recipient's XMPP client, it may be displayed to the
+ * user. This event indicates that the message has been displayed, and is to be raised by the 
+ * XMPP client. Even if a message is displayed multiple times, this event should be raised only 
+ * once.</li>
+ * 
+ * <li>Composing<br>
+ * In threaded chat conversations, this indicates that the recipient is composing a reply to a 
+ * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed
+ * to raise this event multiple times in response to the same request, providing the original 
+ * event is cancelled first.</li>
+ * </ol>
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEvent implements PacketExtension {
+
+    public static final String OFFLINE = "offline";
+    public static final String COMPOSING = "composing";
+    public static final String DISPLAYED = "displayed";
+    public static final String DELIVERED = "delivered";
+    public static final String CANCELLED = "cancelled";
+
+    private boolean offline = false;
+    private boolean delivered = false;
+    private boolean displayed = false;
+    private boolean composing = false;
+    private boolean cancelled = true;
+
+    private String packetID = null;
+
+    /**
+    * Returns the XML element name of the extension sub-packet root element.
+    * Always returns "x"
+    *
+    * @return the XML element name of the packet extension.
+    */
+    public String getElementName() {
+        return "x";
+    }
+
+    /** 
+     * Returns the XML namespace of the extension sub-packet root element.
+     * According the specification the namespace is always "jabber:x:event"
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return "jabber:x:event";
+    }
+
+    /**
+     * When the message is a request returns if the sender of the message requests to be notified
+     * when the receiver is composing a reply.
+     * When the message is a notification returns if the receiver of the message is composing a 
+     * reply.
+     * 
+     * @return true if the sender is requesting to be notified when composing or when notifying
+     * that the receiver of the message is composing a reply
+     */
+    public boolean isComposing() {
+        return composing;
+    }
+
+    /**
+     * When the message is a request returns if the sender of the message requests to be notified
+     * when the message is delivered.
+     * When the message is a notification returns if the message was delivered or not.
+     * 
+     * @return true if the sender is requesting to be notified when delivered or when notifying 
+     * that the message was delivered 
+     */
+    public boolean isDelivered() {
+        return delivered;
+    }
+
+    /**
+     * When the message is a request returns if the sender of the message requests to be notified
+     * when the message is displayed.
+     * When the message is a notification returns if the message was displayed or not.
+     * 
+     * @return true if the sender is requesting to be notified when displayed or when notifying 
+     * that the message was displayed
+     */
+    public boolean isDisplayed() {
+        return displayed;
+    }
+
+    /**
+     * When the message is a request returns if the sender of the message requests to be notified
+     * when the receiver of the message is offline.
+     * When the message is a notification returns if the receiver of the message was offline.
+     * 
+     * @return true if the sender is requesting to be notified when offline or when notifying 
+     * that the receiver of the message is offline
+     */
+    public boolean isOffline() {
+        return offline;
+    }
+
+    /**
+     * When the message is a notification returns if the receiver of the message cancelled 
+     * composing a reply.
+     * 
+     * @return true if the receiver of the message cancelled composing a reply
+     */
+    public boolean isCancelled() {
+        return cancelled;
+    }
+
+    /**
+     * Returns the unique ID of the message that requested to be notified of the event.
+     * The packet id is not used when the message is a request for notifications
+     *
+     * @return the message id that requested to be notified of the event.
+     */
+    public String getPacketID() {
+        return packetID;
+    }
+
+    /**
+     * Returns the types of events. The type of event could be:
+     * "offline", "composing","delivered","displayed", "offline"
+     *
+     * @return an iterator over all the types of events of the MessageEvent.
+     */
+    public Iterator getEventTypes() {
+        ArrayList allEvents = new ArrayList();
+        if (isDelivered()) {
+            allEvents.add(MessageEvent.DELIVERED);
+        }
+        if (isCancelled()) {
+            allEvents.add(MessageEvent.CANCELLED);
+        }
+        if (isComposing()) {
+            allEvents.add(MessageEvent.COMPOSING);
+        }
+        if (isDisplayed()) {
+            allEvents.add(MessageEvent.DISPLAYED);
+        }
+        if (isOffline()) {
+            allEvents.add(MessageEvent.OFFLINE);
+        }
+        return allEvents.iterator();
+    }
+
+    /**
+     * When the message is a request sets if the sender of the message requests to be notified
+     * when the receiver is composing a reply.
+     * When the message is a notification sets if the receiver of the message is composing a 
+     * reply.
+     * 
+     * @param composing sets if the sender is requesting to be notified when composing or when 
+     * notifying that the receiver of the message is composing a reply
+     */
+    public void setComposing(boolean composing) {
+        this.composing = composing;
+        setCancelled(false);
+    }
+
+    /**
+     * When the message is a request sets if the sender of the message requests to be notified
+     * when the message is delivered.
+     * When the message is a notification sets if the message was delivered or not.
+     * 
+     * @param delivered sets if the sender is requesting to be notified when delivered or when 
+     * notifying that the message was delivered 
+     */
+    public void setDelivered(boolean delivered) {
+        this.delivered = delivered;
+        setCancelled(false);
+    }
+
+    /**
+     * When the message is a request sets if the sender of the message requests to be notified
+     * when the message is displayed.
+     * When the message is a notification sets if the message was displayed or not.
+     * 
+     * @param displayed sets if the sender is requesting to be notified when displayed or when 
+     * notifying that the message was displayed
+     */
+    public void setDisplayed(boolean displayed) {
+        this.displayed = displayed;
+        setCancelled(false);
+    }
+
+    /**
+     * When the message is a request sets if the sender of the message requests to be notified
+     * when the receiver of the message is offline.
+     * When the message is a notification sets if the receiver of the message was offline.
+     * 
+     * @param offline sets if the sender is requesting to be notified when offline or when 
+     * notifying that the receiver of the message is offline
+     */
+    public void setOffline(boolean offline) {
+        this.offline = offline;
+        setCancelled(false);
+    }
+
+    /**
+     * When the message is a notification sets if the receiver of the message cancelled 
+     * composing a reply.
+     * The Cancelled event is never requested explicitly. It is requested implicitly when
+     * requesting to be notified of the Composing event.
+     * 
+     * @param cancelled sets if the receiver of the message cancelled composing a reply
+     */
+    public void setCancelled(boolean cancelled) {
+        this.cancelled = cancelled;
+    }
+
+    /**
+     * Sets the unique ID of the message that requested to be notified of the event.
+     * The packet id is not used when the message is a request for notifications
+     *
+     * @param packetID the message id that requested to be notified of the event.
+     */
+    public void setPacketID(String packetID) {
+        this.packetID = packetID;
+    }
+
+    /**
+     * Returns true if this MessageEvent is a request for notifications.
+     * Returns false if this MessageEvent is a notification of an event.
+     *
+    * @return true if this message is a request for notifications.
+     */
+    public boolean isMessageEventRequest() {
+        return this.packetID == null;
+    }
+
+    /**
+     * Returns the XML representation of a Message Event according the specification.
+     * 
+     * Usually the XML representation will be inside of a Message XML representation like
+     * in the following examples:<p>
+     * 
+     * Request to be notified when displayed:
+     * <pre>
+     * &lt;message
+     *    to='romeo@montague.net/orchard'
+     *    from='juliet@capulet.com/balcony'
+     *    id='message22'&gt;
+     * &lt;x xmlns='jabber:x:event'&gt;
+     *   &lt;displayed/&gt;
+     * &lt;/x&gt;
+     * &lt;/message&gt;
+     * </pre>
+     * 
+     * Notification of displayed:
+     * <pre>
+     * &lt;message
+     *    from='romeo@montague.net/orchard'
+     *    to='juliet@capulet.com/balcony'&gt;
+     * &lt;x xmlns='jabber:x:event'&gt;
+     *   &lt;displayed/&gt;
+     *   &lt;id&gt;message22&lt;/id&gt;
+     * &lt;/x&gt;
+     * &lt;/message&gt;
+     * </pre>
+     * 
+     */
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        // Note: Cancellation events don't specify any tag. They just send the packetID
+
+        // Add the offline tag if the sender requests to be notified of offline events or if 
+        // the target is offline
+        if (isOffline())
+            buf.append("<").append(MessageEvent.OFFLINE).append("/>");
+        // Add the delivered tag if the sender requests to be notified when the message is 
+        // delivered or if the target notifies that the message has been delivered
+        if (isDelivered())
+            buf.append("<").append(MessageEvent.DELIVERED).append("/>");
+        // Add the displayed tag if the sender requests to be notified when the message is 
+        // displayed or if the target notifies that the message has been displayed
+        if (isDisplayed())
+            buf.append("<").append(MessageEvent.DISPLAYED).append("/>");
+        // Add the composing tag if the sender requests to be notified when the target is 
+        // composing a reply or if the target notifies that he/she is composing a reply
+        if (isComposing())
+            buf.append("<").append(MessageEvent.COMPOSING).append("/>");
+        // Add the id tag only if the MessageEvent is a notification message (not a request)
+        if (getPacketID() != null)
+            buf.append("<id>").append(getPacketID()).append("</id>");
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java
new file mode 100644
index 000000000..90c1ead71
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java
@@ -0,0 +1,128 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * OfflineMessageInfo is an extension included in the retrieved offline messages requested by
+ * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp
+ * that uniquely identifies the offline message. This stamp may be used for deleting the offline
+ * message. The stamp may be of the form UTC timestamps but it is not required to have that format.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageInfo implements PacketExtension {
+
+    private String node = null;
+
+    /**
+    * Returns the XML element name of the extension sub-packet root element.
+    * Always returns "offline"
+    *
+    * @return the XML element name of the packet extension.
+    */
+    public String getElementName() {
+        return "offline";
+    }
+
+    /**
+     * Returns the XML namespace of the extension sub-packet root element.
+     * According the specification the namespace is always "http://jabber.org/protocol/offline"
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return "http://jabber.org/protocol/offline";
+    }
+
+    /**
+     * Returns the stamp that uniquely identifies the offline message. This stamp may
+     * be used for deleting the offline message. The stamp may be of the form UTC timestamps
+     * but it is not required to have that format.
+     *
+     * @return the stamp that uniquely identifies the offline message.
+     */
+    public String getNode() {
+        return node;
+    }
+
+    /**
+     * Sets the stamp that uniquely identifies the offline message. This stamp may
+     * be used for deleting the offline message. The stamp may be of the form UTC timestamps
+     * but it is not required to have that format.
+     *
+     * @param node the stamp that uniquely identifies the offline message.
+     */
+    public void setNode(String node) {
+        this.node = node;
+    }
+
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        if (getNode() != null)
+            buf.append("<item node=\"").append(getNode()).append("\"/>");
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+    public static class Provider implements PacketExtensionProvider {
+
+        /**
+         * Creates a new Provider.
+         * ProviderManager requires that every PacketExtensionProvider has a public,
+         * no-argument constructor
+         */
+        public Provider() {
+        }
+
+        /**
+         * Parses a OfflineMessageInfo packet (extension sub-packet).
+         *
+         * @param parser the XML parser, positioned at the starting element of the extension.
+         * @return a PacketExtension.
+         * @throws Exception if a parsing error occurs.
+         */
+        public PacketExtension parseExtension(XmlPullParser parser)
+            throws Exception {
+            OfflineMessageInfo info = new OfflineMessageInfo();
+            boolean done = false;
+            while (!done) {
+                int eventType = parser.next();
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals("item"))
+                        info.setNode(parser.getAttributeValue("", "node"));
+                } else if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("offline")) {
+                        done = true;
+                    }
+                }
+            }
+
+            return info;
+        }
+
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java
new file mode 100644
index 000000000..9b726e3c6
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java
@@ -0,0 +1,237 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a request to get some or all the offline messages of a user. This class can also
+ * be used for deleting some or all the offline messages of a user.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageRequest extends IQ {
+
+    private List items = new ArrayList();
+    private boolean purge = false;
+    private boolean fetch = false;
+
+    /**
+     * Returns an Iterator for item childs that holds information about offline messages to
+     * view or delete.
+     *
+     * @return an Iterator for item childs that holds information about offline messages to
+     *         view or delete.
+     */
+    public Iterator getItems() {
+        synchronized (items) {
+            return Collections.unmodifiableList(new ArrayList(items)).iterator();
+        }
+    }
+
+    /**
+     * Adds an item child that holds information about offline messages to view or delete.
+     *
+     * @param item the item child that holds information about offline messages to view or delete.
+     */
+    public void addItem(Item item) {
+        synchronized (items) {
+            items.add(item);
+        }
+    }
+
+    /**
+     * Returns true if all the offline messages of the user should be deleted.
+     *
+     * @return true if all the offline messages of the user should be deleted.
+     */
+    public boolean isPurge() {
+        return purge;
+    }
+
+    /**
+     * Sets if all the offline messages of the user should be deleted.
+     *
+     * @param purge true if all the offline messages of the user should be deleted.
+     */
+    public void setPurge(boolean purge) {
+        this.purge = purge;
+    }
+
+    /**
+     * Returns true if all the offline messages of the user should be retrieved.
+     *
+     * @return true if all the offline messages of the user should be retrieved.
+     */
+    public boolean isFetch() {
+        return fetch;
+    }
+
+    /**
+     * Sets if all the offline messages of the user should be retrieved.
+     *
+     * @param fetch true if all the offline messages of the user should be retrieved.
+     */
+    public void setFetch(boolean fetch) {
+        this.fetch = fetch;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<offline xmlns=\"http://jabber.org/protocol/offline\">");
+        synchronized (items) {
+            for (int i = 0; i < items.size(); i++) {
+                Item item = (Item) items.get(i);
+                buf.append(item.toXML());
+            }
+        }
+        if (purge) {
+            buf.append("<purge/>");
+        }
+        if (fetch) {
+            buf.append("<fetch/>");
+        }
+        // Add packet extensions, if any are defined.
+        buf.append(getExtensionsXML());
+        buf.append("</offline>");
+        return buf.toString();
+    }
+
+    /**
+     * Item child that holds information about offline messages to view or delete.
+     *
+     * @author Gaston Dombiak
+     */
+    public static class Item {
+        private String action;
+        private String jid;
+        private String node;
+
+        /**
+         * Creates a new item child.
+         *
+         * @param node the actor's affiliation to the room
+         */
+        public Item(String node) {
+            this.node = node;
+        }
+
+        public String getNode() {
+            return node;
+        }
+
+        /**
+         * Returns "view" or "remove" that indicate if the server should return the specified
+         * offline message or delete it.
+         *
+         * @return "view" or "remove" that indicate if the server should return the specified
+         *         offline message or delete it.
+         */
+        public String getAction() {
+            return action;
+        }
+
+        /**
+         * Sets if the server should return the specified offline message or delete it. Possible
+         * values are "view" or "remove".
+         *
+         * @param action if the server should return the specified offline message or delete it.
+         */
+        public void setAction(String action) {
+            this.action = action;
+        }
+
+        public String getJid() {
+            return jid;
+        }
+
+        public void setJid(String jid) {
+            this.jid = jid;
+        }
+
+        public String toXML() {
+            StringBuffer buf = new StringBuffer();
+            buf.append("<item");
+            if (getAction() != null) {
+                buf.append(" action=\"").append(getAction()).append("\"");
+            }
+            if (getJid() != null) {
+                buf.append(" jid=\"").append(getJid()).append("\"");
+            }
+            if (getNode() != null) {
+                buf.append(" node=\"").append(getNode()).append("\"");
+            }
+            buf.append("/>");
+            return buf.toString();
+        }
+    }
+
+    public static class Provider implements IQProvider {
+
+        public IQ parseIQ(XmlPullParser parser) throws Exception {
+            OfflineMessageRequest request = new OfflineMessageRequest();
+            boolean done = false;
+            while (!done) {
+                int eventType = parser.next();
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals("item")) {
+                        request.addItem(parseItem(parser));
+                    }
+                    else if (parser.getName().equals("purge")) {
+                        request.setPurge(true);
+                    }
+                    else if (parser.getName().equals("fetch")) {
+                        request.setFetch(true);
+                    }
+                } else if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("offline")) {
+                        done = true;
+                    }
+                }
+            }
+
+            return request;
+        }
+
+        private Item parseItem(XmlPullParser parser) throws Exception {
+            boolean done = false;
+            Item item = new Item(parser.getAttributeValue("", "node"));
+            item.setAction(parser.getAttributeValue("", "action"));
+            item.setJid(parser.getAttributeValue("", "jid"));
+            while (!done) {
+                int eventType = parser.next();
+                if (eventType == XmlPullParser.END_TAG) {
+                    if (parser.getName().equals("item")) {
+                        done = true;
+                    }
+                }
+            }
+            return item;
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java
new file mode 100644
index 000000000..c83269c51
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/PrivateData.java
@@ -0,0 +1,52 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+/**
+ * Interface to represent private data. Each private data chunk is an XML sub-document
+ * with a root element name and namespace.
+ *
+ * @see org.jivesoftware.smackx.PrivateDataManager
+ * @author Matt Tucker
+ */
+public interface PrivateData {
+
+    /**
+     * Returns the root element name.
+     *
+     * @return the element name.
+     */
+    public String getElementName();
+
+    /**
+     * Returns the root element XML namespace.
+     *
+     * @return the namespace.
+     */
+    public String getNamespace();
+
+    /**
+     * Returns the XML reppresentation of the PrivateData.
+     *
+     * @return the private data as XML.
+     */
+    public String toXML();
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java
new file mode 100644
index 000000000..553c6dcdf
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/RosterExchange.java
@@ -0,0 +1,175 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.*;
+
+/**
+ * Represents XMPP Roster Item Exchange packets.<p>
+ * 
+ * The 'jabber:x:roster' namespace (which is not to be confused with the 'jabber:iq:roster' 
+ * namespace) is used to send roster items from one client to another. A roster item is sent by 
+ * adding to the &lt;message/&gt; element an &lt;x/&gt; child scoped by the 'jabber:x:roster' namespace. This 
+ * &lt;x/&gt; element may contain one or more &lt;item/&gt; children (one for each roster item to be sent).<p>
+ * 
+ * Each &lt;item/&gt; element may possess the following attributes:<p>
+ * 
+ * &lt;jid/&gt; -- The id of the contact being sent. This attribute is required.<br>
+ * &lt;name/&gt; -- A natural-language nickname for the contact. This attribute is optional.<p>
+ * 
+ * Each &lt;item/&gt; element may also contain one or more &lt;group/&gt; children specifying the 
+ * natural-language name of a user-specified group, for the purpose of categorizing this contact 
+ * into one or more roster groups.
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchange implements PacketExtension {
+
+    private List remoteRosterEntries = new ArrayList();
+
+    /**
+     * Creates a new empty roster exchange package.
+     *
+     */
+    public RosterExchange() {
+        super();
+    }
+
+    /**
+     * Creates a new roster exchange package with the entries specified in roster.
+     *
+     * @param roster the roster to send to other XMPP entity.
+     */
+    public RosterExchange(Roster roster) {
+        // Add all the roster entries to the new RosterExchange 
+        for (Iterator rosterEntries = roster.getEntries(); rosterEntries.hasNext();) {
+            this.addRosterEntry((RosterEntry) rosterEntries.next());
+        }
+    }
+
+    /**
+     * Adds a roster entry to the packet.
+     *
+     * @param rosterEntry a roster entry to add.
+     */
+    public void addRosterEntry(RosterEntry rosterEntry) {
+		// Obtain a String[] from the roster entry groups name 
+		ArrayList groupNamesList = new ArrayList();
+		String[] groupNames;
+		for (Iterator groups = rosterEntry.getGroups(); groups.hasNext();) {
+			groupNamesList.add(((RosterGroup) groups.next()).getName());
+		}
+		groupNames = (String[]) groupNamesList.toArray(new String[groupNamesList.size()]);
+
+        // Create a new Entry based on the rosterEntry and add it to the packet
+        RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(), rosterEntry.getName(), groupNames);
+		
+        addRosterEntry(remoteRosterEntry);
+    }
+
+    /**
+     * Adds a remote roster entry to the packet.
+     *
+     * @param remoteRosterEntry a remote roster entry to add.
+     */
+    public void addRosterEntry(RemoteRosterEntry remoteRosterEntry) {
+        synchronized (remoteRosterEntries) {
+            remoteRosterEntries.add(remoteRosterEntry);
+        }
+    }
+    
+    /**
+    * Returns the XML element name of the extension sub-packet root element.
+    * Always returns "x"
+    *
+    * @return the XML element name of the packet extension.
+    */
+    public String getElementName() {
+        return "x";
+    }
+
+    /** 
+     * Returns the XML namespace of the extension sub-packet root element.
+     * According the specification the namespace is always "jabber:x:roster"
+     * (which is not to be confused with the 'jabber:iq:roster' namespace
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return "jabber:x:roster";
+    }
+
+    /**
+     * Returns an Iterator for the roster entries in the packet.
+     *
+     * @return an Iterator for the roster entries in the packet.
+     */
+    public Iterator getRosterEntries() {
+        synchronized (remoteRosterEntries) {
+            List entries = Collections.unmodifiableList(new ArrayList(remoteRosterEntries));
+            return entries.iterator();
+        }
+    }
+
+    /**
+     * Returns a count of the entries in the roster exchange.
+     *
+     * @return the number of entries in the roster exchange.
+     */
+    public int getEntryCount() {
+        return remoteRosterEntries.size();
+    }
+
+    /**
+     * Returns the XML representation of a Roster Item Exchange according the specification.
+     * 
+     * Usually the XML representation will be inside of a Message XML representation like
+     * in the following example:
+     * <pre>
+     * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+     *     &lt;subject&gt;Any subject you want&lt;/subject&gt;
+     *     &lt;body&gt;This message contains roster items.&lt;/body&gt;
+     *     &lt;x xmlns="jabber:x:roster"&gt;
+     *         &lt;item jid="gato1@gato.home"/&gt;
+     *         &lt;item jid="gato2@gato.home"/&gt;
+     *     &lt;/x&gt;
+     * &lt;/message&gt;
+     * </pre>
+     * 
+     */
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        // Loop through all roster entries and append them to the string buffer
+        for (Iterator i = getRosterEntries(); i.hasNext();) {
+            RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) i.next();
+            buf.append(remoteRosterEntry.toXML());
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java
new file mode 100644
index 000000000..9ceea620f
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Time.java
@@ -0,0 +1,196 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+import java.util.*;
+import java.text.SimpleDateFormat;
+import java.text.DateFormat;
+
+/**
+ * A Time IQ packet, which is used by XMPP clients to exchange their respective local
+ * times. Clients that wish to fully support the entitity time protocol should register
+ * a PacketListener for incoming time requests that then respond with the local time.
+ * This class can be used to request the time from other clients, such as in the
+ * following code snippet:
+ *
+ * <pre>
+ * // Request the time from a remote user.
+ * Time timeRequest = new Time();
+ * timeRequest.setType(IQ.Type.GET);
+ * timeRequest.setTo(someUser@example.com);
+ *
+ * // Create a packet collector to listen for a response.
+ * PacketCollector collector = con.createPacketCollector(
+ *                new PacketIDFilter(timeRequest.getPacketID()));
+ *
+ * con.sendPacket(timeRequest);
+ *
+ * // Wait up to 5 seconds for a result.
+ * IQ result = (IQ)collector.nextResult(5000);
+ * if (result != null && result.getType() == IQ.Type.RESULT) {
+ *     Time timeResult = (Time)result;
+ *     // Do something with result...
+ * }</pre><p>
+ *
+ * Warning: this is an non-standard protocol documented by
+ * <a href="http://www.jabber.org/jeps/jep-0090.html">JEP-90</a>. Because this is a
+ * non-standard protocol, it is subject to change.
+ *
+ * @author Matt Tucker
+ */
+public class Time extends IQ {
+
+    private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+    private static DateFormat displayFormat = DateFormat.getDateTimeInstance();
+
+    private String utc = null;
+    private String tz = null;
+    private String display = null;
+
+    /**
+     * Creates a new Time instance with empty values for all fields.
+     */
+    public Time() {
+        this(Calendar.getInstance());
+    }
+
+    /**
+     * Creates a new Time instance using the specified calendar instance as
+     * the time value to send.
+     *
+     * @param cal the time value.
+     */
+    public Time(Calendar cal) {
+        TimeZone timeZone = cal.getTimeZone();
+        tz = cal.getTimeZone().getID();
+        display = displayFormat.format(cal.getTime());
+        // Convert local time to the UTC time.
+        utc = utcFormat.format(new Date(
+                cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis())));
+    }
+
+    /**
+     * Returns the local time or <tt>null</tt> if the time hasn't been set.
+     *
+     * @return the lcocal time.
+     */
+    public Date getTime() {
+        if (utc == null) {
+            return null;
+        }
+        Date date = null;
+        try {
+            Calendar cal = Calendar.getInstance();
+            // Convert the UTC time to local time.
+            cal.setTime(new Date(utcFormat.parse(utc).getTime() +
+                    cal.getTimeZone().getOffset(cal.getTimeInMillis())));
+            date = cal.getTime();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+    /**
+     * Sets the time using the local time.
+     *
+     * @param time the current local time.
+     */
+    public void setTime(Date time) {
+        // Convert local time to UTC time.
+        utc = utcFormat.format(new Date(
+                time.getTime() - TimeZone.getDefault().getOffset(time.getTime())));
+    }
+
+    /**
+     * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss.
+     *
+     * @return the time as a UTC formatted String.
+     */
+    public String getUtc() {
+        return utc;
+    }
+
+    /**
+     * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss.
+     *
+     * @param utc the time using a formatted String.
+     */
+    public void setUtc(String utc) {
+        this.utc = utc;
+
+    }
+
+    /**
+     * Returns the time zone.
+     *
+     * @return the time zone.
+     */
+    public String getTz() {
+        return tz;
+    }
+
+    /**
+     * Sets the time zone.
+     *
+     * @param tz the time zone.
+     */
+    public void setTz(String tz) {
+        this.tz = tz;
+    }
+
+    /**
+     * Returns the local (non-utc) time in human-friendly format.
+     *
+     * @return the local time in human-friendly format.
+     */
+    public String getDisplay() {
+        return display;
+    }
+
+    /**
+     * Sets the local time in human-friendly format.
+     *
+     * @param display the local time in human-friendly format.
+     */
+    public void setDisplay(String display) {
+        this.display = display;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"jabber:iq:time\">");
+        if (utc != null) {
+            buf.append("<utc>").append(utc).append("</utc>");
+        }
+        if (tz != null) {
+            buf.append("<tz>").append(tz).append("</tz>");
+        }
+        if (display != null) {
+            buf.append("<display>").append(display).append("</display>");
+        }
+        buf.append("</query>");
+        return buf.toString();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java
new file mode 100644
index 000000000..d6d475348
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/VCard.java
@@ -0,0 +1,646 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.XMPPError;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A VCard class for use with the
+ * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p>
+ *
+ * You should refer to the
+ * <a href="http://www.jabber.org/jeps/jep-0054.html" target="_blank">JEP-54 documentation</a>.<p>
+ *
+ * Please note that this class is incomplete but it does provide the most commonly found
+ * information in vCards. Also remember that VCard transfer is not a standard, and the protocol
+ * may change or be replaced.<p>
+ *
+ * <b>Usage:</b>
+ * <pre>
+ *
+ * // To save VCard:
+ *
+ * VCard vCard = new VCard();
+ * vCard.setFirstName("kir");
+ * vCard.setLastName("max");
+ * vCard.setEmailHome("foo@fee.bar");
+ * vCard.setJabberId("jabber@id.org");
+ * vCard.setOrganization("Jetbrains, s.r.o");
+ * vCard.setNickName("KIR");
+ *
+ * vCard.setField("TITLE", "Mr");
+ * vCard.setAddressFieldHome("STREET", "Some street");
+ * vCard.setAddressFieldWork("CTRY", "US");
+ * vCard.setPhoneWork("FAX", "3443233");
+ *
+ * vCard.save(connection);
+ *
+ * // To load VCard:
+ *
+ * VCard vCard = new VCard();
+ * vCard.load(conn); // load own VCard
+ * vCard.load(conn, "joe@foo.bar"); // load someone's VCard
+ * </pre>
+ *
+ * @author Kirill Maximov (kir@maxkir.com)
+ */
+public class VCard extends IQ {
+
+    /**
+     * Phone types:
+     * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF?
+     */
+    private Map homePhones = new HashMap();
+    private Map workPhones = new HashMap();
+
+
+    /**
+     * Address types:
+     * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?,
+     * REGION?, PCODE?, CTRY?
+     */
+    private Map homeAddr = new HashMap();
+    private Map workAddr = new HashMap();
+
+    private String firstName;
+    private String lastName;
+    private String middleName;
+
+    private String emailHome;
+    private String emailWork;
+
+    private String organization;
+    private String organizationUnit;
+
+    /**
+     * Such as DESC ROLE GEO etc.. see JEP-0054
+     */
+    private Map otherSimpleFields = new HashMap();
+
+    public VCard() {
+    }
+
+    /**
+     * Set generic VCard field.
+     *
+     * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ,
+     *        GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC.
+     */
+    public String getField(String field) {
+        return (String) otherSimpleFields.get(field);
+    }
+
+    /**
+     * Set generic VCard field.
+     *
+     * @param value value of field
+     * @param field field to set. See {@link #getField(String)}
+     * @see #getField(String)
+     */
+    public void setField(String field, String value) {
+        otherSimpleFields.put(field, value);
+    }
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getMiddleName() {
+        return middleName;
+    }
+
+    public void setMiddleName(String middleName) {
+        this.middleName = middleName;
+    }
+
+    public String getNickName() {
+        return (String) otherSimpleFields.get("NICKNAME");
+    }
+
+    public void setNickName(String nickName) {
+        otherSimpleFields.put("NICKNAME", nickName);
+    }
+
+    public String getEmailHome() {
+        return emailHome;
+    }
+
+    public void setEmailHome(String email) {
+        this.emailHome = email;
+    }
+
+    public String getEmailWork() {
+        return emailWork;
+    }
+
+    public void setEmailWork(String emailWork) {
+        this.emailWork = emailWork;
+    }
+
+    public String getJabberId() {
+        return (String) otherSimpleFields.get("JABBERID");
+    }
+
+    public void setJabberId(String jabberId) {
+        otherSimpleFields.put("JABBERID", jabberId);
+    }
+
+    public String getOrganization() {
+        return organization;
+    }
+
+    public void setOrganization(String organization) {
+        this.organization = organization;
+    }
+
+    public String getOrganizationUnit() {
+        return organizationUnit;
+    }
+
+    public void setOrganizationUnit(String organizationUnit) {
+        this.organizationUnit = organizationUnit;
+    }
+
+    /**
+     * Get home address field
+     *
+     * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+     *                  LOCALITY, REGION, PCODE, CTRY
+     */
+    public String getAddressFieldHome(String addrField) {
+        return (String) homeAddr.get(addrField);
+    }
+
+    /**
+     * Set home address field
+     *
+     * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+     *                  LOCALITY, REGION, PCODE, CTRY
+     */
+    public void setAddressFieldHome(String addrField, String value) {
+        homeAddr.put(addrField, value);
+    }
+
+    /**
+     * Get work address field
+     *
+     * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+     *                  LOCALITY, REGION, PCODE, CTRY
+     */
+    public String getAddressFieldWork(String addrField) {
+        return (String) workAddr.get(addrField);
+    }
+
+    /**
+     * Set work address field
+     *
+     * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET,
+     *                  LOCALITY, REGION, PCODE, CTRY
+     */
+    public void setAddressFieldWork(String addrField, String value) {
+        workAddr.put(addrField, value);
+    }
+
+
+    /**
+     * Set home phone number
+     *
+     * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+     * @param phoneNum  phone number
+     */
+    public void setPhoneHome(String phoneType, String phoneNum) {
+        homePhones.put(phoneType, phoneNum);
+    }
+
+    /**
+     * Get home phone number
+     *
+     * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+     */
+    public String getPhoneHome(String phoneType) {
+        return (String) homePhones.get(phoneType);
+    }
+
+    /**
+     * Set work phone number
+     *
+     * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+     * @param phoneNum  phone number
+     */
+    public void setPhoneWork(String phoneType, String phoneNum) {
+        workPhones.put(phoneType, phoneNum);
+    }
+
+    /**
+     * Get work phone number
+     *
+     * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF
+     */
+    public String getPhoneWork(String phoneType) {
+        return (String) workPhones.get(phoneType);
+    }
+
+    /**
+     * Save this vCard for the user connected by 'connection'. Connection should be authenticated
+     * and not anonymous.<p>
+     * <p/>
+     * NOTE: the method is asynchronous and does not wait for the returned value.
+     */
+    public void save(XMPPConnection connection) {
+        checkAuthenticated(connection);
+
+        setType(IQ.Type.SET);
+        setFrom(connection.getUser());
+        connection.sendPacket(this);
+    }
+
+    /**
+     * Load VCard information for a connected user. Connection should be authenticated
+     * and not anonymous.
+     */
+    public void load(XMPPConnection connection) throws XMPPException {
+        checkAuthenticated(connection);
+
+        setFrom(connection.getUser());
+        doLoad(connection, connection.getUser());
+    }
+
+    /**
+     * Load VCard information for a given user. Connection should be authenticated and not anonymous.
+     */
+    public void load(XMPPConnection connection, String user) throws XMPPException {
+        checkAuthenticated(connection);
+
+        setTo(user);
+        doLoad(connection, user);
+    }
+
+    private void doLoad(XMPPConnection connection, String user) throws XMPPException {
+        setType(Type.GET);
+        PacketCollector collector = connection.createPacketCollector(
+                new PacketIDFilter(getPacketID()));
+        connection.sendPacket(this);
+
+        VCard result = null;
+        try {
+            result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+            if (result == null) {
+                throw new XMPPException(new XMPPError(408, "Timeout getting VCard information"));
+            }
+            if (result.getError() != null) {
+                throw new XMPPException(result.getError());
+            }
+        } catch (ClassCastException e) {
+            System.out.println("No VCard for " + user);
+        }
+
+        copyFieldsFrom(result);
+    }
+
+    public String getChildElementXML() {
+        StringBuffer sb = new StringBuffer();
+        new VCardWriter(sb).write();
+        return sb.toString();
+    }
+
+    private void copyFieldsFrom(VCard result) {
+        if (result == null) result = new VCard();
+
+        Field[] fields = VCard.class.getDeclaredFields();
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            if (field.getDeclaringClass() == VCard.class &&
+                    !Modifier.isFinal(field.getModifiers())) {
+                try {
+                    field.setAccessible(true);
+                    field.set(this, field.get(result));
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("This cannot happen:" + field, e);
+                }
+            }
+        }
+    }
+
+    private void checkAuthenticated(XMPPConnection connection) {
+        if (connection == null) {
+            new IllegalArgumentException("No connection was provided");
+        }
+        if (!connection.isAuthenticated()) {
+            new IllegalArgumentException("Connection is not authenticated");
+        }
+        if (connection.isAnonymous()) {
+            new IllegalArgumentException("Connection cannot be anonymous");
+        }
+    }
+
+    private boolean hasContent() {
+        //noinspection OverlyComplexBooleanExpression
+        return hasNameField()
+                || hasOrganizationFields()
+                || emailHome != null
+                || emailWork != null
+                || otherSimpleFields.size() > 0
+                || homeAddr.size() > 0
+                || homePhones.size() > 0
+                || workAddr.size() > 0
+                || workPhones.size() > 0
+                ;
+    }
+
+    private boolean hasNameField() {
+        return firstName != null || lastName != null || middleName != null;
+    }
+
+    private boolean hasOrganizationFields() {
+        return organization != null || organizationUnit != null;
+    }
+
+    // Used in tests:
+
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        final VCard vCard = (VCard) o;
+
+        if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) {
+            return false;
+        }
+        if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) {
+            return false;
+        }
+        if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) {
+            return false;
+        }
+        if (!homeAddr.equals(vCard.homeAddr)) {
+            return false;
+        }
+        if (!homePhones.equals(vCard.homePhones)) {
+            return false;
+        }
+        if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) {
+            return false;
+        }
+        if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) {
+            return false;
+        }
+        if (organization != null ?
+                !organization.equals(vCard.organization) : vCard.organization != null) {
+            return false;
+        }
+        if (organizationUnit != null ?
+                !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) {
+            return false;
+        }
+        if (!otherSimpleFields.equals(vCard.otherSimpleFields)) {
+            return false;
+        }
+        if (!workAddr.equals(vCard.workAddr)) {
+            return false;
+        }
+        if (!workPhones.equals(vCard.workPhones)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public int hashCode() {
+        int result;
+        result = homePhones.hashCode();
+        result = 29 * result + workPhones.hashCode();
+        result = 29 * result + homeAddr.hashCode();
+        result = 29 * result + workAddr.hashCode();
+        result = 29 * result + (firstName != null ? firstName.hashCode() : 0);
+        result = 29 * result + (lastName != null ? lastName.hashCode() : 0);
+        result = 29 * result + (middleName != null ? middleName.hashCode() : 0);
+        result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0);
+        result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0);
+        result = 29 * result + (organization != null ? organization.hashCode() : 0);
+        result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0);
+        result = 29 * result + otherSimpleFields.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return getChildElementXML();
+    }
+
+    //==============================================================
+
+    private class VCardWriter {
+        private final StringBuffer sb;
+
+        VCardWriter(StringBuffer sb) {
+            this.sb = sb;
+        }
+
+        public void write() {
+            appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() {
+                public void addTagContent() {
+                    buildActualContent();
+                }
+            });
+        }
+
+        private void buildActualContent() {
+            if (hasNameField()) {
+                appendFN();
+                appendN();
+            }
+
+            appendOrganization();
+            appendGenericFields();
+
+            appendEmail(emailWork, "WORK");
+            appendEmail(emailHome, "HOME");
+
+            appendPhones(workPhones, "WORK");
+            appendPhones(homePhones, "HOME");
+
+            appendAddress(workAddr, "WORK");
+            appendAddress(homeAddr, "HOME");
+        }
+
+        private void appendEmail(final String email, final String type) {
+            if (email != null) {
+                appendTag("EMAIL", true, new ContentBuilder() {
+                    public void addTagContent() {
+                        appendEmptyTag(type);
+                        appendEmptyTag("INTERNET");
+                        appendEmptyTag("PREF");
+                        appendTag("USERID", email);
+                    }
+                });
+            }
+        }
+
+        private void appendPhones(Map phones, final String code) {
+            Iterator it = phones.entrySet().iterator();
+            while (it.hasNext()) {
+                final Map.Entry entry = (Map.Entry) it.next();
+                appendTag("TEL", true, new ContentBuilder() {
+                    public void addTagContent() {
+                        appendEmptyTag(entry.getKey());
+                        appendEmptyTag(code);
+                        appendTag("NUMBER", (String) entry.getValue());
+                    }
+                });
+            }
+        }
+
+        private void appendAddress(final Map addr, final String code) {
+            if (addr.size() > 0) {
+                appendTag("ADR", true, new ContentBuilder() {
+                    public void addTagContent() {
+                        appendEmptyTag(code);
+
+                        Iterator it = addr.entrySet().iterator();
+                        while (it.hasNext()) {
+                            final Map.Entry entry = (Map.Entry) it.next();
+                            appendTag((String) entry.getKey(), (String) entry.getValue());
+                        }
+                    }
+                });
+            }
+        }
+
+        private void appendEmptyTag(Object tag) {
+            sb.append('<').append(tag).append("/>");
+        }
+
+        private void appendGenericFields() {
+            Iterator it = otherSimpleFields.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry entry = (Map.Entry) it.next();
+                appendTag(entry.getKey().toString(), (String) entry.getValue());
+            }
+        }
+
+        private void appendOrganization() {
+            if (hasOrganizationFields()) {
+                appendTag("ORG", true, new ContentBuilder() {
+                    public void addTagContent() {
+                        appendTag("ORGNAME", organization);
+                        appendTag("ORGUNIT", organizationUnit);
+                    }
+                });
+            }
+        }
+
+        private void appendField(String tag) {
+            String value = (String) otherSimpleFields.get(tag);
+            appendTag(tag, value);
+        }
+
+        private void appendFN() {
+            final ContentBuilder contentBuilder = new ContentBuilder() {
+                public void addTagContent() {
+                    if (firstName != null) {
+                        sb.append(firstName + ' ');
+                    }
+                    if (middleName != null) {
+                        sb.append(middleName + ' ');
+                    }
+                    if (lastName != null) {
+                        sb.append(lastName);
+                    }
+                }
+            };
+            appendTag("FN", true, contentBuilder);
+        }
+
+        private void appendN() {
+            appendTag("N", true, new ContentBuilder() {
+                public void addTagContent() {
+                    appendTag("FAMILY", lastName);
+                    appendTag("GIVEN", firstName);
+                    appendTag("MIDDLE", middleName);
+                }
+            });
+        }
+
+        private void appendTag(String tag, String attr, String attrValue, boolean hasContent,
+                               ContentBuilder builder) {
+            sb.append('<').append(tag);
+            if (attr != null) {
+                sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\'');
+            }
+
+            if (hasContent) {
+                sb.append('>');
+                builder.addTagContent();
+                sb.append("</").append(tag).append(">\n");
+            } else {
+                sb.append("/>\n");
+            }
+        }
+
+        private void appendTag(String tag, boolean hasContent, ContentBuilder builder) {
+            appendTag(tag, null, null, hasContent, builder);
+        }
+
+        private void appendTag(String tag, final String tagText) {
+            if (tagText == null) return;
+            final ContentBuilder contentBuilder = new ContentBuilder() {
+                public void addTagContent() {
+                    sb.append(tagText.trim());
+                }
+            };
+            appendTag(tag, true, contentBuilder);
+        }
+
+    }
+
+    //==============================================================
+
+    private interface ContentBuilder {
+        void addTagContent();
+    }
+
+    //==============================================================
+}
+
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java
new file mode 100644
index 000000000..206208f3f
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/Version.java
@@ -0,0 +1,132 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * A Version IQ packet, which is used by XMPP clients to discover version information
+ * about the software running at another entity's JID.<p>
+ *
+ * An example to discover the version of the server:
+ * <pre>
+ * // Request the version from the server.
+ * Version versionRequest = new Version();
+ * timeRequest.setType(IQ.Type.GET);
+ * timeRequest.setTo("example.com");
+ *
+ * // Create a packet collector to listen for a response.
+ * PacketCollector collector = con.createPacketCollector(
+ *                new PacketIDFilter(versionRequest.getPacketID()));
+ *
+ * con.sendPacket(versionRequest);
+ *
+ * // Wait up to 5 seconds for a result.
+ * IQ result = (IQ)collector.nextResult(5000);
+ * if (result != null && result.getType() == IQ.Type.RESULT) {
+ *     Version versionResult = (Version)result;
+ *     // Do something with result...
+ * }</pre><p>
+ *
+ * @author Gaston Dombiak
+ */
+public class Version extends IQ {
+
+    private String name;
+    private String version;
+    private String os;
+
+    /**
+     * Returns the natural-language name of the software. This property will always be
+     * present in a result.
+     *
+     * @return the natural-language name of the software.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the natural-language name of the software. This message should only be
+     * invoked when parsing the XML and setting the property to a Version instance.
+     *
+     * @param name the natural-language name of the software.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the specific version of the software. This property will always be
+     * present in a result.
+     *
+     * @return the specific version of the software.
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the specific version of the software. This message should only be
+     * invoked when parsing the XML and setting the property to a Version instance.
+     *
+     * @param version the specific version of the software.
+     */
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    /**
+     * Returns the operating system of the queried entity. This property will always be
+     * present in a result.
+     *
+     * @return the operating system of the queried entity.
+     */
+    public String getOs() {
+        return os;
+    }
+
+    /**
+     * Sets the operating system of the queried entity. This message should only be
+     * invoked when parsing the XML and setting the property to a Version instance.
+     *
+     * @param os operating system of the queried entity.
+     */
+    public void setOs(String os) {
+        this.os = os;
+    }
+
+    public String getChildElementXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<query xmlns=\"jabber:iq:version\">");
+        if (name != null) {
+            buf.append("<name>").append(name).append("</name>");
+        }
+        if (version != null) {
+            buf.append("<version>").append(version).append("</version>");
+        }
+        if (os != null) {
+            buf.append("<os>").append(os).append("</os>");
+        }
+        buf.append("</query>");
+        return buf.toString();
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java b/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java
new file mode 100644
index 000000000..6bf266569
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/XHTMLExtension.java
@@ -0,0 +1,123 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * An XHTML sub-packet, which is used by XMPP clients to exchange formatted text. The XHTML 
+ * extension is only a subset of XHTML 1.0.<p>
+ * 
+ * The following link summarizes the requirements of XHTML IM:
+ * <a href="http://www.jabber.org/jeps/jep-0071.html#sect-id2598018">Valid tags</a>.<p>
+ * 
+ * Warning: this is an non-standard protocol documented by
+ * <a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a>. Because this is a
+ * non-standard protocol, it is subject to change.
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLExtension implements PacketExtension {
+
+    private List bodies = new ArrayList();
+
+    /**
+    * Returns the XML element name of the extension sub-packet root element.
+    * Always returns "html"
+    *
+    * @return the XML element name of the packet extension.
+    */
+    public String getElementName() {
+        return "html";
+    }
+
+    /** 
+     * Returns the XML namespace of the extension sub-packet root element.
+     * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im"
+     *
+     * @return the XML namespace of the packet extension.
+     */
+    public String getNamespace() {
+        return "http://jabber.org/protocol/xhtml-im";
+    }
+
+    /**
+     * Returns the XML representation of a XHTML extension according the specification.
+     * 
+     * Usually the XML representation will be inside of a Message XML representation like
+     * in the following example:
+     * <pre>
+     * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+     *     &lt;subject&gt;Any subject you want&lt;/subject&gt;
+     *     &lt;body&gt;This message contains something interesting.&lt;/body&gt;
+     *     &lt;html xmlns="http://jabber.org/protocol/xhtml-im"&gt;
+     *         &lt;body&gt;&lt;p style='font-size:large'&gt;This message contains something &lt;em&gt;interesting&lt;/em&gt;.&lt;/p&gt;&lt;/body&gt;
+     *     &lt;/html&gt;
+     * &lt;/message&gt;
+     * </pre>
+     * 
+     */
+    public String toXML() {
+        StringBuffer buf = new StringBuffer();
+        buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+            "\">");
+        // Loop through all the bodies and append them to the string buffer
+        for (Iterator i = getBodies(); i.hasNext();) {
+            buf.append((String) i.next());
+        }
+        buf.append("</").append(getElementName()).append(">");
+        return buf.toString();
+    }
+
+    /**
+     * Returns an Iterator for the bodies in the packet.
+     *
+     * @return an Iterator for the bodies in the packet.
+     */
+    public Iterator getBodies() {
+        synchronized (bodies) {
+            return Collections.unmodifiableList(new ArrayList(bodies)).iterator();
+        }
+    }
+
+    /**
+     * Adds a body to the packet.
+     *
+     * @param body the body to add.
+     */
+    public void addBody(String body) {
+        synchronized (bodies) {
+            bodies.add(body);
+        }
+    }
+
+    /**
+     * Returns a count of the bodies in the XHTML packet.
+     *
+     * @return the number of bodies in the XHTML packet.
+     */
+    public int getBodiesCount() {
+        return bodies.size();
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html
new file mode 100644
index 000000000..490d1d72d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/packet/package.html
@@ -0,0 +1 @@
+<body>XML packets that are part of the XMPP extension protocols.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java
new file mode 100644
index 000000000..325a9ce2d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DataFormProvider.java
@@ -0,0 +1,160 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.FormField;
+import org.jivesoftware.smackx.packet.DataForm;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The DataFormProvider parses DataForm packets.
+ * 
+ * @author Gaston Dombiak
+ */
+public class DataFormProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new DataFormProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor
+     */
+    public DataFormProvider() {
+    }
+
+    public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        StringBuffer buffer = null;
+        DataForm dataForm = new DataForm(parser.getAttributeValue("", "type"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("instructions")) { 
+                    dataForm.addInstruction(parser.nextText());
+                }
+                else if (parser.getName().equals("title")) {                    
+                    dataForm.setTitle(parser.nextText());
+                }
+                else if (parser.getName().equals("field")) {                    
+                    dataForm.addField(parseField(parser));
+                }
+                else if (parser.getName().equals("item")) {                    
+                    dataForm.addItem(parseItem(parser));
+                }
+                else if (parser.getName().equals("reported")) {                    
+                    dataForm.setReportedData(parseReported(parser));
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals(dataForm.getElementName())) {
+                    done = true;
+                }
+            }
+        }
+        return dataForm;
+    }
+
+    private FormField parseField(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        FormField formField = new FormField(parser.getAttributeValue("", "var"));
+        formField.setLabel(parser.getAttributeValue("", "label"));
+        formField.setType(parser.getAttributeValue("", "type"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("desc")) { 
+                    formField.setDescription(parser.nextText());
+                }
+                else if (parser.getName().equals("value")) {                    
+                    formField.addValue(parser.nextText());
+                }
+                else if (parser.getName().equals("required")) {                    
+                    formField.setRequired(true);
+                }
+                else if (parser.getName().equals("option")) {                    
+                    formField.addOption(parseOption(parser));
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("field")) {
+                    done = true;
+                }
+            }
+        }
+        return formField;
+    }
+
+    private DataForm.Item parseItem(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        List fields = new ArrayList();
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("field")) { 
+                    fields.add(parseField(parser));
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    done = true;
+                }
+            }
+        }
+        return new DataForm.Item(fields);
+    }
+
+    private DataForm.ReportedData parseReported(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        List fields = new ArrayList();
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("field")) { 
+                    fields.add(parseField(parser));
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("reported")) {
+                    done = true;
+                }
+            }
+        }
+        return new DataForm.ReportedData(fields);
+    }
+
+    private FormField.Option parseOption(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        FormField.Option option = null;
+        String label = parser.getAttributeValue("", "label");
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("value")) {
+                    option = new FormField.Option(label, parser.nextText());                     
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("option")) {
+                    done = true;
+                }
+            }
+        }
+        return option;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java
new file mode 100644
index 000000000..2065af6fe
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DelayInformationProvider.java
@@ -0,0 +1,71 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.packet.DelayInformation;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * The DelayInformationProvider parses DelayInformation packets.
+ *
+ * @author Gaston Dombiak
+ */
+public class DelayInformationProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new DeliveryInformationProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument
+     * constructor
+     */
+    public DelayInformationProvider() {
+    }
+
+    public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+        Date stamp = null;
+        try {
+            stamp = DelayInformation.UTC_FORMAT.parse(parser.getAttributeValue("", "stamp"));
+        } catch (ParseException e) {
+            // Try again but assuming that the date follows JEP-82 format
+            // (Jabber Date and Time Profiles) 
+            try {
+                stamp = DelayInformation.NEW_UTC_FORMAT
+                        .parse(parser.getAttributeValue("", "stamp"));
+            } catch (ParseException e1) {
+                // Last attempt. Try parsing the date assuming that it does not include milliseconds
+                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+                formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+                stamp = formatter.parse(parser.getAttributeValue("", "stamp"));
+            }
+        }
+        DelayInformation delayInformation = new DelayInformation(stamp);
+        delayInformation.setFrom(parser.getAttributeValue("", "from"));
+        delayInformation.setReason(parser.nextText());
+        return delayInformation;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java
new file mode 100644
index 000000000..cf13d63f5
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java
@@ -0,0 +1,83 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+* The DiscoverInfoProvider parses Service Discovery information packets.
+*
+* @author Gaston Dombiak
+*/
+public class DiscoverInfoProvider implements IQProvider {
+
+    public IQ parseIQ(XmlPullParser parser) throws Exception {
+        DiscoverInfo discoverInfo = new DiscoverInfo();
+        boolean done = false;
+        DiscoverInfo.Feature feature = null;
+        DiscoverInfo.Identity identity = null;
+        String category = "";
+        String name = "";
+        String type = "";
+        String variable = "";
+        discoverInfo.setNode(parser.getAttributeValue("", "node"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("identity")) {
+                    // Initialize the variables from the parsed XML
+                    category = parser.getAttributeValue("", "category");
+                    name = parser.getAttributeValue("", "name");
+                    type = parser.getAttributeValue("", "type");
+                }
+                else if (parser.getName().equals("feature")) {
+                    // Initialize the variables from the parsed XML
+                    variable = parser.getAttributeValue("", "var");
+                }
+                // Otherwise, it must be a packet extension.
+                else {
+                    discoverInfo.addExtension(PacketParserUtils.parsePacketExtension(parser
+                            .getName(), parser.getNamespace(), parser));
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("identity")) {
+                    // Create a new identity and add it to the discovered info.
+                    identity = new DiscoverInfo.Identity(category, name);
+                    identity.setType(type);
+                    discoverInfo.addIdentity(identity);
+                }
+                if (parser.getName().equals("feature")) {
+                    // Create a new feature and add it to the discovered info.
+                    discoverInfo.addFeature(variable);
+                }
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+
+        return discoverInfo;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java
new file mode 100644
index 000000000..34aae227b
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java
@@ -0,0 +1,71 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.packet.*;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+* The DiscoverInfoProvider parses Service Discovery items packets.
+*
+* @author Gaston Dombiak
+*/
+public class DiscoverItemsProvider implements IQProvider {
+
+    public IQ parseIQ(XmlPullParser parser) throws Exception {
+        DiscoverItems discoverItems = new DiscoverItems();
+        boolean done = false;
+        DiscoverItems.Item item = null;
+        String jid = "";
+        String name = "";
+        String action = "";
+        String node = "";
+        discoverItems.setNode(parser.getAttributeValue("", "node"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    // Initialize the variables from the parsed XML
+                    jid = parser.getAttributeValue("", "jid");
+                    name = parser.getAttributeValue("", "name");
+                    node = parser.getAttributeValue("", "node");
+                    action = parser.getAttributeValue("", "action");
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    // Create a new Item and add it to DiscoverItems.
+                    item = new DiscoverItems.Item(jid);
+                    item.setName(name);
+                    item.setNode(node);
+                    item.setAction(action);
+                    discoverItems.addItem(item);
+                }
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+
+        return discoverItems;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java
new file mode 100644
index 000000000..0f5f04f07
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCAdminProvider.java
@@ -0,0 +1,81 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.packet.MUCAdmin;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The MUCAdminProvider parses MUCAdmin packets. (@see MUCAdmin)
+ * 
+ * @author Gaston Dombiak
+ */
+public class MUCAdminProvider implements IQProvider {
+
+    public IQ parseIQ(XmlPullParser parser) throws Exception {
+        MUCAdmin mucAdmin = new MUCAdmin();
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    mucAdmin.addItem(parseItem(parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+
+        return mucAdmin;
+    }
+
+    private MUCAdmin.Item parseItem(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCAdmin.Item item =
+            new MUCAdmin.Item(
+                parser.getAttributeValue("", "affiliation"),
+                parser.getAttributeValue("", "role"));
+        item.setNick(parser.getAttributeValue("", "nick"));
+        item.setJid(parser.getAttributeValue("", "jid"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("actor")) {
+                    item.setActor(parser.getAttributeValue("", "jid"));
+                }
+                if (parser.getName().equals("reason")) {
+                    item.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    done = true;
+                }
+            }
+        }
+        return item;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java
new file mode 100644
index 000000000..aaa6afa88
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCOwnerProvider.java
@@ -0,0 +1,108 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.packet.MUCOwner;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The MUCOwnerProvider parses MUCOwner packets. (@see MUCOwner)
+ * 
+ * @author Gaston Dombiak
+ */
+public class MUCOwnerProvider implements IQProvider {
+
+    public IQ parseIQ(XmlPullParser parser) throws Exception {
+        MUCOwner mucOwner = new MUCOwner();
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                    mucOwner.addItem(parseItem(parser));
+                }
+                else if (parser.getName().equals("destroy")) {
+                    mucOwner.setDestroy(parseDestroy(parser));
+                }
+                // Otherwise, it must be a packet extension.
+                else {
+                    mucOwner.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(),
+                            parser.getNamespace(), parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("query")) {
+                    done = true;
+                }
+            }
+        }
+
+        return mucOwner;
+    }
+
+    private MUCOwner.Item parseItem(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCOwner.Item item = new MUCOwner.Item(parser.getAttributeValue("", "affiliation"));
+        item.setNick(parser.getAttributeValue("", "nick"));
+        item.setRole(parser.getAttributeValue("", "role"));
+        item.setJid(parser.getAttributeValue("", "jid"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("actor")) {
+                    item.setActor(parser.getAttributeValue("", "jid"));
+                }
+                if (parser.getName().equals("reason")) {
+                    item.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    done = true;
+                }
+            }
+        }
+        return item;
+    }
+
+    private MUCOwner.Destroy parseDestroy(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCOwner.Destroy destroy = new MUCOwner.Destroy();
+        destroy.setJid(parser.getAttributeValue("", "jid"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("reason")) {
+                    destroy.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("destroy")) {
+                    done = true;
+                }
+            }
+        }
+        return destroy;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java
new file mode 100644
index 000000000..25b6e8d6b
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MUCUserProvider.java
@@ -0,0 +1,174 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.provider.*;
+import org.jivesoftware.smackx.packet.*;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The MUCUserProvider parses packets with extended presence information about 
+ * roles and affiliations.
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCUserProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new MUCUserProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument 
+     * constructor
+     */
+    public MUCUserProvider() {
+    }
+
+    /**
+     * Parses a MUCUser packet (extension sub-packet).
+     *
+     * @param parser the XML parser, positioned at the starting element of the extension.
+     * @return a PacketExtension.
+     * @throws Exception if a parsing error occurs.
+     */
+    public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+        MUCUser mucUser = new MUCUser();
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("invite")) {
+                    mucUser.setInvite(parseInvite(parser));
+                }
+                if (parser.getName().equals("item")) {
+                    mucUser.setItem(parseItem(parser));
+                }
+                if (parser.getName().equals("password")) {
+                    mucUser.setPassword(parser.nextText());
+                }
+                if (parser.getName().equals("status")) {
+                    mucUser.setStatus(new MUCUser.Status(parser.getAttributeValue("", "code")));
+                }
+                if (parser.getName().equals("decline")) {
+                    mucUser.setDecline(parseDecline(parser));
+                }
+                if (parser.getName().equals("destroy")) {
+                    mucUser.setDestroy(parseDestroy(parser));
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("x")) {
+                    done = true;
+                }
+            }
+        }
+
+        return mucUser;
+    }
+
+    private MUCUser.Item parseItem(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCUser.Item item =
+            new MUCUser.Item(
+                parser.getAttributeValue("", "affiliation"),
+                parser.getAttributeValue("", "role"));
+        item.setNick(parser.getAttributeValue("", "nick"));
+        item.setJid(parser.getAttributeValue("", "jid"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("actor")) {
+                    item.setActor(parser.getAttributeValue("", "jid"));
+                }
+                if (parser.getName().equals("reason")) {
+                    item.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+                    done = true;
+                }
+            }
+        }
+        return item;
+    }
+
+    private MUCUser.Invite parseInvite(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCUser.Invite invite = new MUCUser.Invite();
+        invite.setFrom(parser.getAttributeValue("", "from"));
+        invite.setTo(parser.getAttributeValue("", "to"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("reason")) {
+                    invite.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("invite")) {
+                    done = true;
+                }
+            }
+        }
+        return invite;
+    }
+
+    private MUCUser.Decline parseDecline(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCUser.Decline decline = new MUCUser.Decline();
+        decline.setFrom(parser.getAttributeValue("", "from"));
+        decline.setTo(parser.getAttributeValue("", "to"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("reason")) {
+                    decline.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("decline")) {
+                    done = true;
+                }
+            }
+        }
+        return decline;
+    }
+
+    private MUCUser.Destroy parseDestroy(XmlPullParser parser) throws Exception {
+        boolean done = false;
+        MUCUser.Destroy destroy = new MUCUser.Destroy();
+        destroy.setJid(parser.getAttributeValue("", "jid"));
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("reason")) {
+                    destroy.setReason(parser.nextText());
+                }
+            }
+            else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("destroy")) {
+                    done = true;
+                }
+            }
+        }
+        return destroy;
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java
new file mode 100644
index 000000000..b93a0b9a4
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/MessageEventProvider.java
@@ -0,0 +1,77 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.packet.MessageEvent;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ *
+ * The MessageEventProvider parses Message Event packets.
+*
+ * @author Gaston Dombiak
+ */
+public class MessageEventProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new MessageEventProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor
+     */
+    public MessageEventProvider() {
+    }
+
+    /**
+     * Parses a MessageEvent packet (extension sub-packet).
+     *
+     * @param parser the XML parser, positioned at the starting element of the extension.
+     * @return a PacketExtension.
+     * @throws Exception if a parsing error occurs.
+     */
+    public PacketExtension parseExtension(XmlPullParser parser)
+        throws Exception {
+        MessageEvent messageEvent = new MessageEvent();
+        boolean done = false;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("id"))
+                    messageEvent.setPacketID(parser.nextText());
+                if (parser.getName().equals(MessageEvent.COMPOSING))
+                    messageEvent.setComposing(true);
+                if (parser.getName().equals(MessageEvent.DELIVERED))
+                    messageEvent.setDelivered(true);
+                if (parser.getName().equals(MessageEvent.DISPLAYED))
+                    messageEvent.setDisplayed(true);
+                if (parser.getName().equals(MessageEvent.OFFLINE))
+                    messageEvent.setOffline(true);
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("x")) {
+                    done = true;
+                }
+            }
+        }
+
+        return messageEvent;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java
new file mode 100644
index 000000000..7961da1bc
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/PrivateDataProvider.java
@@ -0,0 +1,46 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.jivesoftware.smackx.packet.PrivateData;
+
+/**
+ * An interface for parsing custom private data. Each PrivateDataProvider must
+ * be registered with the PrivateDataManager class for it to be used. Every implementation
+ * of this interface <b>must</b> have a public, no-argument constructor.
+ *
+ * @author Matt Tucker
+ */
+public interface PrivateDataProvider {
+
+    /**
+     * Parse the private data sub-document and create a PrivateData instance. At the
+     * beginning of the method call, the xml parser will be positioned at the opening
+     * tag of the private data child element. At the end of the method call, the parser
+     * <b>must</b> be positioned on the closing tag of the child element.
+     *
+     * @param parser an XML parser.
+     * @return a new PrivateData instance.
+     * @throws Exception if an error occurs parsing the XML.
+     */
+    public PrivateData parsePrivateData(XmlPullParser parser) throws Exception;
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java
new file mode 100644
index 000000000..956e1328d
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/RosterExchangeProvider.java
@@ -0,0 +1,90 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import java.util.ArrayList;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.*;
+import org.jivesoftware.smackx.packet.*;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ *
+ * The RosterExchangeProvider parses RosterExchange packets.
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchangeProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new RosterExchangeProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor
+     */
+    public RosterExchangeProvider() {
+    }
+
+    /**
+     * Parses a RosterExchange packet (extension sub-packet).
+     *
+     * @param parser the XML parser, positioned at the starting element of the extension.
+     * @return a PacketExtension.
+     * @throws Exception if a parsing error occurs.
+     */
+    public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+
+        RosterExchange rosterExchange = new RosterExchange();
+        boolean done = false;
+        RemoteRosterEntry remoteRosterEntry = null;
+		String jid = "";
+		String name = "";
+		ArrayList groupsName = new ArrayList();
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("item")) {
+                	// Reset this variable since they are optional for each item
+					groupsName = new ArrayList();
+					// Initialize the variables from the parsed XML
+                    jid = parser.getAttributeValue("", "jid");
+                    name = parser.getAttributeValue("", "name");
+                }
+                if (parser.getName().equals("group")) {
+					groupsName.add(parser.nextText());
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("item")) {
+					// Create packet.
+					remoteRosterEntry = new RemoteRosterEntry(jid, name, (String[]) groupsName.toArray(new String[groupsName.size()]));
+                    rosterExchange.addRosterEntry(remoteRosterEntry);
+                }
+                if (parser.getName().equals("x")) {
+                    done = true;
+                }
+            }
+        }
+
+        return rosterExchange;
+
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java
new file mode 100644
index 000000000..085832f8a
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/VCardProvider.java
@@ -0,0 +1,209 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smackx.packet.VCard;
+import org.w3c.dom.*;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Gaston
+ * Date: Jun 18, 2005
+ * Time: 1:00:57 AM
+ * To change this template use File | Settings | File Templates.
+ */
+public class VCardProvider implements IQProvider {
+
+  public IQ parseIQ(XmlPullParser parser) throws Exception {
+      StringBuffer sb = new StringBuffer();
+      try {
+          int event = parser.getEventType();
+          // get the content
+          while (true) {
+              switch (event) {
+                  case XmlPullParser.TEXT:
+                      sb.append(parser.getText());
+                      break;
+                  case XmlPullParser.START_TAG:
+                      sb.append('<' + parser.getName() + '>');
+                      break;
+                  case XmlPullParser.END_TAG:
+                      sb.append("</" + parser.getName() + '>');
+                      break;
+                  default:
+              }
+
+              if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break;
+
+              event = parser.next();
+          }
+      } catch (XmlPullParserException e) {
+          e.printStackTrace();
+      } catch (IOException e) {
+          e.printStackTrace();
+      }
+
+      String xmlText = sb.toString();
+      VCard vCard = new VCard();
+      try {
+          DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+          DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+          Document document = documentBuilder.parse(new ByteArrayInputStream(xmlText.getBytes()));
+
+          new VCardReader(vCard, document).initializeFields();
+
+      } catch (Exception e) {
+          e.printStackTrace(System.err);
+      }
+      return vCard;
+  }
+
+    private class VCardReader {
+        private final VCard vCard;
+        private final Document document;
+
+        VCardReader(VCard vCard, Document document) {
+            this.vCard = vCard;
+            this.document = document;
+        }
+
+        public void initializeFields() {
+            vCard.setFirstName(getTagContents("GIVEN"));
+            vCard.setLastName(getTagContents("FAMILY"));
+            vCard.setMiddleName(getTagContents("MIDDLE"));
+
+            setupEmails();
+
+            vCard.setOrganization(getTagContents("ORGNAME"));
+            vCard.setOrganizationUnit(getTagContents("ORGUNIT"));
+
+            setupSimpleFields();
+            setupPhones("WORK", true);
+            setupPhones("HOME", false);
+
+            setupAddress("WORK", true);
+            setupAddress("HOME", false);
+        }
+
+        private void setupEmails() {
+            NodeList nodes = document.getElementsByTagName("USERID");
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Element element = (Element) nodes.item(i);
+                if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) {
+                    vCard.setEmailWork(getTextContent(element));
+                } else {
+                    vCard.setEmailHome(getTextContent(element));
+                }
+            }
+        }
+
+        private void setupPhones(String type, boolean work) {
+            NodeList allPhones = document.getElementsByTagName("TEL");
+            for (int i = 0; i < allPhones.getLength(); i++) {
+                Element node = (Element) allPhones.item(i);
+                if (type.equals(node.getChildNodes().item(1).getNodeName())) {
+                    String code = node.getFirstChild().getNodeName();
+                    String value = getTextContent(node.getChildNodes().item(2));
+                    if (work) {
+                        vCard.setPhoneWork(code, value);
+                    }
+                    else {
+                        vCard.setPhoneHome(code, value);
+                    }
+                }
+            }
+        }
+
+        private void setupAddress(String type, boolean work) {
+            NodeList allAddresses = document.getElementsByTagName("ADR");
+            for (int i = 0; i < allAddresses.getLength(); i++) {
+                Element node = (Element) allAddresses.item(i);
+                NodeList childNodes = node.getChildNodes();
+                if (type.equals(childNodes.item(0).getNodeName())) {
+                    for (int j = 1; j < childNodes.getLength(); j++) {
+                        Node item = childNodes.item(j);
+                        if (item instanceof Element) {
+                            if (work) {
+                                vCard.setAddressFieldWork(item.getNodeName(), getTextContent(item));
+                            }
+                            else {
+                                vCard.setAddressFieldHome(item.getNodeName(), getTextContent(item));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        private String getTagContents(String tag) {
+            NodeList nodes = document.getElementsByTagName(tag);
+            if (nodes.getLength() == 1) {
+                return getTextContent(nodes.item(0));
+            }
+            return null;
+        }
+
+        private void setupSimpleFields() {
+            NodeList childNodes = document.getDocumentElement().getChildNodes();
+            for (int i = 0; i < childNodes.getLength(); i++) {
+                Node node = childNodes.item(i);
+                if (node instanceof Element) {
+                    Element element = (Element) node;
+                    if ("FN".equals(element.getNodeName())) continue;
+
+                    if (element.getChildNodes().getLength() == 0) {
+                        vCard.setField(element.getNodeName(), "");
+                    } else if (element.getChildNodes().getLength() == 1 &&
+                            element.getChildNodes().item(0) instanceof Text) {
+                        vCard.setField(element.getNodeName(), getTextContent(element));
+                    }
+                }
+            }
+        }
+
+        private String getTextContent(Node node) {
+            StringBuffer result = new StringBuffer();
+            appendText(result, node);
+            return result.toString();
+        }
+
+        private void appendText(StringBuffer result, Node node) {
+            NodeList childNodes = node.getChildNodes();
+            for (int i = 0; i < childNodes.getLength(); i++) {
+                Node nd = childNodes.item(i);
+                String nodeValue = nd.getNodeValue();
+                if (nodeValue != null) {
+                    result.append(nodeValue);
+                }
+                appendText(result, nd);
+            }
+        }
+    }
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java b/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java
new file mode 100644
index 000000000..7970c7907
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java
@@ -0,0 +1,78 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.packet.XHTMLExtension;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The XHTMLExtensionProvider parses XHTML packets.
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLExtensionProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new XHTMLExtensionProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor
+     */
+    public XHTMLExtensionProvider() {
+    }
+
+    /**
+     * Parses a XHTMLExtension packet (extension sub-packet).
+     *
+     * @param parser the XML parser, positioned at the starting element of the extension.
+     * @return a PacketExtension.
+     * @throws Exception if a parsing error occurs.
+     */
+    public PacketExtension parseExtension(XmlPullParser parser)
+        throws Exception {
+        XHTMLExtension xhtmlExtension = new XHTMLExtension();
+        boolean done = false;
+        StringBuffer buffer = new StringBuffer();;
+        while (!done) {
+            int eventType = parser.next();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (parser.getName().equals("body")) 
+                    buffer = new StringBuffer();
+                buffer.append(parser.getText());
+            } else if (eventType == XmlPullParser.TEXT) {
+                if (buffer != null) buffer.append(parser.getText());
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (parser.getName().equals("body")) {
+                    buffer.append(parser.getText());
+                    xhtmlExtension.addBody(buffer.toString());
+                }
+                else if (parser.getName().equals(xhtmlExtension.getElementName())) {
+                    done = true;
+                }
+                else 
+                    buffer.append(parser.getText());
+            }
+        }
+
+        return xhtmlExtension;
+    }
+
+}
diff --git a/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html b/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html
new file mode 100644
index 000000000..962ba6372
--- /dev/null
+++ b/CopyOftrunk/source/org/jivesoftware/smackx/provider/package.html
@@ -0,0 +1 @@
+<body>Provides pluggable parsing logic for Smack extensions.</body>
\ No newline at end of file
diff --git a/CopyOftrunk/source/overview.html b/CopyOftrunk/source/overview.html
new file mode 100644
index 000000000..49a6ce41e
--- /dev/null
+++ b/CopyOftrunk/source/overview.html
@@ -0,0 +1,4 @@
+<body>API specification for <a href="http://www.jivesoftware.org/smack">Smack</a>, an Open Source XMPP client library.
+<p>
+The {@link org.jivesoftware.smack.XMPPConnection} class is the main entry point for the API.
+</body>
diff --git a/CopyOftrunk/test/config/test-case.xml b/CopyOftrunk/test/config/test-case.xml
new file mode 100644
index 000000000..bf14215a6
--- /dev/null
+++ b/CopyOftrunk/test/config/test-case.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<!-- Default configuration for Smack test cases. -->
+<testcase>
+
+    <!-- Host and port of the XMPP server to use -->
+    <host>gato.dyndns.org</host>
+    <port>5222</port>
+
+    <!-- Chat and MUC domain names to use -->
+    <chat>chat.gato.dyndns.org</chat>
+    <muc>conference.gato.dyndns.org</muc>
+
+</testcase>
\ No newline at end of file
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java b/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java
new file mode 100644
index 000000000..c678592ef
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/ChatTest.java
@@ -0,0 +1,131 @@
+/**
+* $RCSfile$
+* $Revision$
+* $Date$
+*
+* Copyright (C) 2002-2003 Jive Software. All rights reserved.
+* ====================================================================
+* The Jive Software License (based on Apache Software License, Version 1.1)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in
+*    the documentation and/or other materials provided with the
+*    distribution.
+*
+* 3. The end-user documentation included with the redistribution,
+*    if any, must include the following acknowledgment:
+*       "This product includes software developed by
+*        Jive Software (http://www.jivesoftware.com)."
+*    Alternately, this acknowledgment may appear in the software itself,
+*    if and wherever such third-party acknowledgments normally appear.
+*
+* 4. The names "Smack" and "Jive Software" must not be used to
+*    endorse or promote products derived from this software without
+*    prior written permission. For written permission, please
+*    contact webmaster@jivesoftware.com.
+*
+* 5. Products derived from this software may not be called "Smack",
+*    nor may "Smack" appear in their name, without prior written
+*    permission of Jive Software.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*/
+
+package org.jivesoftware.smack;
+
+import java.util.Date;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+
+/**
+ * Tests the chat functionality.
+ * 
+ * @author Gaston Dombiak
+ */
+public class ChatTest extends SmackTestCase {
+
+    /**
+     * Constructor for ChatTest.
+     * @param arg0
+     */
+    public ChatTest(String arg0) {
+        super(arg0);
+    }
+
+    public void testProperties() {
+        try {
+            Chat newChat = getConnection(0).createChat(getFullJID(1));
+            Chat newChat2 = new Chat(getConnection(1), getFullJID(0), newChat.getThreadID());
+
+            Message msg = newChat.createMessage();
+
+            msg.setSubject("Subject of the chat");
+            msg.setBody("Body of the chat");
+            msg.setProperty("favoriteColor", "red");
+            msg.setProperty("age", 30);
+            msg.setProperty("distance", 30f);
+            msg.setProperty("weight", 30d);
+            msg.setProperty("male", true);
+            msg.setProperty("birthdate", new Date());
+            newChat.sendMessage(msg);
+
+            Message msg2 = newChat2.nextMessage(2000);
+            assertNotNull("No message was received", msg2);
+            assertEquals("Subjects are different", msg.getSubject(), msg2.getSubject());
+            assertEquals("Bodies are different", msg.getBody(), msg2.getBody());
+            assertEquals(
+                "favoriteColors are different",
+                msg.getProperty("favoriteColor"),
+                msg2.getProperty("favoriteColor"));
+            assertEquals(
+                "ages are different",
+                msg.getProperty("age"),
+                msg2.getProperty("age"));
+            assertEquals(
+                "distances are different",
+                msg.getProperty("distance"),
+                msg2.getProperty("distance"));
+            assertEquals(
+                "weights are different",
+                msg.getProperty("weight"),
+                msg2.getProperty("weight"));
+            assertEquals(
+                "males are different",
+                msg.getProperty("male"),
+                msg2.getProperty("male"));
+            assertEquals(
+                "birthdates are different",
+                msg.getProperty("birthdate"),
+                msg2.getProperty("birthdate"));
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java b/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java
new file mode 100644
index 000000000..4885298ca
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/FloodTest.java
@@ -0,0 +1,102 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Simple test to measure server performance.
+ *
+ * @author Gaston Dombiak
+ */
+public class FloodTest extends SmackTestCase {
+
+    public FloodTest(String arg0) {
+        super(arg0);
+    }
+
+    public void testMessageFlood() {
+        try {
+            Chat chat11 = getConnection(0).createChat(getBareJID(1));
+            Chat chat12 = new Chat(getConnection(1), getBareJID(0), chat11.getThreadID());
+
+            Chat chat21 = getConnection(0).createChat(getBareJID(2));
+            Chat chat22 = new Chat(getConnection(2), getBareJID(0), chat21.getThreadID());
+
+            Chat chat31 = getConnection(0).createChat(getBareJID(3));
+            Chat chat32 = new Chat(getConnection(3), getBareJID(0), chat31.getThreadID());
+
+            for (int i=0; i<500; i++) {
+                chat11.sendMessage("Hello_1" + i);
+                chat21.sendMessage("Hello_2" + i);
+                chat31.sendMessage("Hello_3" + i);
+            }
+            for (int i=0; i<500; i++) {
+                assertNotNull("Some message was lost (" + i + ")", chat12.nextMessage(1000));
+                assertNotNull("Some message was lost (" + i + ")", chat22.nextMessage(1000));
+                assertNotNull("Some message was lost (" + i + ")", chat32.nextMessage(1000));
+            }
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /*public void testMUCFlood() {
+        try {
+            int floodNumber = 50000;
+            MultiUserChat chat = new MultiUserChat(getConnection(0), "myroom@" + getMUCDomain());
+            chat.create("phatom");
+            chat.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
+
+            MultiUserChat chat2 = new MultiUserChat(getConnection(1), "myroom@" + getMUCDomain());
+            chat2.join("christine");
+
+            for (int i=0; i<floodNumber; i++)
+            {
+                chat.sendMessage("hi");
+            }
+
+            Thread.sleep(200);
+
+            for (int i=0; i<floodNumber; i++)
+            {
+                if (i % 100 == 0) {
+                    System.out.println(i);
+                }
+                assertNotNull("Received " + i + " of " + floodNumber + " messages",
+                        chat2.nextMessage(SmackConfiguration.getPacketReplyTimeout()));
+            }
+
+            chat.leave();
+            //chat2.leave();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }*/
+
+    protected int getMaxConnections() {
+        return 4;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java b/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java
new file mode 100644
index 000000000..3db04417e
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/IQTest.java
@@ -0,0 +1,78 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.AndFilter;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+
+/**
+ * Ensure that the server is handling IQ packets correctly.
+ *
+ * @author Gaston Dombiak
+ */
+public class IQTest extends SmackTestCase {
+
+    public IQTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Check that the server responds a 503 error code when the client sends an IQ packet with an
+     * invalid namespace.
+     */
+    public void testInvalidNamespace() {
+        IQ iq = new IQ() {
+            public String getChildElementXML() {
+                StringBuffer buf = new StringBuffer();
+                buf.append("<query xmlns=\"jabber:iq:anything\">");
+                buf.append("</query>");
+                return buf.toString();
+            }
+        };
+
+        PacketFilter filter = new AndFilter(new PacketIDFilter(iq.getPacketID()),
+                new PacketTypeFilter(IQ.class));
+        PacketCollector collector = getConnection(0).createPacketCollector(filter);
+        // Send the iq packet with an invalid namespace
+        getConnection(0).sendPacket(iq);
+
+        IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        // Stop queuing results
+        collector.cancel();
+        if (result == null) {
+            fail("No response from server");
+        }
+        else if (result.getType() != IQ.Type.ERROR) {
+            fail("The server didn't reply with an error packet");
+        }
+        else {
+            assertEquals("Server answered an incorrect error code", 503, result.getError().getCode());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 1;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java b/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java
new file mode 100644
index 000000000..3850177d5
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/LoginTest.java
@@ -0,0 +1,104 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2004 Jive Software. All rights reserved.
+ *
+ * This software is published under the terms of the GNU Public License (GPL),
+ * a copy of which is included in this distribution.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Includes set of login tests. 
+ *
+ * @author Gaston Dombiak
+ */
+public class LoginTest extends SmackTestCase {
+
+    public LoginTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Check that the server is returning the correct error when trying to login using an invalid
+     * (i.e. non-existent) user.
+     */
+    public void testInvalidLogin() {
+        try {
+            XMPPConnection connection = new XMPPConnection(getHost(), getPort());
+            try {
+                // Login with an invalid user
+                connection.login("invaliduser" , "invalidpass");
+                connection.close();
+                fail("Invalid user was able to log into the server");
+            }
+            catch (XMPPException e) {
+                assertEquals("Incorrect error code while login with an invalid user", 401,
+                        e.getXMPPError().getCode());
+            }
+            // Wait here while trying tests with exodus
+            //Thread.sleep(300);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Check that the server handles anonymous users correctly.
+     */
+    public void testAnonymousLogin() {
+        try {
+            XMPPConnection conn1 = new XMPPConnection(getHost(), getPort());
+            XMPPConnection conn2 = new XMPPConnection(getHost(), getPort());
+            try {
+                // Try to login anonymously
+                conn1.loginAnonymously();
+                conn2.loginAnonymously();
+            }
+            catch (XMPPException e) {
+                e.printStackTrace();
+                fail(e.getMessage());
+            }
+            // Close the connection
+            conn1.close();
+            conn2.close();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Check that the server does not allow to log in without specifying a resource.
+     */
+    public void testLoginWithNoResource() {
+        try {
+            XMPPConnection conn = new XMPPConnection(getHost(), getPort());
+            try {
+                conn.getAccountManager().createAccount("user_1", "user_1");
+            } catch (XMPPException e) {
+                // Do nothing if the accout already exists
+                if (e.getXMPPError().getCode() != 409) {
+                    throw e;
+                }
+            }
+            conn.login("user_1", "user_1", null);
+            fail("User with no resource was able to log into the server");
+
+        } catch (XMPPException e) {
+            assertEquals("Wrong error code returned", 406, e.getXMPPError().getCode());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 0;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java b/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java
new file mode 100644
index 000000000..418143e52
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/MessageTest.java
@@ -0,0 +1,139 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+
+/**
+ * Tests sending messages to other clients.
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageTest extends SmackTestCase {
+
+    public MessageTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Check that when a client becomes unavailable all messages sent to the client are stored offline. So that when
+     * the client becomes available again the offline messages are received.
+     */
+    public void testOfflineMessage() {
+        // Make user2 unavailable
+        getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE));
+
+        try {
+            Thread.sleep(500);
+
+            // User1 sends some messages to User2 which is not available at the moment
+            Chat chat = getConnection(0).createChat(getBareJID(1));
+            chat.sendMessage("Test 1");
+            chat.sendMessage("Test 2");
+
+            Thread.sleep(500);
+
+            // User2 becomes available again
+            PacketCollector collector = getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.CHAT));
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE));
+
+            // Check that offline messages are retrieved by user2 which is now available
+            Message message = (Message) collector.nextResult(2500);
+            assertNotNull(message);
+            message = (Message) collector.nextResult(2000);
+            assertNotNull(message);
+            message = (Message) collector.nextResult(1000);
+            assertNull(message);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Check that two clients are able to send messages with a body of 4K characters and their
+     * connections are not being closed.
+     */
+    public void testHugeMessage() {
+        // User2 becomes available again
+        PacketCollector collector = getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.CHAT));
+
+        // Create message with a body of 4K characters
+        Message msg = new Message(getFullJID(1), Message.Type.CHAT);
+        StringBuffer sb = new StringBuffer(5000);
+        for (int i=0; i<=4000; i++) {
+            sb.append("X");
+        }
+        msg.setBody(sb.toString());
+
+        // Send the first message
+        getConnection(0).sendPacket(msg);
+        // Check that the connection that sent the message is still connected
+        assertTrue("Connection was closed", getConnection(0).isConnected());
+        // Check that the message was received
+        Message rcv = (Message) collector.nextResult(1000);
+        assertNotNull("No Message was received", rcv);
+
+        // Send the second message
+        getConnection(0).sendPacket(msg);
+        // Check that the connection that sent the message is still connected
+        assertTrue("Connection was closed", getConnection(0).isConnected());
+        // Check that the second message was received
+        rcv = (Message) collector.nextResult(1000);
+        assertNotNull("No Message was received", rcv);
+
+        // Try now sending huge messages over an SSL connection
+        XMPPConnection conn = null;
+        try {
+            conn = new SSLXMPPConnection(getHost());
+            conn.login(getUsername(0), getUsername(0), "Other resource");
+
+            // Send the first message
+            conn.sendPacket(msg);
+            // Check that the connection that sent the message is still connected
+            assertTrue("Connection was closed", conn.isConnected());
+            // Check that the message was received
+            rcv = (Message) collector.nextResult(1000);
+            assertNotNull("No Message was received", rcv);
+        } catch (XMPPException e) {
+            fail(e.getMessage());
+        }
+        finally {
+            if (conn != null) {
+                conn.close();
+            }
+        }
+
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+    protected void setUp() throws Exception {
+        XMPPConnection.DEBUG_ENABLED = false;
+        super.setUp();
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java b/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java
new file mode 100644
index 000000000..884a97142
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/MessengerLoginTest.java
@@ -0,0 +1,101 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+package org.jivesoftware.smack;
+
+import junit.framework.TestCase;
+
+public class MessengerLoginTest extends TestCase {
+
+    private String host;
+    private int port;
+
+    public void setUp() {
+        // override default settings from system properties
+        if (System.getProperty("smack.test.host") != null) {
+            host = System.getProperty("smack.test.host");
+        }
+        if (System.getProperty("smack.test.port") != null) {
+            try {
+                port = Integer.parseInt(System.getProperty("smack.test.port"));
+            }
+            catch (Exception ignored) {}
+        }
+    }
+
+    public void testAdminLogin() {
+
+        String username = System.getProperty("smack.test.admin.username");
+        String password = System.getProperty("smack.test.admin.password");
+        String resource = System.getProperty("smack.test.admin.resource");
+        boolean debug = false;
+        try {
+            debug = Boolean.valueOf(System.getProperty("smack.debug")).booleanValue();
+        }
+        catch (Exception ignored) {}
+
+        XMPPConnection.DEBUG_ENABLED = debug;
+
+        try {
+            XMPPConnection con = new XMPPConnection(host, port);
+            con.login(username, password, resource);
+        }
+        catch (XMPPException e) {
+            String message = e.getMessage();
+            if (e.getXMPPError() != null) {
+                message = "XMPPError code: " + e.getXMPPError().getCode() + ", message: "
+                        + e.getXMPPError().getMessage();
+            }
+            /*fail("Login to server " + host + ":" + port + " failed using user/pass/resource: "
+                    + username + "/" + password + "/" + resource + ". Error message: " + message);*/
+        }
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java b/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java
new file mode 100644
index 000000000..77fb14f1a
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/PacketReaderTest.java
@@ -0,0 +1,102 @@
+/**
+* $RCSfile$
+* $Revision$
+* $Date$
+*
+* Copyright (C) 2002-2003 Jive Software. All rights reserved.
+* ====================================================================
+* The Jive Software License (based on Apache Software License, Version 1.1)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in
+*    the documentation and/or other materials provided with the
+*    distribution.
+*
+* 3. The end-user documentation included with the redistribution,
+*    if any, must include the following acknowledgment:
+*       "This product includes software developed by
+*        Jive Software (http://www.jivesoftware.com)."
+*    Alternately, this acknowledgment may appear in the software itself,
+*    if and wherever such third-party acknowledgments normally appear.
+*
+* 4. The names "Smack" and "Jive Software" must not be used to
+*    endorse or promote products derived from this software without
+*    prior written permission. For written permission, please
+*    contact webmaster@jivesoftware.com.
+*
+* 5. Products derived from this software may not be called "Smack",
+*    nor may "Smack" appear in their name, without prior written
+*    permission of Jive Software.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*/
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+
+public class PacketReaderTest extends SmackTestCase {
+
+    /**
+     * Constructor for PacketReaderTest.
+     * @param arg0
+     */
+    public PacketReaderTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Verify that when Smack receives a "not implemented IQ" answers with an IQ packet
+     * with error code 501.
+     */
+    public void testIQNotImplemented() {
+        
+        // Create a new type of IQ to send. The new IQ will include a
+        // non-existant namespace to cause the "feature-not-implemented" answer
+        IQ iqPacket = new IQ() {
+            public String getChildElementXML() {
+                return "<query xmlns=\"my:ns:test\"/>";
+            }
+        };
+        iqPacket.setTo(getFullJID(1));
+        iqPacket.setType(IQ.Type.GET);
+
+        // Send the IQ and wait for the answer
+        PacketCollector collector = getConnection(0).createPacketCollector(
+                new PacketIDFilter(iqPacket.getPacketID()));
+        getConnection(0).sendPacket(iqPacket);
+        IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+        if (response == null) {
+            fail("No response from the other user.");
+        }
+        assertEquals("The received IQ is not of type ERROR", IQ.Type.ERROR, response.getType());
+        assertEquals("The error code is not 501", 501, response.getError().getCode());
+        collector.cancel();
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java b/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java
new file mode 100644
index 000000000..88ccd721d
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/PresencePriorityTest.java
@@ -0,0 +1,137 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2004 Jive Software. All rights reserved.
+ *
+ * This software is published under the terms of the GNU Public License (GPL),
+ * a copy of which is included in this distribution.
+ */
+
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.packet.Presence;
+
+/**
+ * Ensure that the server is delivering messages to the correct client based on the client's
+ * presence priority.
+ *
+ * @author Gaston Dombiak
+ */
+public class PresencePriorityTest extends SmackTestCase {
+
+    public PresencePriorityTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Connection(0) will send messages to the bareJID of Connection(1) where the user of
+     * Connection(1) has logged from two different places with different presence priorities.
+     */
+    public void testMessageToHighestPriority() {
+        boolean wasFiltering = Chat.isFilteredOnThreadID();
+        Chat.setFilteredOnThreadID(false);
+        XMPPConnection conn = null;
+        try {
+            // User_1 will log in again using another resource
+            conn = new XMPPConnection(getHost(), getPort());
+            conn.login(getUsername(1), getUsername(1), "OtherPlace");
+            // Change the presence priorities of User_1
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1,
+                    Presence.Mode.AVAILABLE));
+            conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2,
+                    Presence.Mode.AVAILABLE));
+            Thread.sleep(150);
+            // Create the chats between the participants
+            Chat chat0 = new Chat(getConnection(0), getBareJID(1));
+            Chat chat1 = new Chat(getConnection(1), getBareJID(0));
+            Chat chat2 = new Chat(conn, getBareJID(0));
+
+            // Test delivery of message to the presence with highest priority
+            chat0.sendMessage("Hello");
+            assertNotNull("Resource with highest priority didn't receive the message",
+                    chat2.nextMessage(2000));
+            assertNull("Resource with lowest priority received the message",
+                    chat1.nextMessage(1000));
+
+            // Invert the presence priorities of User_1
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2,
+                    Presence.Mode.AVAILABLE));
+            conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1,
+                    Presence.Mode.AVAILABLE));
+
+            Thread.sleep(150);
+            // Test delivery of message to the presence with highest priority
+            chat0.sendMessage("Hello");
+            assertNotNull("Resource with highest priority didn't receive the message",
+                    chat1.nextMessage(2000));
+            assertNull("Resource with lowest priority received the message",
+                    chat2.nextMessage(1000));
+
+            // User_1 closes his connection
+            chat2 = null;
+            conn.close();
+            Thread.sleep(150);
+
+            // Test delivery of message to the unique presence of the user_1
+            chat0.sendMessage("Hello");
+            assertNotNull("Resource with highest priority didn't receive the message",
+                    chat1.nextMessage(2000));
+
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2,
+                    Presence.Mode.AVAILABLE));
+
+            // User_1 will log in again using another resource
+            conn = new XMPPConnection(getHost(), getPort());
+            conn.login(getUsername(1), getUsername(1), "OtherPlace");
+            conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1,
+                    Presence.Mode.AVAILABLE));
+            chat2 = new Chat(conn, getBareJID(0));
+
+            Thread.sleep(150);
+            // Test delivery of message to the presence with highest priority
+            chat0.sendMessage("Hello");
+            assertNotNull("Resource with highest priority didn't receive the message",
+                    chat1.nextMessage(2000));
+            assertNull("Resource with lowest priority received the message",
+                    chat2.nextMessage(1000));
+
+            // Invert the presence priorities of User_1
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE, null, 1,
+                    Presence.Mode.AVAILABLE));
+            conn.sendPacket(new Presence(Presence.Type.AVAILABLE, null, 2,
+                    Presence.Mode.AVAILABLE));
+
+            Thread.sleep(150);
+            // Test delivery of message to the presence with highest priority
+            chat0.sendMessage("Hello");
+            assertNotNull("Resource with highest priority didn't receive the message",
+                    chat2.nextMessage(2000));
+            assertNull("Resource with lowest priority received the message",
+                    chat1.nextMessage(1000));
+
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        finally {
+            // Restore the previous filtering value so we don't affect other test cases
+            Chat.setFilteredOnThreadID(wasFiltering);
+            if (conn != null) {
+                conn.close();
+            }
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+    protected void setUp() throws Exception {
+        XMPPConnection.DEBUG_ENABLED = false;
+        super.setUp();
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java
new file mode 100644
index 000000000..27d8fe78e
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/RosterTest.java
@@ -0,0 +1,527 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Tests the Roster functionality by creating and removing roster entries.
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterTest extends SmackTestCase {
+
+    /**
+     * Constructor for RosterTest.
+     * @param name
+     */
+    public RosterTest(String name) {
+        super(name);
+    }
+
+    /**
+     * 1. Create entries in roster groups
+     * 2. Iterate on the groups and remove the entry from each group
+     * 3. Check that the entries are kept as unfiled entries
+     */
+    public void testDeleteAllRosterGroupEntries() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends", "Family" });
+            roster.createEntry(getBareJID(2), "gato12", new String[] { "Family" });
+
+            // Wait until the server confirms the new entries
+            while (roster.getEntryCount() != 2) {
+                Thread.sleep(50);
+            }
+
+            Iterator it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                Iterator groups = entry.getGroups();
+                while (groups.hasNext()) {
+                    RosterGroup rosterGroup = (RosterGroup) groups.next();
+                    rosterGroup.removeEntry(entry);
+                }
+            }
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (roster.getGroupCount() != 0 &&
+                    getConnection(2).getRoster().getEntryCount() != 2)) {
+                Thread.sleep(100);
+            }
+
+            assertEquals(
+                "The number of entries in connection 1 should be 1",
+                1,
+                getConnection(1).getRoster().getEntryCount());
+            assertEquals(
+                "The number of groups in connection 1 should be 0",
+                0,
+                getConnection(1).getRoster().getGroupCount());
+
+            assertEquals(
+                "The number of entries in connection 2 should be 1",
+                1,
+                getConnection(2).getRoster().getEntryCount());
+            assertEquals(
+                "The number of groups in connection 2 should be 0",
+                0,
+                getConnection(2).getRoster().getGroupCount());
+
+            assertEquals(
+                "The number of entries in connection 0 should be 2",
+                2,
+                roster.getEntryCount());
+            assertEquals(
+                "The number of groups in connection 0 should be 0",
+                0,
+                roster.getGroupCount());
+
+            cleanUpRoster();
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 1. Create entries in roster groups
+     * 2. Iterate on all the entries and remove them from the roster
+     * 3. Check that the number of entries and groups is zero
+     */
+    public void testDeleteAllRosterEntries() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends" });
+            roster.createEntry(getBareJID(2), "gato12", new String[] { "Family" });
+
+            // Wait up to 2 seconds to receive new roster contacts
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000  && roster.getEntryCount() != 2) {
+                Thread.sleep(100);
+            }
+
+            assertEquals("Wrong number of entries in connection 0", 2, roster.getEntryCount());
+
+            // Wait up to 2 seconds to receive presences of the new roster contacts
+            initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 5000 &&
+                    (roster.getPresence(getBareJID(1)) == null ||
+                    roster.getPresence(getBareJID(2)) == null)) {
+                Thread.sleep(100);
+            }
+            assertNotNull("Presence not received", roster.getPresence(getBareJID(1)));
+            assertNotNull("Presence not received", roster.getPresence(getBareJID(2)));
+
+            Iterator it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                roster.removeEntry(entry);
+                Thread.sleep(250);
+            }
+
+            // Wait up to 2 seconds to receive roster removal notifications
+            initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000  && roster.getEntryCount() != 0) {
+                Thread.sleep(100);
+            }
+
+            assertEquals("Wrong number of entries in connection 0", 0, roster.getEntryCount());
+            assertEquals("Wrong number of groups in connection 0", 0, roster.getGroupCount());
+
+            assertEquals(
+                "Wrong number of entries in connection 1",
+                0,
+                getConnection(1).getRoster().getEntryCount());
+            assertEquals(
+                "Wrong number of groups in connection 1",
+                0,
+                getConnection(1).getRoster().getGroupCount());
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 1. Create unfiled entries
+     * 2. Iterate on all the entries and remove them from the roster
+     * 3. Check that the number of entries and groups is zero
+     */
+    public void testDeleteAllUnfiledRosterEntries() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), "gato11", null);
+            roster.createEntry(getBareJID(2), "gato12", null);
+
+            Thread.sleep(200);
+
+            Iterator it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                roster.removeEntry(entry);
+                Thread.sleep(250);
+            }
+
+            assertEquals("Wrong number of entries in connection 0", 0, roster.getEntryCount());
+            assertEquals("Wrong number of groups in connection 0", 0, roster.getGroupCount());
+
+            assertEquals(
+                "Wrong number of entries in connection 1",
+                0,
+                getConnection(1).getRoster().getEntryCount());
+            assertEquals(
+                "Wrong number of groups in connection 1",
+                0,
+                getConnection(1).getRoster().getGroupCount());
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 1. Create an unfiled entry
+     * 2. Change its name
+     * 3. Check that the name has been modified
+     * 4. Reload the whole roster
+     * 5. Check that the name has been modified
+     */
+    public void testChangeNameToUnfiledEntry() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), null, null);
+
+            Thread.sleep(200);
+
+            // Change the roster entry name and check if the change was made
+            Iterator it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                entry.setName("gato11");
+                assertEquals("gato11", entry.getName());
+            }
+            // Reload the roster and check the name again
+            roster.reload();
+            Thread.sleep(2000);
+            it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                assertEquals("gato11", entry.getName());
+            }
+
+            cleanUpRoster();
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * 1. Create an unfiled entry with no name
+     * 2. Check that the the entry does not belong to any group
+     * 3. Change its name and add it to a group
+     * 4. Check that the name has been modified and that the entry belongs to a group
+     */
+    public void testChangeGroupAndNameToUnfiledEntry() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), null, null);
+
+            Thread.sleep(500);
+
+            getConnection(1).getRoster().createEntry(getBareJID(0), null, null);
+
+            // Wait up to 5 seconds to receive presences of the new roster contacts
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 5000 &&
+                    roster.getPresence(getBareJID(0)) == null) {
+                Thread.sleep(100);
+            }
+            //assertNotNull("Presence not received", roster.getPresence(getBareJID(0)));
+
+            Iterator it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                assertFalse("The roster entry belongs to a group", entry.getGroups().hasNext());
+            }
+
+            // Change the roster entry name and check if the change was made
+            roster.createEntry(getBareJID(1), "NewName", new String[] { "Friends" });
+
+            // Reload the roster and check the name again
+            Thread.sleep(200);
+            it = roster.getEntries();
+            while (it.hasNext()) {
+                RosterEntry entry = (RosterEntry) it.next();
+                assertEquals("Name of roster entry is wrong", "NewName", entry.getName());
+                assertTrue("The roster entry does not belong to any group", entry.getGroups()
+                        .hasNext());
+            }
+            // Wait up to 5 seconds to receive presences of the new roster contacts
+            initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 5000 &&
+                    roster.getPresence(getBareJID(1)) == null) {
+                Thread.sleep(100);
+            }
+            assertNotNull("Presence not received", roster.getPresence(getBareJID(1)));
+
+            cleanUpRoster();
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Test if renaming a roster group works fine.
+     *
+     */
+    public void testRenameRosterGroup() {
+        try {
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), "gato11", new String[] { "Friends" });
+            roster.createEntry(getBareJID(2), "gato12", new String[] { "Friends" });
+
+            Thread.sleep(200);
+
+            roster.getGroup("Friends").setName("Amigos");
+
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (roster.getGroup("Friends") != null)) {
+                Thread.sleep(100);
+            }
+
+            assertNull("The group Friends still exists", roster.getGroup("Friends"));
+            assertNotNull("The group Amigos does not exist", roster.getGroup("Amigos"));
+            assertEquals(
+                "Wrong number of entries in the group Amigos",
+                2,
+                roster.getGroup("Amigos").getEntryCount());
+
+            roster.getGroup("Amigos").setName("");
+
+            Thread.sleep(500);
+
+            assertNull("The group Amigos still exists", roster.getGroup("Amigos"));
+            assertNotNull("The group with no name does not exist", roster.getGroup(""));
+            assertEquals(
+                "Wrong number of entries in the group \"\" ",
+                2,
+                roster.getGroup("").getEntryCount());
+
+            cleanUpRoster();
+            Thread.sleep(200);
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Test presence management.
+     */
+    public void testRosterPresences() {
+        try {
+            Presence presence = null;
+
+            // Create another connection for the same user of connection 1
+            XMPPConnection conn4 = new XMPPConnection(getHost());
+            conn4.login(getUsername(1), getUsername(1), "Home");
+
+            // Add a new roster entry
+            Roster roster = getConnection(0).getRoster();
+            roster.createEntry(getBareJID(1), "gato11", null);
+
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (roster.getPresence(getBareJID(1)) == null)) {
+                Thread.sleep(100);
+            }
+
+            // Check that a presence is returned for a user
+            presence = roster.getPresence(getBareJID(1));
+            assertNotNull("Returned a null Presence for an existing user", presence);
+
+            // Check that the right presence is returned for a user+resource
+            presence = roster.getPresenceResource(getUsername(1) + "@" + conn4.getHost() + "/Home");
+            assertEquals(
+                "Returned the wrong Presence",
+                StringUtils.parseResource(presence.getFrom()),
+                "Home");
+
+            // Check that the right presence is returned for a user+resource
+            presence = roster.getPresenceResource(getFullJID(1));
+            assertEquals(
+                "Returned the wrong Presence",
+                StringUtils.parseResource(presence.getFrom()),
+                "Smack");
+
+            // Check that the no presence is returned for a non-existent user+resource
+            presence = roster.getPresenceResource("noname@" + getHost() + "/Smack");
+            assertNull("Returned a Presence for a non-existing user", presence);
+
+            // Check that the returned presences are correct
+            Iterator presences = roster.getPresences(getBareJID(1));
+            int count = 0;
+            while (presences.hasNext()) {
+                count++;
+                presences.next();
+            }
+            assertEquals("Wrong number of returned presences", count, 2);
+
+            // Close the connection so one presence must go            
+            conn4.close();
+
+            // Check that the returned presences are correct
+            presences = roster.getPresences(getBareJID(1));
+            count = 0;
+            while (presences.hasNext()) {
+                count++;
+                presences.next();
+            }
+            assertEquals("Wrong number of returned presences", count, 1);
+
+            Thread.sleep(200);
+            cleanUpRoster();
+
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Clean up all the entries in the roster
+     */
+    private void cleanUpRoster() {
+        // Delete all the entries from the roster
+        Iterator it = getConnection(0).getRoster().getEntries();
+        while (it.hasNext()) {
+            RosterEntry entry = (RosterEntry) it.next();
+            try {
+                getConnection(0).getRoster().removeEntry(entry);
+            } catch (XMPPException e) {
+                e.printStackTrace();
+                fail(e.getMessage());
+            }
+        }
+        // Wait up to 2 seconds to receive roster removal notifications
+        long initial = System.currentTimeMillis();
+        while (System.currentTimeMillis() - initial < 2000 &&
+                getConnection(0).getRoster().getEntryCount() != 0) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {}
+        }
+
+        // Wait up to 2 seconds to receive roster removal notifications
+        initial = System.currentTimeMillis();
+        while (System.currentTimeMillis() - initial < 2000 &&
+                getConnection(1).getRoster().getEntryCount() != 0) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {}
+        }
+
+        assertEquals(
+            "Wrong number of entries in connection 0",
+            0,
+            getConnection(0).getRoster().getEntryCount());
+        assertEquals(
+            "Wrong number of groups in connection 0",
+            0,
+            getConnection(0).getRoster().getGroupCount());
+
+        assertEquals(
+            "Wrong number of entries in connection 1",
+            0,
+            getConnection(1).getRoster().getEntryCount());
+        assertEquals(
+            "Wrong number of groups in connection 1",
+            0,
+            getConnection(1).getRoster().getGroupCount());
+
+        assertEquals(
+            "Wrong number of entries in connection 2",
+            0,
+            getConnection(2).getRoster().getEntryCount());
+        assertEquals(
+            "Wrong number of groups in connection 2",
+            0,
+            getConnection(2).getRoster().getGroupCount());
+    }
+
+    protected int getMaxConnections() {
+        return 3;
+    }
+
+    protected void setUp() throws Exception {
+        //XMPPConnection.DEBUG_ENABLED = false;
+        super.setUp();
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java
new file mode 100644
index 000000000..68029a681
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/AndFilterTest.java
@@ -0,0 +1,99 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the AndFilter class.
+ */
+public class AndFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        PacketFilter filter = null;
+        try {
+            AndFilter and = new AndFilter(filter, filter);
+            fail("Should have thrown IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockPacketFilter trueFilter = new MockPacketFilter(true);
+        MockPacketFilter falseFilter = new MockPacketFilter(false);
+
+        MockPacket packet = new MockPacket();
+
+        AndFilter andFilter = new AndFilter(trueFilter, trueFilter);
+        assertTrue(andFilter.accept(packet));
+
+        andFilter = new AndFilter(trueFilter, falseFilter);
+        assertFalse(andFilter.accept(packet));
+
+        andFilter = new AndFilter(falseFilter, trueFilter);
+        assertFalse(andFilter.accept(packet));
+
+        andFilter = new AndFilter(falseFilter, falseFilter);
+        assertFalse(andFilter.accept(packet));
+
+        andFilter = new AndFilter();
+        andFilter.addFilter(trueFilter);
+        andFilter.addFilter(trueFilter);
+        andFilter.addFilter(falseFilter);
+        andFilter.addFilter(trueFilter);
+        assertFalse(andFilter.accept(packet));
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java
new file mode 100644
index 000000000..c06df49aa
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/FromContainsFilterTest.java
@@ -0,0 +1,104 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the FromContainsFilter class.
+ */
+public class FromContainsFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        try {
+            new FromContainsFilter(null);
+            fail("Parameter can not be null");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockFromPacket packet = new MockFromPacket("foo@bar.com");
+
+        FromContainsFilter fromContainsFilter = new FromContainsFilter("");
+        assertTrue(fromContainsFilter.accept(packet));
+
+        fromContainsFilter = new FromContainsFilter("foo");
+        assertTrue(fromContainsFilter.accept(packet));
+
+        fromContainsFilter = new FromContainsFilter("foo@bar.com");
+        assertTrue(fromContainsFilter.accept(packet));
+
+        fromContainsFilter = new FromContainsFilter("bar@foo.com");
+        assertFalse(fromContainsFilter.accept(packet));
+
+        fromContainsFilter = new FromContainsFilter("blah-stuff,net");
+        assertFalse(fromContainsFilter.accept(packet));
+    }
+
+    /**
+     * Wraps the MockPacket class to always give an expected From field.
+     */
+    private class MockFromPacket extends MockPacket {
+        private String from;
+        public MockFromPacket(String from) {
+            this.from = from;
+        }
+        public String getFrom() {
+            return from;
+        }
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java
new file mode 100644
index 000000000..da8204f6e
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/NotFilterTest.java
@@ -0,0 +1,86 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the NotFilter class.
+ */
+public class NotFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        PacketFilter filter = null;
+        try {
+            NotFilter or = new NotFilter(filter);
+            fail("Should have thrown IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockPacketFilter trueFilter = new MockPacketFilter(true);
+        MockPacketFilter falseFilter = new MockPacketFilter(false);
+
+        MockPacket packet = new MockPacket();
+
+        NotFilter NotFilter = new NotFilter(trueFilter);
+        assertFalse(NotFilter.accept(packet));
+
+        NotFilter = new NotFilter(falseFilter);
+        assertTrue(NotFilter.accept(packet));
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java
new file mode 100644
index 000000000..2ef909998
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/OrFilterTest.java
@@ -0,0 +1,133 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the OrFilter class.
+ */
+public class OrFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        PacketFilter filter = null;
+        try {
+            OrFilter or = new OrFilter(filter, filter);
+            fail("Should have thrown IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockPacketFilter trueFilter = new MockPacketFilter(true);
+        MockPacketFilter falseFilter = new MockPacketFilter(false);
+
+        MockPacket packet = new MockPacket();
+
+        // Testing TT == T
+        OrFilter orFilter = new OrFilter(trueFilter, trueFilter);
+        assertTrue(orFilter.accept(packet));
+
+        // Testing TF = F
+        orFilter = new OrFilter(trueFilter, falseFilter);
+        assertTrue(orFilter.accept(packet));
+
+        // Testing FT = F
+        orFilter = new OrFilter(falseFilter, trueFilter);
+        assertTrue(orFilter.accept(packet));
+
+        // Testing FF = F
+        orFilter = new OrFilter(falseFilter, falseFilter);
+        assertFalse(orFilter.accept(packet));
+
+        // Testing TTTT = T
+        orFilter = new OrFilter(
+            new OrFilter(trueFilter, trueFilter), new OrFilter(trueFilter, trueFilter)
+        );
+        assertTrue(orFilter.accept(packet));
+
+        // Testing TFTT = F
+        orFilter = new OrFilter(
+            new OrFilter(trueFilter, falseFilter), new OrFilter(trueFilter, trueFilter)
+        );
+        assertTrue(orFilter.accept(packet));
+
+        // Testing TTFT = F
+        orFilter = new OrFilter(
+            new OrFilter(trueFilter, trueFilter), new OrFilter(falseFilter, trueFilter)
+        );
+        assertTrue(orFilter.accept(packet));
+
+        // Testing TTTF = F
+        orFilter = new OrFilter(
+            new OrFilter(trueFilter, trueFilter), new OrFilter(trueFilter, falseFilter)
+        );
+        assertTrue(orFilter.accept(packet));
+
+        // Testing FFFF = F
+        orFilter = new OrFilter(
+            new OrFilter(falseFilter, falseFilter), new OrFilter(falseFilter, falseFilter)
+        );
+        assertFalse(orFilter.accept(packet));
+
+        orFilter = new OrFilter();
+        orFilter.addFilter(trueFilter);
+        orFilter.addFilter(trueFilter);
+        orFilter.addFilter(falseFilter);
+        orFilter.addFilter(trueFilter);
+        assertTrue(orFilter.accept(packet));
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java
new file mode 100644
index 000000000..8561c7a7b
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketIDFilterTest.java
@@ -0,0 +1,98 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the PacketIDFilter class.
+ */
+public class PacketIDFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        try {
+            new PacketIDFilter(null);
+            fail("Parameter can not be null");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockIDPacket packet = new MockIDPacket("foo");
+
+        PacketIDFilter packetIDFilter = new PacketIDFilter("");
+        assertFalse(packetIDFilter.accept(packet));
+
+        packetIDFilter = new PacketIDFilter("foo");
+        assertTrue(packetIDFilter.accept(packet));
+
+        packetIDFilter = new PacketIDFilter("fOO");
+        assertFalse(packetIDFilter.accept(packet));
+    }
+
+    /**
+     * Wraps the MockPacket class to always give an expected packet ID field.
+     */
+    private class MockIDPacket extends MockPacket {
+        private String id;
+        public MockIDPacket(String id) {
+            this.id = id;
+        }
+        public String getPacketID() {
+            return id;
+        }
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java
new file mode 100644
index 000000000..3573384c0
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/PacketTypeFilterTest.java
@@ -0,0 +1,147 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * Test cases for the PacketTypeFilter class.
+ */
+public class PacketTypeFilterTest extends TestCase {
+
+    private class Dummy {}
+
+    private class InnerClassDummy {
+        public class DummyPacket extends Packet {
+            public String toXML() {
+                return null;
+            }
+        }
+        public DummyPacket getInnerInstance() {
+            return new DummyPacket();
+        }
+    }
+
+    private static class StaticInnerClassDummy {
+        public static class StaticDummyPacket extends Packet {
+            public String toXML() {
+                return null;
+            }
+        }
+        public static StaticDummyPacket getInnerInstance() {
+            return new StaticDummyPacket();
+        }
+    }
+
+    /**
+     * Test case for the constructor of PacketTypeFilter objects.
+     */
+    public void testConstructor() {
+        // Test a class that is not a subclass of Packet
+        try {
+            new PacketTypeFilter(Dummy.class);
+            fail("Parameter must be a subclass of Packet.");
+        }
+        catch (IllegalArgumentException e) {}
+
+        // Test a class that is a subclass of Packet
+        try {
+            new PacketTypeFilter(MockPacket.class);
+        }
+        catch (IllegalArgumentException e) {
+            fail();
+        }
+
+        // Test another class which is a subclass of Packet
+        try {
+            new PacketTypeFilter(IQ.class);
+        }
+        catch (IllegalArgumentException e) {
+            fail();
+        }
+
+        // Test an internal class which is a subclass of Packet
+        try {
+            new PacketTypeFilter(InnerClassDummy.DummyPacket.class);
+        }
+        catch (IllegalArgumentException e) {
+            fail();
+        }
+
+        // Test an internal static class which is a static subclass of Packet
+        try {
+            new PacketTypeFilter(StaticInnerClassDummy.StaticDummyPacket.class);
+        }
+        catch (IllegalArgumentException e) {
+            fail();
+        }
+    }
+
+    /**
+     * Test case to test the accept() method of PacketTypeFilter objects.
+     */
+    public void testAccept() {
+        Packet packet = new MockPacket();
+        PacketTypeFilter filter = new PacketTypeFilter(MockPacket.class);
+        assertTrue(filter.accept(packet));
+
+        packet = (new InnerClassDummy()).getInnerInstance();
+        filter = new PacketTypeFilter(InnerClassDummy.DummyPacket.class);
+        assertTrue(filter.accept(packet));
+
+        packet = StaticInnerClassDummy.getInnerInstance();
+        filter = new PacketTypeFilter(StaticInnerClassDummy.StaticDummyPacket.class);
+        assertTrue(filter.accept(packet));
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java b/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java
new file mode 100644
index 000000000..20986af1a
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/filter/ToContainsFilterTest.java
@@ -0,0 +1,104 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.filter;
+
+import junit.framework.TestCase;
+import org.jivesoftware.smack.packet.*;
+
+/**
+ * A test case for the ToContainsFilter class.
+ */
+public class ToContainsFilterTest extends TestCase {
+
+    public void testNullArgs() {
+        try {
+            new ToContainsFilter(null);
+            fail("Parameter can not be null");
+        }
+        catch (IllegalArgumentException e) {
+            assertTrue(true);
+        }
+    }
+
+    public void testAccept() {
+        MockToPacket packet = new MockToPacket("foo@bar.com");
+
+        ToContainsFilter toContainsFilter = new ToContainsFilter("");
+        assertTrue(toContainsFilter.accept(packet));
+
+        toContainsFilter = new ToContainsFilter("foo");
+        assertTrue(toContainsFilter.accept(packet));
+
+        toContainsFilter = new ToContainsFilter("foo@bar.com");
+        assertTrue(toContainsFilter.accept(packet));
+
+        toContainsFilter = new ToContainsFilter("bar@foo.com");
+        assertFalse(toContainsFilter.accept(packet));
+
+        toContainsFilter = new ToContainsFilter("blah-stuff,net");
+        assertFalse(toContainsFilter.accept(packet));
+    }
+
+    /**
+     * Wraps the MockPacket class to always give an expected To field.
+     */
+    private class MockToPacket extends MockPacket {
+        private String to;
+        public MockToPacket(String to) {
+            this.to = to;
+        }
+        public String getTo() {
+            return to;
+        }
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java
new file mode 100644
index 000000000..3fba4c016
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacket.java
@@ -0,0 +1,69 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.packet;
+
+import java.util.*;
+
+/**
+ * A mock implementation of the Packet abstract class. Implements toXML() by returning null.
+ */
+public class MockPacket extends Packet {
+
+    /**
+     * Returns null always.
+     * @return null
+     */
+    public String toXML() {
+        return null;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java
new file mode 100644
index 000000000..f2f845d18
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/packet/MockPacketFilter.java
@@ -0,0 +1,73 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.packet;
+
+import org.jivesoftware.smack.filter.PacketFilter;
+
+/**
+ * A mock implementation of the PacketFilter class. Pass in the value you want the
+ * accept(..) method to return.
+ */
+public class MockPacketFilter implements PacketFilter {
+
+    private boolean acceptValue;
+
+    public MockPacketFilter(boolean acceptValue) {
+        this.acceptValue = acceptValue;
+    }
+
+    public boolean accept(Packet packet) {
+        return acceptValue;
+    }
+
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java b/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java
new file mode 100644
index 000000000..938416e80
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/test/SmackTestCase.java
@@ -0,0 +1,370 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+package org.jivesoftware.smack.test;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+import javax.net.SocketFactory;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.xmlpull.v1.*;
+import org.xmlpull.mxp1.MXParser;
+
+import junit.framework.TestCase;
+
+/**
+ * Base class for all the test cases which provides a pre-configured execution context. This 
+ * means that any test case that subclassifies this base class will have access to a pool of 
+ * connections and to the user of each connection. The maximum number of connections in the pool
+ * can be controlled by the message {@link #getMaxConnections()} which every subclass must 
+ * implement.<p>   
+ * 
+ * This base class defines a default execution context (i.e. host, port, chat domain and muc 
+ * domain) which can be found in the file "config/test-case.xml". However, each subclass could
+ * redefine the default configuration by providing its own configuration file (if desired). The
+ * name of the configuration file must be of the form <test class name>.xml (e.g. RosterTest.xml).
+ * The file must be placed in the folder "config". This folder is where the default configuration 
+ * file is being held.
+ *
+ * @author Gaston Dombiak
+ */
+public abstract class SmackTestCase extends TestCase {
+
+    private String host = "localhost";
+    private int port = 5222;
+
+    private String chatDomain = "chat.localhost";
+    private String mucDomain = "conference.localhost";
+
+    private XMPPConnection[] connections = null;
+
+    /**
+     * Constructor for SmackTestCase.
+     * @param arg0
+     */
+    public SmackTestCase(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Returns the maximum number of connections to initialize for this test case. All the 
+     * initialized connections will be connected to the server using a new test account for 
+     * each conection. 
+     * 
+     * @return the maximum number of connections to initialize for this test case.
+     */
+    protected abstract int getMaxConnections();
+
+    /**
+     * Returns a SocketFactory that will be used to create the socket to the XMPP server. By 
+     * default no SocketFactory is used but subclasses my want to redefine this method.<p>
+     * 
+     * A custom SocketFactory allows fine-grained control of the actual connection to the XMPP 
+     * server. A typical use for a custom SocketFactory is when connecting through a SOCKS proxy.
+     * 
+     * @return a SocketFactory that will be used to create the socket to the XMPP server.
+     */
+    protected SocketFactory getSocketFactory() {
+        return null;
+    }
+
+    /**
+     * Returns the XMPPConnection located at the requested position. Each test case holds a
+     * pool of connections which is initialized while setting up the test case. The maximum
+     * number of connections is controlled by the message {@link #getMaxConnections()} which
+     * every subclass must implement.<p>   
+     * 
+     * If the requested position is greater than the connections size then an 
+     * IllegalArgumentException will be thrown. 
+     * 
+     * @param index the position in the pool of the connection to look for.
+     * @return the XMPPConnection located at the requested position.
+     */
+    protected XMPPConnection getConnection(int index) {
+        if (index > getMaxConnections()) {
+            throw new IllegalArgumentException("Index out of bounds");
+        }
+        return connections[index];
+    }
+
+    /**
+     * Returns the name of the user (e.g. johndoe) that is using the connection 
+     * located at the requested position.
+     * 
+     * @param index the position in the pool of the connection to look for.
+     * @return the user of the user (e.g. johndoe).
+     */
+    protected String getUsername(int index) {
+        if (index > getMaxConnections()) {
+            throw new IllegalArgumentException("Index out of bounds");
+        }
+        return "user" + index;
+    }
+
+    /**
+     * Returns the bare XMPP address of the user (e.g. johndoe@jabber.org) that is
+     * using the connection located at the requested position.
+     * 
+     * @param index the position in the pool of the connection to look for.
+     * @return the bare XMPP address of the user (e.g. johndoe@jabber.org).
+     */
+    protected String getBareJID(int index) {
+        return getUsername(index) + "@" + getConnection(index).getHost();
+    }
+
+    /**
+     * Returns the full XMPP address of the user (e.g. johndoe@jabber.org/Smack) that is
+     * using the connection located at the requested position.
+     * 
+     * @param index the position in the pool of the connection to look for.
+     * @return the full XMPP address of the user (e.g. johndoe@jabber.org/Smack).
+     */
+    protected String getFullJID(int index) {
+        return getBareJID(index) + "/Smack";
+    }
+
+    protected String getHost() {
+        return host;
+    }
+
+    protected int getPort() {
+        return port;
+    }
+
+    /**
+     * Returns the default groupchat service domain.
+     * 
+     * @return the default groupchat service domain.
+     */
+    protected String getChatDomain() {
+        return chatDomain;
+    }
+
+    /**
+     * Returns the default MUC service domain.
+     * 
+     * @return the default MUC service domain.
+     */
+    protected String getMUCDomain() {
+        return mucDomain;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        init();
+        if (getMaxConnections() < 1) {
+            return;
+        }
+        connections = new XMPPConnection[getMaxConnections()];
+        try {
+            // Connect to the server
+            for (int i = 0; i < getMaxConnections(); i++) {
+                if (getSocketFactory() == null) {
+                    connections[i] = new XMPPConnection(host, port);
+                }
+                else {
+                    connections[i] = new XMPPConnection(host, port, getSocketFactory());
+                }
+            }
+            // Use the host name that the server reports. This is a good idea in most
+            // cases, but could fail if the user set a hostname in their XMPP server
+            // that will not resolve as a network connection.
+            host = connections[0].getHost();
+            // Create the test accounts
+            if (!getConnection(0).getAccountManager().supportsAccountCreation())
+                fail("Server does not support account creation");
+
+            for (int i = 0; i < getMaxConnections(); i++) {
+                // Create the test account
+                try {
+                    getConnection(i).getAccountManager().createAccount("user" + i, "user" + i);
+                } catch (XMPPException e) {
+                    // Do nothing if the accout already exists
+                    if (e.getXMPPError().getCode() != 409) {
+                        throw e;
+                    }
+                }
+                // Login with the new test account
+                getConnection(i).login("user" + i, "user" + i);
+            }
+            // Let the server process the available presences
+            Thread.sleep(150);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        for (int i = 0; i < getMaxConnections(); i++) {
+            // Delete the created account for the test
+            getConnection(i).getAccountManager().deleteAccount();
+            // Close the connection
+            getConnection(i).close();
+
+        }
+    }
+
+    /**
+     * Initializes the context of the test case. We will first try to load the configuration from 
+     * a file whose name is conformed by the test case class name plus an .xml extension 
+     * (e.g RosterTest.xml). If no file was found under that name then we will try to load the 
+     * default configuration for all the test cases from the file "config/test-case.xml".
+     *
+     */
+    private void init() {
+        try {
+            boolean found = false;
+            // Try to load the configutation from an XML file specific for this test case 
+            Enumeration resources =
+                ClassLoader.getSystemClassLoader().getResources(getConfigurationFilename());
+            while (resources.hasMoreElements()) {
+                found = parseURL((URL) resources.nextElement());
+            }
+            // If none was found then try to load the configuration from the default configuration 
+            // file (i.e. "config/test-case.xml")
+            if (!found) {
+                resources = ClassLoader.getSystemClassLoader().getResources("config/test-case.xml");
+                while (resources.hasMoreElements()) {
+                    found = parseURL((URL) resources.nextElement());
+                }
+            }
+            if (!found) {
+                System.err.println("File config/test-case.xml not found. Using default config.");
+            }
+        }
+        catch (Exception e) {
+        }
+    }
+
+    /**
+     * Returns true if the given URL was found and parsed without problems. The file provided
+     * by the URL must contain information useful for the test case configuration, such us,
+     * host and port of the server.  
+     * 
+     * @param url the url of the file to parse.
+     * @return true if the given URL was found and parsed without problems.
+     */
+    private boolean parseURL(URL url) {
+        boolean parsedOK = false;
+        InputStream systemStream = null;
+        try {
+            systemStream = url.openStream();
+            XmlPullParser parser = new MXParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            parser.setInput(systemStream, "UTF-8");
+            int eventType = parser.getEventType();
+            do {
+                if (eventType == XmlPullParser.START_TAG) {
+                    if (parser.getName().equals("host")) {
+                        host = parser.nextText();
+                    }
+                    else if (parser.getName().equals("port")) {
+                        port = parseIntProperty(parser, port);
+                        ;
+                    }
+                    else if (parser.getName().equals("chat")) {
+                        chatDomain = parser.nextText();
+                    }
+                    else if (parser.getName().equals("muc")) {
+                        mucDomain = parser.nextText();
+                    }
+                }
+                eventType = parser.next();
+            }
+            while (eventType != XmlPullParser.END_DOCUMENT);
+            parsedOK = true;
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+        finally {
+            try {
+                systemStream.close();
+            }
+            catch (Exception e) {
+            }
+        }
+        return parsedOK;
+    }
+
+    private static int parseIntProperty(XmlPullParser parser, int defaultValue) throws Exception {
+        try {
+            return Integer.parseInt(parser.nextText());
+        }
+        catch (NumberFormatException nfe) {
+            nfe.printStackTrace();
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the name of the configuration file related to <b>this</b> test case. By default all 
+     * the test cases will use the same configuration file. However, it's possible to override the
+     * default configuration by providing a file of the form <test case class name>.xml
+     * (e.g. RosterTest.xml).
+     * 
+     * @return the name of the configuration file related to this test case.
+     */
+    private String getConfigurationFilename() {
+        String fullClassName = this.getClass().getName();
+        int firstChar = fullClassName.lastIndexOf('.') + 1;
+        return "config/" + fullClassName.substring(firstChar) + ".xml";
+    }
+
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java b/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java
new file mode 100644
index 000000000..b3ac230ea
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smack/util/StringUtilsTest.java
@@ -0,0 +1,236 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smack.util;
+
+import junit.framework.TestCase;
+
+/**
+ * A test case for the StringUtils class.
+ */
+public class StringUtilsTest extends TestCase {
+
+    public void testEscapeForXML() {
+        String input = null;
+
+        assertNull(StringUtils.escapeForXML(null));
+
+        input = "<b>";
+        assertEquals("&lt;b&gt;", StringUtils.escapeForXML(input));
+
+        input = "\"";
+        assertEquals("&quot;", StringUtils.escapeForXML(input));
+
+        input = "&";
+        assertEquals("&amp;", StringUtils.escapeForXML(input));
+
+        input = "<b>\n\t\r</b>";
+        assertEquals("&lt;b&gt;\n\t\r&lt;/b&gt;", StringUtils.escapeForXML(input));
+
+        input = "   &   ";
+        assertEquals("   &amp;   ", StringUtils.escapeForXML(input));
+
+        input = "   \"   ";
+        assertEquals("   &quot;   ", StringUtils.escapeForXML(input));
+
+        input = "> of me <";
+        assertEquals("&gt; of me &lt;", StringUtils.escapeForXML(input));
+
+        input = "> of me & you<";
+        assertEquals("&gt; of me &amp; you&lt;", StringUtils.escapeForXML(input));
+
+        input = "& <";
+        assertEquals("&amp; &lt;", StringUtils.escapeForXML(input));
+
+        input = "&";
+        assertEquals("&amp;", StringUtils.escapeForXML(input));
+    }
+
+    public void testHash() {
+        // Test null
+        // @TODO - should the StringUtils.hash(String) method be fixed to handle null input?
+        try {
+            StringUtils.hash(null);
+            fail();
+        }
+        catch (NullPointerException npe) {
+            assertTrue(true);
+        }
+
+        // Test empty String
+        String result = StringUtils.hash("");
+        assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", result);
+
+        // Test a known hash
+        String adminInHash = "d033e22ae348aeb5660fc2140aec35850c4da997";
+        result = StringUtils.hash("admin");
+        assertEquals(adminInHash, result);
+
+        // Test a random String - make sure all resulting characters are valid hash characters
+        // and that the returned string is 32 characters long.
+        String random = "jive software blah and stuff this is pretty cool";
+        result = StringUtils.hash(random);
+        assertTrue(isValidHash(result));
+
+        // Test junk input:
+        String junk = "\n\n\t\b\r!@(!)^(#)@+_-\u2031\u09291\u00A9\u00BD\u0394\u00F8";
+        result = StringUtils.hash(junk);
+        assertTrue(isValidHash(result));
+    }
+
+    /* ----- Utility methods and vars ----- */
+
+    private final String HASH_CHARS = "0123456789abcdef";
+
+    /**
+     * Returns true if the input string is valid md5 hash, false otherwise.
+     */
+    private boolean isValidHash(String result) {
+        boolean valid = true;
+        for (int i=0; i<result.length(); i++) {
+            char c = result.charAt(i);
+            if (HASH_CHARS.indexOf(c) < 0) {
+                valid = false;
+            }
+        }
+        return valid;
+    }
+
+    public void testEncodeHex() {
+        String input = "";
+        String output = "";
+        assertEquals(new String(StringUtils.encodeHex(input.getBytes())),
+                new String(output.getBytes()));
+
+        input = "foo bar 123";
+        output = "666f6f2062617220313233";
+        assertEquals(new String(StringUtils.encodeHex(input.getBytes())),
+                new String(output.getBytes()));
+    }
+
+    /**
+     * This method tests 2 StringUtil methods - encodeBase64(String) and encodeBase64(byte[]).
+     */
+    public void testEncodeBase64() {
+        String input = "";
+        String output = "";
+        assertEquals(StringUtils.encodeBase64(input), output);
+
+        input = "foo bar 123";
+        output = "Zm9vIGJhciAxMjM=";
+        assertEquals(StringUtils.encodeBase64(input), output);
+
+        input = "=";
+        output = "PQ==";
+        assertEquals(StringUtils.encodeBase64(input), output);
+
+        input = "abcdefghijklmnopqrstuvwxyz0123456789\n\t\"?!.@{}[]();',./<>#$%^&*";
+        output = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5CgkiPyEuQHt9W10oKTsnLC4vPD4jJCVeJio=";
+        assertEquals(StringUtils.encodeBase64(input), output);
+    }
+
+    /***
+     * This method tests 2 StringUtil methods - decodeBase64(String) and decodeBase64(byte[]).
+     */
+    /*
+    public void testDecodeBase64() {
+        String input = "";
+        String output = "";
+        assertEquals(StringUtils.decodeBase64(input), output);
+
+        input = "Zm9vIGJhciAxMjM=";
+        output = "foo bar 123";
+        assertEquals(StringUtils.decodeBase64(input), output);
+
+        input = "PQ==";
+        output = "=";
+        assertEquals(StringUtils.decodeBase64(input), output);
+
+        input = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5CgkiPyEuQHt9W10oKTsnLC4vPD4jJCVeJio=";
+        output = "abcdefghijklmnopqrstuvwxyz0123456789\n\t\"?!.@{}[]();',./<>#$%^&*";
+        assertEquals(StringUtils.decodeBase64(input), output);
+    }
+    */
+
+    public void testRandomString() {
+        // Boundary test
+        String result = StringUtils.randomString(-1);
+        assertNull(result);
+
+        // Zero length string test
+        result = StringUtils.randomString(0);
+        assertNull(result);
+
+        // Test various lengths - make sure the same length is returned
+        result = StringUtils.randomString(4);
+        assertTrue(result.length() == 4);
+        result = StringUtils.randomString(16);
+        assertTrue(result.length() == 16);
+        result = StringUtils.randomString(128);
+        assertTrue(result.length() == 128);
+    }
+
+    public void testParsing() {
+        String error = "Error parsing node name";
+        assertEquals(error, "", StringUtils.parseName("yahoo.myjabber.net"));
+        assertEquals(error, "", StringUtils.parseName("yahoo.myjabber.net/registred"));
+        assertEquals(error, "user", StringUtils.parseName("user@yahoo.myjabber.net/registred"));
+        assertEquals(error, "user", StringUtils.parseName("user@yahoo.myjabber.net"));
+
+        error = "Error parsing server name";
+        String result = "yahoo.myjabber.net";
+        assertEquals(error, result, StringUtils.parseServer("yahoo.myjabber.net"));
+        assertEquals(error, result, StringUtils.parseServer("yahoo.myjabber.net/registred"));
+        assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net/registred"));
+        assertEquals(error, result, StringUtils.parseServer("user@yahoo.myjabber.net"));
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java
new file mode 100644
index 000000000..3fd0090c1
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/FormTest.java
@@ -0,0 +1,182 @@
+/**
+* $RCSfile$
+* $Revision$
+* $Date$
+*
+* Copyright (C) 2002-2003 Jive Software. All rights reserved.
+* ====================================================================
+* The Jive Software License (based on Apache Software License, Version 1.1)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in
+*    the documentation and/or other materials provided with the
+*    distribution.
+*
+* 3. The end-user documentation included with the redistribution,
+*    if any, must include the following acknowledgment:
+*       "This product includes software developed by
+*        Jive Software (http://www.jivesoftware.com)."
+*    Alternately, this acknowledgment may appear in the software itself,
+*    if and wherever such third-party acknowledgments normally appear.
+*
+* 4. The names "Smack" and "Jive Software" must not be used to
+*    endorse or promote products derived from this software without
+*    prior written permission. For written permission, please
+*    contact webmaster@jivesoftware.com.
+*
+* 5. Products derived from this software may not be called "Smack",
+*    nor may "Smack" appear in their name, without prior written
+*    permission of Jive Software.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*/
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.Chat;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Tests the DataForms extensions.
+ * 
+ * @author Gaston Dombiak
+ */
+public class FormTest extends SmackTestCase {
+
+    /**
+     * Constructor for FormTest.
+     * @param arg0
+     */
+    public FormTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * 1. Create a form to fill out and send it to the other user
+     * 2. Retrieve the form to fill out, complete it and return it to the requestor
+     * 3. Retrieve the completed form and check that everything is OK
+     */
+    public void testFilloutForm() {
+        Form formToSend = new Form(Form.TYPE_FORM);
+        formToSend.setInstructions(
+            "Fill out this form to report your case.\nThe case will be created automatically.");
+        formToSend.setTitle("Case configurations");
+        // Add a hidden variable
+        FormField field = new FormField("hidden_var");
+        field.setType(FormField.TYPE_HIDDEN);
+        field.addValue("Some value for the hidden variable");
+        formToSend.addField(field);
+        // Add a fixed variable
+        field = new FormField();
+        field.addValue("Section 1: Case description");
+        formToSend.addField(field);
+        // Add a text-single variable
+        field = new FormField("name");
+        field.setLabel("Enter a name for the case");
+        field.setType(FormField.TYPE_TEXT_SINGLE);
+        formToSend.addField(field);
+        // Add a text-multi variable
+        field = new FormField("description");
+        field.setLabel("Enter a description");
+        field.setType(FormField.TYPE_TEXT_MULTI);
+        formToSend.addField(field);
+        // Add a boolean variable
+        field = new FormField("time");
+        field.setLabel("Is this your first case?");
+        field.setType(FormField.TYPE_BOOLEAN);
+        formToSend.addField(field);
+        // Add a text variable where an int value is expected
+        field = new FormField("age");
+        field.setLabel("How old are you?");
+        field.setType(FormField.TYPE_TEXT_SINGLE);
+        formToSend.addField(field);
+
+        // Create the chats between the two participants
+        Chat chat = getConnection(0).createChat(getBareJID(1));
+        Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat.getThreadID());
+
+        Message msg = chat.createMessage();
+        msg.setBody("To enter a case please fill out this form and send it back to me");
+        msg.addExtension(formToSend.getDataFormToSend());
+
+        try {
+            // Send the message with the form to fill out
+            chat.sendMessage(msg);
+
+            // Get the message with the form to fill out
+            Message msg2 = chat2.nextMessage(2000);
+            // Retrieve the form to fill out
+            Form formToRespond = Form.getFormFrom(msg2);
+            assertNotNull(formToRespond);
+            assertNotNull(formToRespond.getField("name"));
+            assertNotNull(formToRespond.getField("description"));
+            // Obtain the form to send with the replies
+            Form completedForm = formToRespond.createAnswerForm();
+            assertNotNull(completedForm.getField("hidden_var"));
+            // Check that a field of type String does not accept booleans
+            try {
+                completedForm.setAnswer("name", true);
+                fail("A boolean value was set to a field of type String");
+            }
+            catch (IllegalArgumentException e) {
+            }
+            completedForm.setAnswer("name", "Credit card number invalid");
+            completedForm.setAnswer(
+                "description",
+                "The ATM says that my credit card number is invalid. What's going on?");
+            completedForm.setAnswer("time", true);
+            completedForm.setAnswer("age", 20);
+            // Create a new message to send with the completed form
+            msg2 = chat2.createMessage();
+            msg2.setBody("To enter a case please fill out this form and send it back to me");
+            // Add the completed form to the message
+            msg2.addExtension(completedForm.getDataFormToSend());
+            // Send the message with the completed form
+            chat2.sendMessage(msg2);
+
+            // Get the message with the completed form
+            Message msg3 = chat.nextMessage(2000);
+            // Retrieve the completed form
+            completedForm = Form.getFormFrom(msg3);
+            assertNotNull(completedForm);
+            assertNotNull(completedForm.getField("name"));
+            assertNotNull(completedForm.getField("description"));
+            assertEquals(
+                completedForm.getField("name").getValues().next(),
+                "Credit card number invalid");
+            assertNotNull(completedForm.getField("time"));
+            assertNotNull(completedForm.getField("age"));
+            assertEquals("The age is bad", "20", completedForm.getField("age").getValues().next());
+
+        }
+        catch (XMPPException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java
new file mode 100644
index 000000000..8b8e30e67
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/GroupChatInvitationTest.java
@@ -0,0 +1,119 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketExtensionFilter;
+
+/**
+ * 
+ * 
+ * @author Matt Tucker
+ */
+public class GroupChatInvitationTest extends SmackTestCase {
+
+    private PacketCollector collector = null;
+
+    /**
+     * Constructor for GroupChatInvitationTest.
+     * @param arg0
+     */
+    public GroupChatInvitationTest(String arg0) {
+        super(arg0);
+    }
+
+    public void testInvitation() {
+        try {
+            GroupChatInvitation invitation = new GroupChatInvitation("test@" + getChatDomain());
+            Message message = new Message(getBareJID(1));
+            message.setBody("Group chat invitation!");
+            message.addExtension(invitation);
+            getConnection(0).sendPacket(message);
+
+            Thread.sleep(250);
+
+            Message result = (Message)collector.pollResult();
+            assertNotNull("Message not delivered correctly.", result);
+
+            GroupChatInvitation resultInvite = (GroupChatInvitation)result.getExtension("x",
+                    "jabber:x:conference");
+
+            assertEquals("Invitation not to correct room", "test@" + getChatDomain(),
+                    resultInvite.getRoomAddress());
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        // Register listener for groupchat invitations.
+        PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference");
+        collector = getConnection(1).createPacketCollector(filter);
+    }
+
+    protected void tearDown() throws Exception {
+        // Cancel the packet collector so that no more results are queued up
+        collector.cancel();
+
+        super.tearDown();
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
\ No newline at end of file
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java
new file mode 100644
index 000000000..689b1a1d6
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventManagerTest.java
@@ -0,0 +1,278 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.ArrayList;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ *
+ * Test the MessageEvent extension using the high level API.
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEventManagerTest extends SmackTestCase {
+
+    /**
+     * Constructor for MessageEventManagerTest.
+     * @param name
+     */
+    public MessageEventManagerTest(String name) {
+        super(name);
+    }
+
+    /**
+     * High level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives the 
+     * message
+     * 1. User_1 will send a message to user_2 requesting to be notified when any of these events
+     * occurs: offline, composing, displayed or delivered 
+     */
+    public void testSendMessageEventRequest() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("An interesting body comes here...");
+        // Add to the message all the notifications requests (offline, delivered, displayed,
+        // composing)
+        MessageEventManager.addNotificationsRequests(msg, true, true, true, true);
+
+        // Send the message that contains the notifications request
+        try {
+            chat1.sendMessage(msg);
+        } catch (Exception e) {
+            fail("An error occured sending the message");
+        }
+    }
+
+    /**
+     * High level API test.
+     * This is a simple test to use with a XMPP client, check if the client receives the 
+     * message and display in the console any notification
+     * 1. User_1 will send a message to user_2 requesting to be notified when any of these events
+     * occurs: offline, composing, displayed or delivered 
+     * 2. User_2 will use a XMPP client (like Exodus) to display the message and compose a reply
+     * 3. User_1 will display any notification that receives
+     */
+    public void testSendMessageEventRequestAndDisplayNotifications() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        MessageEventManager messageEventManager = new MessageEventManager(getConnection(0));
+        messageEventManager
+            .addMessageEventNotificationListener(new MessageEventNotificationListener() {
+            public void deliveredNotification(String from, String packetID) {
+                System.out.println("From: " + from + " PacketID: " + packetID + "(delivered)");
+            }
+
+            public void displayedNotification(String from, String packetID) {
+                System.out.println("From: " + from + " PacketID: " + packetID + "(displayed)");
+            }
+
+            public void composingNotification(String from, String packetID) {
+                System.out.println("From: " + from + " PacketID: " + packetID + "(composing)");
+            }
+
+            public void offlineNotification(String from, String packetID) {
+                System.out.println("From: " + from + " PacketID: " + packetID + "(offline)");
+            }
+
+            public void cancelledNotification(String from, String packetID) {
+                System.out.println("From: " + from + " PacketID: " + packetID + "(cancelled)");
+            }
+        });
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("An interesting body comes here...");
+        // Add to the message all the notifications requests (offline, delivered, displayed,
+        // composing)
+        MessageEventManager.addNotificationsRequests(msg, true, true, true, true);
+
+        // Send the message that contains the notifications request
+        try {
+            chat1.sendMessage(msg);
+            // Wait a few seconds so that the XMPP client can send any event
+            Thread.sleep(200);
+        } catch (Exception e) {
+            fail("An error occured sending the message");
+        }
+    }
+
+    /**
+     * High level API test.
+     * 1. User_1 will send a message to user_2 requesting to be notified when any of these events
+     * occurs: offline, composing, displayed or delivered 
+     * 2. User_2 will receive the message
+     * 3. User_2 will simulate that the message was displayed 
+     * 4. User_2 will simulate that he/she is composing a reply
+     * 5. User_2 will simulate that he/she has cancelled the reply
+     */
+    public void testRequestsAndNotifications() {
+        final ArrayList results = new ArrayList();
+        ArrayList resultsExpected = new ArrayList();
+        resultsExpected.add("deliveredNotificationRequested");
+        resultsExpected.add("composingNotificationRequested");
+        resultsExpected.add("displayedNotificationRequested");
+        resultsExpected.add("offlineNotificationRequested");
+        resultsExpected.add("deliveredNotification");
+        resultsExpected.add("displayedNotification");
+        resultsExpected.add("composingNotification");
+        resultsExpected.add("cancelledNotification");
+
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        MessageEventManager messageEventManager1 = new MessageEventManager(getConnection(0));
+        messageEventManager1
+            .addMessageEventNotificationListener(new MessageEventNotificationListener() {
+            public void deliveredNotification(String from, String packetID) {
+                results.add("deliveredNotification");
+            }
+
+            public void displayedNotification(String from, String packetID) {
+                results.add("displayedNotification");
+            }
+
+            public void composingNotification(String from, String packetID) {
+                results.add("composingNotification");
+            }
+
+            public void offlineNotification(String from, String packetID) {
+                results.add("offlineNotification");
+            }
+
+            public void cancelledNotification(String from, String packetID) {
+                results.add("cancelledNotification");
+            }
+        });
+
+        MessageEventManager messageEventManager2 = new MessageEventManager(getConnection(1));
+        messageEventManager2
+            .addMessageEventRequestListener(new DefaultMessageEventRequestListener() {
+            public void deliveredNotificationRequested(
+                String from,
+                String packetID,
+                MessageEventManager messageEventManager) {
+                super.deliveredNotificationRequested(from, packetID, messageEventManager);
+                results.add("deliveredNotificationRequested");
+            }
+
+            public void displayedNotificationRequested(
+                String from,
+                String packetID,
+                MessageEventManager messageEventManager) {
+                super.displayedNotificationRequested(from, packetID, messageEventManager);
+                results.add("displayedNotificationRequested");
+            }
+
+            public void composingNotificationRequested(
+                String from,
+                String packetID,
+                MessageEventManager messageEventManager) {
+                super.composingNotificationRequested(from, packetID, messageEventManager);
+                results.add("composingNotificationRequested");
+            }
+
+            public void offlineNotificationRequested(
+                String from,
+                String packetID,
+                MessageEventManager messageEventManager) {
+                super.offlineNotificationRequested(from, packetID, messageEventManager);
+                results.add("offlineNotificationRequested");
+            }
+        });
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("An interesting body comes here...");
+        // Add to the message all the notifications requests (offline, delivered, displayed,
+        // composing)
+        MessageEventManager.addNotificationsRequests(msg, true, true, true, true);
+
+        // Send the message that contains the notifications request
+        try {
+            chat1.sendMessage(msg);
+            messageEventManager2.sendDisplayedNotification(getBareJID(0), msg.getPacketID());
+            messageEventManager2.sendComposingNotification(getBareJID(0), msg.getPacketID());
+            messageEventManager2.sendCancelledNotification(getBareJID(0), msg.getPacketID());
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (!results.containsAll(resultsExpected))) {
+                Thread.sleep(100);
+            }
+            assertTrue(
+                "Test failed due to bad results (1)" + resultsExpected,
+                resultsExpected.containsAll(results));
+            assertTrue(
+                "Test failed due to bad results (2)" + results,
+                results.containsAll(resultsExpected));
+
+        } catch (Exception e) {
+            fail("An error occured sending the message");
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java
new file mode 100644
index 000000000..59eec801f
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/MessageEventTests.java
@@ -0,0 +1,76 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smackx.packet.MessageEventTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ *
+ * Test suite that runs all the Message Event extension tests
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEventTests {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("High and low level API tests for message event extension");
+        //$JUnit-BEGIN$
+        suite.addTest(new TestSuite(MessageEventManagerTest.class));
+        suite.addTest(new TestSuite(MessageEventTest.class));
+        //$JUnit-END$
+        return suite;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java
new file mode 100644
index 000000000..07c2fae6d
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java
@@ -0,0 +1,184 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.Chat;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.packet.OfflineMessageInfo;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Tests handling of offline messaging using OfflineMessageManager. This server requires the
+ * server to support JEP-0013: Flexible Offline Message Retrieval.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageManagerTest extends SmackTestCase {
+
+    public OfflineMessageManagerTest(String arg0) {
+        super(arg0);
+    }
+
+    public void testDiscoverFlexibleRetrievalSupport() throws XMPPException {
+        OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1));
+        assertTrue("Server does not support JEP-13", offlineManager.supportsFlexibleRetrieval());
+    }
+
+    /**
+     * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then
+     * performs some "Flexible Offline Message Retrieval" checking the number of offline messages,
+     * retriving the headers, then the real messages of the headers and finally removing the
+     * loaded messages.
+     */
+    public void testReadAndDelete() {
+        // Make user2 unavailable
+        getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE));
+
+        try {
+            Thread.sleep(500);
+
+            // User1 sends some messages to User2 which is not available at the moment
+            Chat chat = getConnection(0).createChat(getBareJID(1));
+            chat.sendMessage("Test 1");
+            chat.sendMessage("Test 2");
+
+            Thread.sleep(500);
+
+            // User2 checks the number of offline messages
+            OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1));
+            assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount());
+            // Check the message headers
+            Iterator headers = offlineManager.getHeaders();
+            assertTrue("No message header was found", headers.hasNext());
+            List stamps = new ArrayList();
+            while (headers.hasNext()) {
+                OfflineMessageHeader header = (OfflineMessageHeader) headers.next();
+                assertEquals("Incorrect sender", getFullJID(0), header.getJid());
+                assertNotNull("No stamp was found in the message header", header.getStamp());
+                stamps.add(header.getStamp());
+            }
+            assertEquals("Wrong number of headers", 2, stamps.size());
+            // Get the offline messages
+            Iterator messages = offlineManager.getMessages(stamps);
+            assertTrue("No message was found", messages.hasNext());
+            stamps = new ArrayList();
+            while (messages.hasNext()) {
+                Message message = (Message) messages.next();
+                OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline",
+                        "http://jabber.org/protocol/offline");
+                assertNotNull("No offline information was included in the offline message", info);
+                assertNotNull("No stamp was found in the message header", info.getNode());
+                stamps.add(info.getNode());
+            }
+            assertEquals("Wrong number of messages", 2, stamps.size());
+            // Check that the offline messages have not been deleted
+            assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount());
+
+            // User2 becomes available again
+            PacketCollector collector = getConnection(1).createPacketCollector(
+                    new MessageTypeFilter(Message.Type.CHAT));
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE));
+
+            // Check that no offline messages was sent to the user
+            Message message = (Message) collector.nextResult(2500);
+            assertNull("An offline message was sent from the server", message);
+
+            // Delete the retrieved offline messages
+            offlineManager.deleteMessages(stamps);
+            // Check that there are no offline message for this user
+            assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount());
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then
+     * performs some "Flexible Offline Message Retrieval" by fetching all the offline messages
+     * and then removing all the offline messages.
+     */
+    public void testFetchAndPurge() {
+        // Make user2 unavailable
+        getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE));
+
+        try {
+            Thread.sleep(500);
+
+            // User1 sends some messages to User2 which is not available at the moment
+            Chat chat = getConnection(0).createChat(getBareJID(1));
+            chat.sendMessage("Test 1");
+            chat.sendMessage("Test 2");
+
+            Thread.sleep(500);
+
+            // User2 checks the number of offline messages
+            OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1));
+            assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount());
+            // Get all offline messages
+            Iterator messages = offlineManager.getMessages();
+            assertTrue("No message was found", messages.hasNext());
+            List stamps = new ArrayList();
+            while (messages.hasNext()) {
+                Message message = (Message) messages.next();
+                OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline",
+                        "http://jabber.org/protocol/offline");
+                assertNotNull("No offline information was included in the offline message", info);
+                assertNotNull("No stamp was found in the message header", info.getNode());
+                stamps.add(info.getNode());
+            }
+            assertEquals("Wrong number of messages", 2, stamps.size());
+            // Check that the offline messages have not been deleted
+            assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount());
+
+            // User2 becomes available again
+            PacketCollector collector = getConnection(1).createPacketCollector(
+                    new MessageTypeFilter(Message.Type.CHAT));
+            getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE));
+
+            // Check that no offline messages was sent to the user
+            Message message = (Message) collector.nextResult(2500);
+            assertNull("An offline message was sent from the server", message);
+
+            // Delete all offline messages
+            offlineManager.deleteMessages();
+            // Check that there are no offline message for this user
+            assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount());
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java
new file mode 100644
index 000000000..f1e009118
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeManagerTest.java
@@ -0,0 +1,246 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ *
+ * Test the Roster Exchange extension using the high level API
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchangeManagerTest extends SmackTestCase {
+
+    private int entriesSent;
+    private int entriesReceived;
+
+    /**
+     * Constructor for RosterExchangeManagerTest.
+     * @param name
+     */
+    public RosterExchangeManagerTest(String name) {
+        super(name);
+    }
+
+    /**
+     * High level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives user1's 
+     * roster
+     * 1. User_1 will send his/her roster to user_2
+     */
+    public void testSendRoster() {
+        // Send user1's roster to user2
+        try {
+            RosterExchangeManager rosterExchangeManager =
+                new RosterExchangeManager(getConnection(0));
+            rosterExchangeManager.send(getConnection(0).getRoster(), getBareJID(1));
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail("An error occured sending the roster");
+        }
+    }
+
+    /**
+     * High level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives user1's 
+     * roster groups
+     * 1. User_1 will send his/her RosterGroups to user_2
+     */
+    public void testSendRosterGroup() {
+        // Send user1's RosterGroups to user2
+        try {
+            RosterExchangeManager rosterExchangeManager =
+                new RosterExchangeManager(getConnection(0));
+            for (Iterator it = getConnection(0).getRoster().getGroups(); it.hasNext();)
+                rosterExchangeManager.send((RosterGroup) it.next(), getBareJID(1));
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail("An error occured sending the roster");
+        }
+    }
+
+    /**
+     * High level API test.
+     * 1. User_1 will send his/her roster to user_2
+     * 2. User_2 will receive the entries and iterate over them to check if everything is fine
+     * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+     * something is wrong
+     */
+    public void testSendAndReceiveRoster() {
+        RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(getConnection(0));
+        RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(getConnection(1));
+
+        // Create a RosterExchangeListener that will iterate over the received roster entries
+        RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() {
+            public void entriesReceived(String from, Iterator remoteRosterEntries) {
+                int received = 0;
+                assertNotNull("From is null", from);
+                assertNotNull("rosterEntries is null", remoteRosterEntries);
+                assertTrue("Roster without entries", remoteRosterEntries.hasNext());
+                while (remoteRosterEntries.hasNext()) {
+                    received++;
+                    RemoteRosterEntry remoteEntry = (RemoteRosterEntry) remoteRosterEntries.next();
+                    System.out.println(remoteEntry);
+                }
+                entriesReceived = received;
+            }
+        };
+        rosterExchangeManager2.addRosterListener(rosterExchangeListener);
+
+        // Send user1's roster to user2
+        try {
+            entriesSent = getConnection(0).getRoster().getEntryCount();
+            entriesReceived = 0;
+            rosterExchangeManager1.send(getConnection(0).getRoster(), getBareJID(1));
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (entriesSent != entriesReceived)) {
+                Thread.sleep(100);
+            }
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message with the roster");
+        }
+        assertEquals(
+            "Number of sent and received entries does not match",
+            entriesSent,
+            entriesReceived);
+    }
+
+    /**
+     * High level API test.
+     * 1. User_1 will send his/her roster to user_2
+     * 2. User_2 will automatically add the entries that receives to his/her roster in the 
+     * corresponding group
+     * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+     * something is wrong
+     */
+    public void testSendAndAcceptRoster() {
+        RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(getConnection(0));
+        RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(getConnection(1));
+
+        // Create a RosterExchangeListener that will accept all the received roster entries
+        RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() {
+            public void entriesReceived(String from, Iterator remoteRosterEntries) {
+                int received = 0;
+                assertNotNull("From is null", from);
+                assertNotNull("remoteRosterEntries is null", remoteRosterEntries);
+                assertTrue("Roster without entries", remoteRosterEntries.hasNext());
+                while (remoteRosterEntries.hasNext()) {
+                    received++;
+                    try {
+                        RemoteRosterEntry remoteRosterEntry =
+                            (RemoteRosterEntry) remoteRosterEntries.next();
+                        getConnection(1).getRoster().createEntry(
+                            remoteRosterEntry.getUser(),
+                            remoteRosterEntry.getName(),
+                            remoteRosterEntry.getGroupArrayNames());
+                    }
+                    catch (Exception e) {
+                        fail(e.toString());
+                    }
+                }
+                entriesReceived = received;
+            }
+        };
+        rosterExchangeManager2.addRosterListener(rosterExchangeListener);
+
+        // Send user1's roster to user2
+        try {
+            entriesSent = getConnection(0).getRoster().getEntryCount();
+            entriesReceived = 0;
+            rosterExchangeManager1.send(getConnection(0).getRoster(), getBareJID(1));
+            // Wait up to 2 seconds
+            long initial = System.currentTimeMillis();
+            while (System.currentTimeMillis() - initial < 2000 &&
+                    (entriesSent != entriesReceived)) {
+                Thread.sleep(100);
+            }
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message with the roster");
+        }
+        assertEquals(
+            "Number of sent and received entries does not match",
+            entriesSent,
+            entriesReceived);
+        assertTrue("Roster2 has no entries", getConnection(1).getRoster().getEntryCount() > 0);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        try {
+            getConnection(0).getRoster().createEntry(
+                getBareJID(2),
+                "gato5",
+                new String[] { "Friends, Coworker" });
+            getConnection(0).getRoster().createEntry(getBareJID(3), "gato6", null);
+            Thread.sleep(100);
+
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 4;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java
new file mode 100644
index 000000000..a324c6d20
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/RosterExchangeTests.java
@@ -0,0 +1,76 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smackx.packet.RosterExchangeTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ *
+ * Test suite that runs all the Roster Exchange extension tests
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchangeTests {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("High and low level API tests for roster exchange extension");
+        //$JUnit-BEGIN$
+        suite.addTest(new TestSuite(RosterExchangeManagerTest.class));
+        suite.addTest(new TestSuite(RosterExchangeTest.class));
+        //$JUnit-END$
+        return suite;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java
new file mode 100644
index 000000000..2766925e8
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/ServiceDiscoveryManagerTest.java
@@ -0,0 +1,190 @@
+/**
+* $RCSfile$
+* $Revision$
+* $Date$
+*
+* Copyright (C) 2002-2003 Jive Software. All rights reserved.
+* ====================================================================
+* The Jive Software License (based on Apache Software License, Version 1.1)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in
+*    the documentation and/or other materials provided with the
+*    distribution.
+*
+* 3. The end-user documentation included with the redistribution,
+*    if any, must include the following acknowledgment:
+*       "This product includes software developed by
+*        Jive Software (http://www.jivesoftware.com)."
+*    Alternately, this acknowledgment may appear in the software itself,
+*    if and wherever such third-party acknowledgments normally appear.
+*
+* 4. The names "Smack" and "Jive Software" must not be used to
+*    endorse or promote products derived from this software without
+*    prior written permission. For written permission, please
+*    contact webmaster@jivesoftware.com.
+*
+* 5. Products derived from this software may not be called "Smack",
+*    nor may "Smack" appear in their name, without prior written
+*    permission of Jive Software.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*/
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+
+
+/**
+ * Tests the service discovery functionality.
+ * 
+ * @author Gaston Dombiak
+ */
+public class ServiceDiscoveryManagerTest extends SmackTestCase {
+
+    /**
+     * Constructor for ServiceDiscoveryManagerTest.
+     * @param arg0
+     */
+    public ServiceDiscoveryManagerTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Tests info discovery of a Smack client. 
+     */
+    public void testSmackInfo() {
+
+        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager
+                .getInstanceFor(getConnection(0));
+        try {
+            // Discover the information of another Smack client
+            DiscoverInfo info = discoManager.discoverInfo(getFullJID(1));
+            // Check the identity of the Smack client
+            Iterator identities = info.getIdentities();
+            assertTrue("No identities were found", identities.hasNext());
+            DiscoverInfo.Identity identity = (DiscoverInfo.Identity)identities.next();
+            assertEquals("Name in identity is wrong", ServiceDiscoveryManager.getIdentityName(),
+                    identity.getName());
+            assertEquals("Category in identity is wrong", "client", identity.getCategory());
+            assertEquals("Type in identity is wrong", ServiceDiscoveryManager.getIdentityType(),
+                    identity.getType());
+            assertFalse("More identities were found", identities.hasNext());
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Tests that ensures that Smack answers a 404 error when the disco#info includes a node.
+     */
+    public void testInfoWithNode() {
+
+        ServiceDiscoveryManager discoManager = ServiceDiscoveryManager
+                .getInstanceFor(getConnection(0));
+        try {
+            // Discover the information of another Smack client
+            discoManager.discoverInfo(getFullJID(1), "some node");
+            // Check the identity of the Smack client
+            fail("Unexpected identities were returned instead of a 404 error");
+        }
+        catch (XMPPException e) {
+            assertEquals("Incorrect error", 404, e.getXMPPError().getCode());
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Tests service discovery of XHTML support. 
+     */
+    public void testXHTMLFeature() {
+        // Check for local XHTML service support
+        // By default the XHTML service support is enabled in all the connections
+        assertTrue(XHTMLManager.isServiceEnabled(getConnection(0)));
+        assertTrue(XHTMLManager.isServiceEnabled(getConnection(1)));
+        // Check for XHTML support in connection1 from connection2
+        // Must specify a full JID and not a bare JID. Ensure that the server is working ok. 
+        assertFalse(XHTMLManager.isServiceEnabled(getConnection(1), getBareJID(0)));
+        // Using a full JID check that the other client supports XHTML. 
+        assertTrue(XHTMLManager.isServiceEnabled(getConnection(1), getFullJID(0)));
+        
+        // Disable the XHTML Message support in connection1
+        XHTMLManager.setServiceEnabled(getConnection(0), false);
+        // Check for local XHTML service support 
+        assertFalse(XHTMLManager.isServiceEnabled(getConnection(0)));
+        assertTrue(XHTMLManager.isServiceEnabled(getConnection(1)));
+        // Check for XHTML support in connection1 from connection2 
+        assertFalse(XHTMLManager.isServiceEnabled(getConnection(1), getFullJID(0)));
+    }
+
+    /**
+     * Tests support for publishing items to another entity.
+     */
+    public void testDiscoverPublishItemsSupport() {
+        try {
+            boolean canPublish = ServiceDiscoveryManager.getInstanceFor(getConnection(0))
+                    .canPublishItems(getHost());
+            assertFalse("Messenger does not support publishing...so far!!", canPublish);
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+
+    }
+
+    /**
+     * Tests publishing items to another entity.
+     */
+    /*public void testPublishItems() {
+        DiscoverItems itemsToPublish = new DiscoverItems();
+        DiscoverItems.Item itemToPublish = new DiscoverItems.Item("pubsub.shakespeare.lit");
+        itemToPublish.setName("Avatar");
+        itemToPublish.setNode("romeo/avatar");
+        itemToPublish.setAction(DiscoverItems.Item.UPDATE_ACTION);
+        itemsToPublish.addItem(itemToPublish);
+        
+        try {
+            ServiceDiscoveryManager.getInstanceFor(getConnection(0)).publishItems(getHost(),
+                    itemsToPublish);
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+        
+    }*/
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java
new file mode 100644
index 000000000..3707070cd
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/VCardTest.java
@@ -0,0 +1,60 @@
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.packet.VCard;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Gaston
+ * Date: Jun 18, 2005
+ * Time: 1:29:30 AM
+ * To change this template use File | Settings | File Templates.
+ */
+public class VCardTest extends SmackTestCase {
+
+    public VCardTest(String arg0) {
+        super(arg0);
+    }
+
+    public void testBigFunctional() {
+        VCard origVCard = new VCard();
+
+        origVCard.setFirstName("kir");
+        origVCard.setLastName("max");
+        origVCard.setEmailHome("foo@fee.bar");
+        origVCard.setJabberId("jabber@id.org");
+        origVCard.setOrganization("Jetbrains, s.r.o");
+        origVCard.setNickName("KIR");
+
+        origVCard.setField("TITLE", "Mr");
+        origVCard.setAddressFieldHome("STREET", "Some street");
+        origVCard.setPhoneWork("FAX", "3443233");
+
+        origVCard.save(getConnection(0));
+
+        VCard loaded = new VCard();
+        try {
+            loaded.load(getConnection(0));
+        } catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+
+        assertEquals("Should load own VCard successfully", origVCard, loaded);
+
+        loaded = new VCard();
+        try {
+            loaded.load(getConnection(1), getBareJID(0));
+        } catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+
+        assertEquals("Should load another user's VCard successfully", origVCard, loaded);
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java
new file mode 100644
index 000000000..824ff2919
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/VersionTest.java
@@ -0,0 +1,76 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2004 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.packet.Version;
+
+/**
+ * Test case to ensure that Smack is able to get and parse correctly iq:version packets.
+ *
+ * @author Gaston Dombiak
+ */
+public class VersionTest extends SmackTestCase {
+
+    public VersionTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Get the version of the server and make sure that all the required data is present
+     *
+     * Note: This test expects the server to answer an iq:version packet.
+     */
+    public void testGetServerVersion() {
+        Version version = new Version();
+        version.setType(IQ.Type.GET);
+        version.setTo(getHost());
+
+        // Create a packet collector to listen for a response.
+        PacketCollector collector = getConnection(0).createPacketCollector(new PacketIDFilter(version.getPacketID()));
+
+        getConnection(0).sendPacket(version);
+
+        // Wait up to 5 seconds for a result.
+        IQ result = (IQ)collector.nextResult(5000);
+        // Close the collector
+        collector.cancel();
+
+        assertNotNull("No result from the server", result);
+
+        assertEquals("Incorrect result type", IQ.Type.RESULT, result.getType());
+        assertNotNull("No name specified in the result", ((Version)result).getName());
+        assertNotNull("No version specified in the result", ((Version)result).getVersion());
+    }
+
+    protected int getMaxConnections() {
+        return 1;
+    }
+
+    protected void setUp() throws Exception {
+        XMPPConnection.DEBUG_ENABLED = false;
+        super.setUp();
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java
new file mode 100644
index 000000000..cd9c65fdd
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLManagerTest.java
@@ -0,0 +1,284 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Test the XHTML extension using the high level API
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLManagerTest extends SmackTestCase {
+
+    private int bodiesSent;
+    private int bodiesReceived;
+
+    /**
+     * Constructor for XHTMLManagerTest.
+     * @param name
+     */
+    public XHTMLManagerTest(String name) {
+        super(name);
+    }
+
+    /**
+     * High level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives the message
+     * 1. User_1 will send a message with formatted text (XHTML) to user_2
+     */
+    public void testSendSimpleXHTMLMessage() {
+        // User1 creates a chat with user2
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("Hey John, this is my new green!!!!");
+
+        // Create an XHTMLText to send with the message
+        XHTMLText xhtmlText = new XHTMLText(null, null);
+        xhtmlText.appendOpenParagraphTag("font-size:large");
+        xhtmlText.append("Hey John, this is my new ");
+        xhtmlText.appendOpenSpanTag("color:green");
+        xhtmlText.append("green");
+        xhtmlText.appendCloseSpanTag();
+        xhtmlText.appendOpenEmTag();
+        xhtmlText.append("!!!!");
+        xhtmlText.appendCloseEmTag();
+        xhtmlText.appendCloseParagraphTag();
+        // Add the XHTML text to the message
+        XHTMLManager.addBody(msg, xhtmlText.toString());
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            chat1.sendMessage(msg);
+            Thread.sleep(200);
+        } catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+    }
+
+    /**
+    * High level API test.
+    * 1. User_1 will send a message with XHTML to user_2
+    * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything 
+    *    is fine
+    * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+    *    something is wrong
+    */
+    public void testSendSimpleXHTMLMessageAndDisplayReceivedXHTMLMessage() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a listener for the chat that will check if the received message includes 
+        // an XHTML extension. Answer an ACK if everything is ok
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                assertTrue(
+                    "The received message is not an XHTML Message",
+                    XHTMLManager.isXHTMLMessage(message));
+                try {
+                    assertTrue(
+                        "Message without XHTML bodies",
+                        XHTMLManager.getBodies(message).hasNext());
+                    for (Iterator it = XHTMLManager.getBodies(message); it.hasNext();) {
+                        String body = (String) it.next();
+                        System.out.println(body);
+                    }
+                } catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+                try {
+                    chat2.sendMessage("ok");
+                } catch (Exception e) {
+                    fail("An error occured sending ack " + e.getMessage());
+                }
+            }
+        };
+        chat2.addMessageListener(packetListener);
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("Hey John, this is my new green!!!!");
+
+        // Create an XHTMLText to send with the message
+        XHTMLText xhtmlText = new XHTMLText(null, null);
+        xhtmlText.appendOpenParagraphTag("font-size:large");
+        xhtmlText.append("Hey John, this is my new ");
+        xhtmlText.appendOpenSpanTag("color:green");
+        xhtmlText.append("green");
+        xhtmlText.appendCloseSpanTag();
+        xhtmlText.appendOpenEmTag();
+        xhtmlText.append("!!!!");
+        xhtmlText.appendCloseEmTag();
+        xhtmlText.appendCloseParagraphTag();
+        // Add the XHTML text to the message
+        XHTMLManager.addBody(msg, xhtmlText.toString());
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            chat1.sendMessage(msg);
+        } catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+        // Wait for 1 second for a reply
+        msg = chat1.nextMessage(1000);
+        assertNotNull("No reply received", msg);
+    }
+
+    /**
+    * Low level API test. Test a message with two XHTML bodies and several XHTML tags.
+    * 1. User_1 will send a message with XHTML to user_2
+    * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything 
+    *    is fine
+    * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+    *    something is wrong
+    */
+    public void testSendComplexXHTMLMessageAndDisplayReceivedXHTMLMessage() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a listener for the chat that will check if the received message includes 
+        // an XHTML extension. Answer an ACK if everything is ok
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                int received = 0;
+                Message message = (Message) packet;
+                assertTrue(
+                    "The received message is not an XHTML Message",
+                    XHTMLManager.isXHTMLMessage(message));
+                try {
+                    assertTrue(
+                        "Message without XHTML bodies",
+                        XHTMLManager.getBodies(message).hasNext());
+                    for (Iterator it = XHTMLManager.getBodies(message); it.hasNext();) {
+                        received++;
+                        String body = (String) it.next();
+                        System.out.println(body);
+                    }
+                    bodiesReceived = received;
+                } catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+            }
+        };
+        chat2.addMessageListener(packetListener);
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody(
+            "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds.");
+
+        // Create an XHTMLText to send with the message (in Spanish)
+        XHTMLText xhtmlText = new XHTMLText(null, "es-ES");
+        xhtmlText.appendOpenHeaderTag(1, null);
+        xhtmlText.append("impresionante!");
+        xhtmlText.appendCloseHeaderTag(1);
+        xhtmlText.appendOpenParagraphTag(null);
+        xhtmlText.append("Como Emerson dijo una vez:");
+        xhtmlText.appendCloseParagraphTag();
+        xhtmlText.appendOpenBlockQuoteTag(null);
+        xhtmlText.appendOpenParagraphTag(null);
+        xhtmlText.append("Una consistencia rid&#237;cula es el espantajo de mentes peque&#241;as.");
+        xhtmlText.appendCloseParagraphTag();
+        xhtmlText.appendCloseBlockQuoteTag();
+        // Add the XHTML text to the message
+        XHTMLManager.addBody(msg, xhtmlText.toString());
+
+        // Create an XHTMLText to send with the message (in English)
+        xhtmlText = new XHTMLText(null, "en-US");
+        xhtmlText.appendOpenHeaderTag(1, null);
+        xhtmlText.append("awesome!");
+        xhtmlText.appendCloseHeaderTag(1);
+        xhtmlText.appendOpenParagraphTag(null);
+        xhtmlText.append("As Emerson once said:");
+        xhtmlText.appendCloseParagraphTag();
+        xhtmlText.appendOpenBlockQuoteTag(null);
+        xhtmlText.appendOpenParagraphTag(null);
+        xhtmlText.append("A foolish consistency is the hobgoblin of little minds.");
+        xhtmlText.appendCloseParagraphTag();
+        xhtmlText.appendCloseBlockQuoteTag();
+        // Add the XHTML text to the message
+        XHTMLManager.addBody(msg, xhtmlText.toString());
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            bodiesSent = 2;
+            bodiesReceived = 0;
+            chat1.sendMessage(msg);
+            // Wait half second so that the complete test can run
+            Thread.sleep(300);
+        } catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+        assertEquals(
+            "Number of sent and received XHTMP bodies does not match",
+            bodiesSent,
+            bodiesReceived);
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java
new file mode 100644
index 000000000..e311f8631
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/XHTMLSupportTests.java
@@ -0,0 +1,75 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smackx.packet.XHTMLExtensionTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that runs all the XHTML support tests
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLSupportTests {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("High and low level API tests for XHTML support");
+        //$JUnit-BEGIN$
+        suite.addTest(new TestSuite(XHTMLManagerTest.class));
+        suite.addTest(new TestSuite(XHTMLExtensionTest.class));
+        //$JUnit-END$
+        return suite;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java
new file mode 100644
index 000000000..7aa341fc3
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatCreationTest.java
@@ -0,0 +1,149 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+package org.jivesoftware.smackx.muc;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.*;
+
+/**
+ * Tests creating new MUC rooms.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultiUserChatCreationTest extends SmackTestCase {
+
+    private String room;
+
+    /**
+     * Constructor for MultiUserChatCreationTest.
+     * @param arg0
+     */
+    public MultiUserChatCreationTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Tests creating a new "Reserved Room".
+     */
+    public void testCreateReservedRoom() {
+        MultiUserChat muc = new MultiUserChat(getConnection(0), room);
+
+        try {
+            // Create the room
+            muc.create("testbot1");
+
+            // Get the the room's configuration form
+            Form form = muc.getConfigurationForm();
+            assertNotNull("No room configuration form", form);
+            // Create a new form to submit based on the original form
+            Form submitForm = form.createAnswerForm();
+            // Add default answers to the form to submit
+            for (Iterator fields = form.getFields(); fields.hasNext();) {
+                FormField field = (FormField) fields.next();
+                if (!FormField.TYPE_HIDDEN.equals(field.getType())
+                    && field.getVariable() != null) {
+                    // Sets the default value as the answer
+                    submitForm.setDefaultAnswer(field.getVariable());
+                }
+            }
+            List owners = new ArrayList();
+            owners.add(getBareJID(0));
+            submitForm.setAnswer("muc#roomconfig_roomowners", owners);
+
+            // Update the new room's configuration
+            muc.sendConfigurationForm(submitForm);
+
+            // Destroy the new room
+            muc.destroy("The room has almost no activity...", null);
+
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Tests creating a new "Instant Room".
+     */
+    public void testCreateInstantRoom() {
+        MultiUserChat muc = new MultiUserChat(getConnection(0), room);
+
+        try {
+            // Create the room
+            muc.create("testbot");
+
+            // Send an empty room configuration form which indicates that we want
+            // an instant room
+            muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
+
+            // Destroy the new room
+            muc.destroy("The room has almost no activity...", null);
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        room = "fruta124@" + getMUCDomain();
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java
new file mode 100644
index 000000000..31c2cc94b
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/muc/MultiUserChatTest.java
@@ -0,0 +1,1810 @@
+/**
+* $RCSfile$
+* $Revision$
+* $Date$
+*
+* Copyright (C) 2002-2003 Jive Software. All rights reserved.
+* ====================================================================
+* The Jive Software License (based on Apache Software License, Version 1.1)
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+*
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+*
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in
+*    the documentation and/or other materials provided with the
+*    distribution.
+*
+* 3. The end-user documentation included with the redistribution,
+*    if any, must include the following acknowledgment:
+*       "This product includes software developed by
+*        Jive Software (http://www.jivesoftware.com)."
+*    Alternately, this acknowledgment may appear in the software itself,
+*    if and wherever such third-party acknowledgments normally appear.
+*
+* 4. The names "Smack" and "Jive Software" must not be used to
+*    endorse or promote products derived from this software without
+*    prior written permission. For written permission, please
+*    contact webmaster@jivesoftware.com.
+*
+* 5. Products derived from this software may not be called "Smack",
+*    nor may "Smack" appear in their name, without prior written
+*    permission of Jive Software.
+*
+* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+* SUCH DAMAGE.
+* ====================================================================
+*/
+
+package org.jivesoftware.smackx.muc;
+
+import java.util.*;
+import java.text.SimpleDateFormat;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.packet.XHTMLExtension;
+import org.jivesoftware.smackx.packet.DelayInformation;
+
+/**
+ * Tests the new MUC functionalities.
+ * 
+ * @author Gaston Dombiak
+ */
+public class MultiUserChatTest extends SmackTestCase {
+
+    private String room;
+
+    private MultiUserChat muc;
+
+    /**
+     * Constructor for MultiUserChatTest.
+     * @param arg0
+     */
+    public MultiUserChatTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Test the compatibility of the MUC service with clients that still use the old groupchat
+     * protocol.
+     */
+    public void testGroupchatCompatibility() {
+        try {
+            Message message;
+            
+            GroupChat groupchat = new GroupChat(getConnection(1), room);
+            groupchat.join("testbot2");
+            Thread.sleep(400);
+
+            // User1 checks the presence of user2 in the room
+            Presence presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNotNull("Presence of user2 in room is missing", presence);
+            assertEquals(
+                "Presence mode of user2 is wrong",
+                Presence.Mode.AVAILABLE,
+                presence.getMode());
+            
+            // User using old client send a message
+            groupchat.sendMessage("Hello");
+            // Check that the rest of the occupants (that are support MUC) received the message
+            message = muc.nextMessage(1000);
+            assertNotNull("A MUC client didn't receive the message from an old client", message);
+            // User that supports MUC send a message
+            muc.sendMessage("Bye");
+            // Check that the client the doesn't support MUC received the message
+            message = groupchat.nextMessage(1000);
+            assertNotNull("An old client didn't receive the message from a MUC client", message);
+            // User that doesn't support MUC leaves the room
+            groupchat.leave();
+            Thread.sleep(300);
+            // User1 checks the that user2 is not present in the room
+            Occupant occupant = muc.getOccupant(room + "/testbot2");
+            assertNull("Occupant testbot2 still exists", occupant);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testDiscussionHistory() {
+        try {
+            // User1 sends some messages to the room
+            muc.sendMessage("Message 1");
+            muc.sendMessage("Message 2");
+            // Wait 5 seconds before sending the last message
+            Thread.sleep(5000);
+            muc.sendMessage("Message 3");
+
+            // User2 joins the room requesting to receive the messages of the last 2 seconds.
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            DiscussionHistory history = new DiscussionHistory();
+            history.setSeconds(2);
+            muc2.join("testbot2", null, history, SmackConfiguration.getPacketReplyTimeout());
+    
+            Message msg;
+            // Get first historic message
+            msg = muc2.nextMessage(1000);
+            DelayInformation delay = (DelayInformation) msg.getExtension("x", "jabber:x:delay");
+            SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+            UTC_FORMAT.setTimeZone(TimeZone.getDefault());
+            System.out.println(UTC_FORMAT.format(delay.getStamp()));
+            
+            assertNotNull("First message is null", msg);
+            assertEquals("Body of first message is incorrect", "Message 3", msg.getBody());
+            // Try to get second historic message 
+            msg = muc2.nextMessage(1000);
+            assertNull("Second message is not null", msg);
+
+
+            // User3 joins the room requesting to receive the last 2 messages.
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            history = new DiscussionHistory();
+            history.setMaxStanzas(2);
+            muc3.join("testbot3", null, history, SmackConfiguration.getPacketReplyTimeout());
+
+            // Get first historic message 
+            msg = muc3.nextMessage(1000);
+            assertNotNull("First message is null", msg);
+            assertEquals("Body of first message is incorrect", "Message 2", msg.getBody());
+            // Get second historic message 
+            msg = muc3.nextMessage(1000);
+            assertNotNull("Second message is null", msg);
+            assertEquals("Body of second message is incorrect", "Message 3", msg.getBody());
+            // Try to get third historic message 
+            msg = muc3.nextMessage(1000);
+            assertNull("Third message is not null", msg);
+    
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+    
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testParticipantPresence() {
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            Thread.sleep(400);
+
+            // User1 checks the presence of user2 in the room
+            Presence presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNotNull("Presence of user2 in room is missing", presence);
+            assertEquals(
+                "Presence mode of user2 is wrong",
+                Presence.Mode.AVAILABLE,
+                presence.getMode());
+
+            // User2 changes his availability to AWAY
+            muc2.changeAvailabilityStatus("Gone to have lunch", Presence.Mode.AWAY);
+            Thread.sleep(200);
+            // User1 checks the presence of user2 in the room
+            presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNotNull("Presence of user2 in room is missing", presence);
+            assertEquals("Presence mode of user2 is wrong", Presence.Mode.AWAY, presence.getMode());
+            assertEquals(
+                "Presence status of user2 is wrong",
+                "Gone to have lunch",
+                presence.getStatus());
+
+            // User2 changes his nickname
+            muc2.changeNickname("testbotII");
+            Thread.sleep(200);
+            // User1 checks the presence of user2 in the room
+            presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNull("Presence of participant testbot2 still exists", presence);
+            presence = muc.getOccupantPresence(room + "/testbotII");
+            assertNotNull("Presence of participant testbotII does not exist", presence);
+            assertEquals(
+                "Presence of participant testbotII has a wrong from",
+                room + "/testbotII",
+                presence.getFrom());
+
+            // User2 leaves the room
+            muc2.leave();
+            Thread.sleep(250);
+            // User1 checks the presence of user2 in the room
+            presence = muc.getOccupantPresence(room + "/testbotII");
+            assertNull("Presence of participant testbotII still exists", presence);
+
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testAnonymousParticipant() {
+        try {
+            // Anonymous user joins the new room
+            XMPPConnection anonConnection = new XMPPConnection(getHost(), getPort());
+            anonConnection.loginAnonymously();
+            MultiUserChat muc2 = new MultiUserChat(anonConnection, room);
+            muc2.join("testbot2");
+            Thread.sleep(400);
+
+            // User1 checks the presence of Anonymous user in the room
+            Presence presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNotNull("Presence of user2 in room is missing", presence);
+            assertEquals(
+                "Presence mode of user2 is wrong",
+                Presence.Mode.AVAILABLE,
+                presence.getMode());
+
+            // Anonymous user leaves the room
+            muc2.leave();
+            anonConnection.close();
+            Thread.sleep(250);
+            // User1 checks the presence of Anonymous user in the room
+            presence = muc.getOccupantPresence(room + "/testbot2");
+            assertNull("Presence of participant testbotII still exists", presence);
+
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testInvitation() {
+        final String[] answer = new String[2];
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+
+            // User3 is listening to MUC invitations
+            MultiUserChat.addInvitationListener(getConnection(2), new InvitationListener() {
+                public void invitationReceived(
+                    XMPPConnection conn,
+                    String room,
+                    String inviter,
+                    String reason,
+                    String password,
+                    Message message) {
+                    // Indicate that the invitation was received
+                    answer[0] = reason;
+                    // Reject the invitation
+                    MultiUserChat.decline(conn, room, inviter, "I'm busy right now");
+                }
+            });
+
+            // User2 is listening to invitation rejections
+            muc2.addInvitationRejectionListener(new InvitationRejectionListener() {
+                public void invitationDeclined(String invitee, String reason) {
+                    // Indicate that the rejection was received
+                    answer[1] = reason;
+                }
+            });
+
+            // User2 invites user3 to join to the room
+            muc2.invite(getFullJID(2), "Meet me in this excellent room");
+            Thread.sleep(350);
+
+            assertEquals(
+                "Invitation was not received",
+                "Meet me in this excellent room",
+                answer[0]);
+            assertEquals("Rejection was not received", "I'm busy right now", answer[1]);
+
+            // User2 leaves the room
+            muc2.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testInvitationWithMessage() {
+        final String[] answer = new String[2];
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+
+            // User3 is listening to MUC invitations
+            MultiUserChat.addInvitationListener(getConnection(2), new InvitationListener() {
+                public void invitationReceived(
+                    XMPPConnection conn,
+                    String room,
+                    String inviter,
+                    String reason,
+                    String password,
+                    Message message) {
+                    // Indicate that the invitation was received
+                    answer[0] = reason;
+                    XHTMLExtension extension = (XHTMLExtension) message.getExtension("html",
+                            "http://jabber.org/protocol/xhtml-im");
+                    assertNotNull("An extension was not found in the invitation", extension);                    
+                    answer[1] = (String) extension.getBodies().next();
+                }
+            });
+
+            // User2 invites user3 to join to the room
+            Message msg = new Message();
+            XHTMLExtension xhtmlExtension = new XHTMLExtension();
+            xhtmlExtension.addBody("<body>Meet me in this excellent room</body>");
+            msg.addExtension(xhtmlExtension);
+            muc2.invite(msg , getFullJID(2), "Meet me in this excellent room");
+            Thread.sleep(350);
+
+            assertEquals(
+                "Invitation was not received",
+                "Meet me in this excellent room",
+                answer[0]);
+            assertEquals("Rejection was not received",
+                    "<body>Meet me in this excellent room</body>", answer[1]);
+
+            // User2 leaves the room
+            muc2.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testDiscoverJoinedRooms() {
+        try {
+            // Check that user1 has joined only to one room
+            Iterator joinedRooms = MultiUserChat.getJoinedRooms(getConnection(1), getFullJID(0));
+            assertTrue("Joined rooms shouldn't be empty", joinedRooms.hasNext());
+            assertEquals("Joined room is incorrect", joinedRooms.next(), room);
+            assertFalse("User has joined more than one room", joinedRooms.hasNext());
+
+            // Leave the new room
+            muc.leave();
+
+            // Check that user1 is not currently join any room
+            joinedRooms = MultiUserChat.getJoinedRooms(getConnection(1), getFullJID(0));
+            assertFalse("Joined rooms should be empty", joinedRooms.hasNext());
+
+            muc.join("testbot");
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testDiscoverMUCSupport() {
+        // Discover user1 support of MUC
+        boolean supports = MultiUserChat.isServiceEnabled(getConnection(1), getFullJID(0));
+        assertTrue("Couldn't detect that user1 supports MUC", supports);
+    }
+
+    public void testDiscoverRoomInfo() {
+        try {
+            makeRoomModerated();
+
+            RoomInfo info = MultiUserChat.getRoomInfo(getConnection(1), room);
+
+            assertFalse("Room is members-only", info.isMembersOnly());
+            assertTrue("Room is moderated", info.isModerated());
+            assertFalse("Room is Nonanonymous", info.isNonanonymous());
+            assertFalse("Room is PasswordProtected", info.isPasswordProtected());
+            assertFalse("Room is Persistent", info.isPersistent());
+            assertEquals("Room's description is incorrect", "fruta124", info.getDescription());
+            assertEquals("Room's subject is incorrect", "", info.getSubject());
+            assertEquals("Number of occupants is incorrect", 1, info.getOccupantsCount());
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testDiscoverMUCService() {
+        try {
+            Collection services = MultiUserChat.getServiceNames(getConnection(1));
+            assertFalse("No MUC service was found", services.isEmpty());
+
+            // Discover the hosted rooms by the chat service.
+            Collection rooms = MultiUserChat.getHostedRooms(getConnection(1),
+                    (String) services.toArray()[0]);
+            // Check that we have discovered the room used by this test
+            assertFalse("No room was found", rooms.isEmpty());
+            // Check that we have discovered the room used by this test
+            assertEquals("Wrong room JID found", room, ((HostedRoom)rooms.toArray()[0]).getJid());
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testPrivateChat() {
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+
+            getConnection(0).addPacketListener(new PacketListener() {
+                public void processPacket(Packet packet) {
+                    Message message = (Message) packet;
+                    Chat chat2 = new Chat(getConnection(0), message.getFrom(), message.getThread());
+                    assertEquals(
+                        "Sender of chat is incorrect",
+                        room + "/testbot2",
+                        message.getFrom());
+                    try {
+                        chat2.sendMessage("ACK");
+                    }
+                    catch (XMPPException e) {
+                        fail(e.getMessage());
+                    }
+                }
+            },
+                new AndFilter(
+                    new MessageTypeFilter(Message.Type.CHAT),
+                    new PacketTypeFilter(Message.class)));
+
+            // Start a private chat with another participant            
+            Chat chat = muc2.createPrivateChat(room + "/testbot");
+            chat.sendMessage("Hello there");
+
+            Message response = chat.nextMessage(2000);
+            assertEquals("Sender of response is incorrect", room + "/testbot", response.getFrom());
+            assertEquals("Body of response is incorrect", "ACK", response.getBody());
+
+            // User2 leaves the room
+            muc2.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testReservedNickname() {
+        try {
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            // Check that user2 doesn't have a reserved nickname yet
+            String reservedNickname = muc2.getReservedNickname();
+            assertNull("Reserved nickname is not null", reservedNickname);
+            
+            // User2 registers with the room and reserves a nickname
+            Form registrationForm = muc2.getRegistrationForm();
+            Form answerForm = registrationForm.createAnswerForm();
+            answerForm.setAnswer("muc#register_first", "MyFirstName");
+            answerForm.setAnswer("muc#register_last", "MyLastName");
+            answerForm.setAnswer("muc#register_roomnick", "MyNick");
+            muc2.sendRegistrationForm(answerForm);
+            
+            // Check that user2 has a reserved nickname
+            reservedNickname = muc2.getReservedNickname();
+            assertEquals("Reserved nickname is wrong", "MyNick", reservedNickname);
+            
+            // Check that user2 can join the room using his reserved nickname
+            muc2.join("MyNick");
+            muc2.leave();
+            
+            // Check that other users cannot join the room with user2's reserved nickname
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            try {
+                muc3.join("MyNick");
+                fail("Other user was able to join with other user's reserved nickname");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull(
+                    "No XMPPError was received when joining with other user's reserved nickname",
+                    xmppError);
+                assertEquals(
+                    "Different error code was received while joining with other user's reserved nickname",
+                    409,
+                    xmppError.getCode());
+            }
+            
+            // Check that user3 can join the room using his own nickname (not reserved)
+            muc3.join("MyNotReservedNick");
+            muc3.leave();
+            
+            // Check that another user cannot reserve an already reserved nickname
+            registrationForm = muc3.getRegistrationForm();
+            answerForm = registrationForm.createAnswerForm();
+            answerForm.setAnswer("muc#register_first", "MyFirstName 2");
+            answerForm.setAnswer("muc#register_last", "MyLastName 2");
+            answerForm.setAnswer("muc#register_roomnick", "MyNick");
+            try {
+                muc3.sendRegistrationForm(answerForm);
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull(
+                    "No XMPPError was received when reserving an already reserved nickname",
+                    xmppError);
+                assertEquals(
+                    "Different error code was received while reserving an already reserved nickname",
+                    409,
+                    xmppError.getCode());
+            }
+            
+            // Check that another user can reserve a new nickname
+            registrationForm = muc3.getRegistrationForm();
+            answerForm = registrationForm.createAnswerForm();
+            answerForm.setAnswer("muc#register_first", "MyFirstName 2");
+            answerForm.setAnswer("muc#register_last", "MyLastName 2");
+            answerForm.setAnswer("muc#register_roomnick", "MyNick 2");
+            muc3.sendRegistrationForm(answerForm);
+            
+        }
+        catch (XMPPException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testChangeSubject() {
+        final String[] answer = new String[2];
+        try {
+            // User1 sets an initial subject
+            muc.changeSubject("Initial Subject");
+
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+
+            // User3 joins the new room
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+
+            // User3 wants to be notified every time the room's subject is changed.
+            muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() {
+                public void subjectUpdated(String subject, String from) {
+                    answer[0] = subject;
+                    answer[1] = from;
+                }
+            });
+
+            // Check that a 403 error is received when a not allowed user tries to change the 
+            // subject in a room
+            try {
+                muc2.changeSubject("New Subject2");
+                fail("User2 was allowed to change the room's subject");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull(
+                    "No XMPPError was received when changing the room's subject",
+                    xmppError);
+                assertEquals(
+                    "Different error code was received while changing the room's subject",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that every MUC updates its subject when an allowed user changes the subject 
+            // in a room
+            muc.changeSubject("New Subject1");
+            Thread.sleep(300);
+            // Check that User2's MUC has updated its subject
+            assertEquals(
+                "User2 didn't receive the subject notification",
+                "New Subject1",
+                muc2.getSubject());
+            // Check that SubjectUpdatedListener is working OK
+            assertEquals(
+                "User3 didn't receive the subject notification",
+                "New Subject1",
+                answer[0]);
+            assertEquals(
+                "User3 didn't receive the correct user that changed the subject",
+                room + "/testbot",
+                answer[1]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testKickParticipant() {
+        final String[] answer = new String[3];
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will lister for his own "kicking"            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void kicked(String actor, String reason) {
+                    super.kicked(actor, reason);
+                    answer[0] = actor;
+                    answer[1] = reason;
+                }
+            });
+
+            // User3 joins the new room
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's "kicking"            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void kicked(String participant) {
+                    super.kicked(participant);
+                    answer[2] = participant;
+                }
+            });
+
+            try {
+                // Check whether a simple participant can kick a room owner or not
+                muc2.kickParticipant("testbot", "Because I'm bad");
+                fail("User2 was able to kick a room owner");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received when kicking a room owner", xmppError);
+                assertEquals(
+                    "A simple participant was able to kick another participant from the room",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can kick a simple participant
+            muc.kickParticipant("testbot2", "Because I'm the owner");
+            Thread.sleep(300);
+
+            assertNull(
+                "User2 wasn't kicked from the room",
+                muc.getOccupant(room + "/testbot2"));
+
+            assertFalse("User2 thinks that he's still in the room", muc2.isJoined());
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the correct initiator of the kick",
+                getBareJID(0),
+                answer[0]);
+            assertEquals(
+                "User2 didn't receive the correct reason for the kick",
+                "Because I'm the owner",
+                answer[1]);
+
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive the correct kicked participant",
+                room + "/testbot2",
+                answer[2]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testBanUser() {
+        final String[] answer = new String[3];
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will lister for his own "banning"            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void banned(String actor, String reason) {
+                    super.banned(actor, reason);
+                    answer[0] = actor;
+                    answer[1] = reason;
+                }
+            });
+
+            // User3 joins the new room
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's "banning"            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void banned(String participant) {
+                    super.banned(participant);
+                    answer[2] = participant;
+                }
+            });
+
+            try {
+                // Check whether a simple participant can ban a room owner or not
+                muc2.banUser(getBareJID(0), "Because I'm bad");
+                fail("User2 was able to ban a room owner");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received when banning a room owner", xmppError);
+                assertEquals(
+                    "A simple participant was able to ban another participant from the room",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can ban a simple participant
+            muc.banUser(getBareJID(1), "Because I'm the owner");
+            Thread.sleep(300);
+
+            assertNull(
+                "User2 wasn't banned from the room",
+                muc.getOccupant(room + "/testbot2"));
+
+            assertFalse("User2 thinks that he's still in the room", muc2.isJoined());
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the correct initiator of the ban",
+                getBareJID(0),
+                answer[0]);
+            assertEquals(
+                "User2 didn't receive the correct reason for the banning",
+                "Because I'm the owner",
+                answer[1]);
+
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive the correct banned JID",
+                room + "/testbot2",
+                answer[2]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testVoice() {
+        final String[] answer = new String[4];
+        try {
+
+            makeRoomModerated();
+
+            // User2 joins the new room (as a visitor)
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will listen for his own "voice"            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void voiceGranted() {
+                    super.voiceGranted();
+                    answer[0] = "canSpeak";
+                }
+                public void voiceRevoked() {
+                    super.voiceRevoked();
+                    answer[1] = "cannot speak";
+                }
+            });
+
+            // User3 joins the new room (as a visitor)
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's "voice"            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void voiceGranted(String participant) {
+                    super.voiceGranted(participant);
+                    answer[2] = participant;
+                }
+
+                public void voiceRevoked(String participant) {
+                    super.voiceRevoked(participant);
+                    answer[3] = participant;
+                }
+            });
+
+            try {
+                // Check whether a visitor can grant voice to another visitor
+                muc2.grantVoice("testbot3");
+                fail("User2 was able to grant voice");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received granting voice", xmppError);
+                assertEquals(
+                    "A visitor was able to grant voice to another visitor",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can grant voice to a participant
+            muc.grantVoice("testbot2");
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the grant voice notification",
+                "canSpeak",
+                answer[0]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's grant voice notification",
+                room + "/testbot2",
+                answer[2]);
+
+            // Check that the room's owner can revoke voice from a participant
+            muc.revokeVoice("testbot2");
+            Thread.sleep(300);
+
+            assertEquals(
+                "User2 didn't receive the revoke voice notification",
+                "cannot speak",
+                answer[1]);
+            assertEquals(
+                "User3 didn't receive user2's revoke voice notification",
+                room + "/testbot2",
+                answer[3]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testModerator() {
+        final String[] answer = new String[8];
+        try {
+
+            makeRoomModerated();
+
+            // User2 joins the new room (as a visitor)
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will listen for moderator privileges            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void voiceGranted() {
+                    super.voiceGranted();
+                    answer[0] = "canSpeak";
+                }
+                public void voiceRevoked() {
+                    super.voiceRevoked();
+                    answer[1] = "cannot speak";
+                }
+                public void moderatorGranted() {
+                    super.moderatorGranted();
+                    answer[4] = "I'm a moderator";
+                }
+                public void moderatorRevoked() {
+                    super.moderatorRevoked();
+                    answer[5] = "I'm not a moderator";
+                }
+            });
+
+            // User3 joins the new room (as a visitor)
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's moderator privileges            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void voiceGranted(String participant) {
+                    super.voiceGranted(participant);
+                    answer[2] = participant;
+                }
+                public void voiceRevoked(String participant) {
+                    super.voiceRevoked(participant);
+                    answer[3] = participant;
+                }
+                public void moderatorGranted(String participant) {
+                    super.moderatorGranted(participant);
+                    answer[6] = participant;
+                }
+                public void moderatorRevoked(String participant) {
+                    super.moderatorRevoked(participant);
+                    answer[7] = participant;
+                }
+            });
+
+            try {
+                // Check whether a visitor can grant moderator privileges to another visitor
+                muc2.grantModerator("testbot3");
+                fail("User2 was able to grant moderator privileges");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received granting moderator privileges", xmppError);
+                assertEquals(
+                    "A visitor was able to grant moderator privileges to another visitor",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can grant moderator privileges to a visitor
+            muc.grantModerator("testbot2");
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the grant voice notification",
+                "canSpeak",
+                answer[0]);
+            assertEquals(
+                "User2 didn't receive the grant moderator privileges notification",
+                "I'm a moderator",
+                answer[4]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's grant voice notification",
+                room + "/testbot2",
+                answer[2]);
+            assertEquals(
+                "User3 didn't receive user2's grant moderator privileges notification",
+                room + "/testbot2",
+                answer[6]);
+
+            // Check that the room's owner can revoke moderator privileges from a moderator
+            muc.revokeModerator("testbot2");
+            Thread.sleep(300);
+
+            assertNull("User2 received a false revoke voice notification", answer[1]);
+            assertNull("User3 received a false user2's voice privileges notification", answer[3]);
+            assertEquals(
+                "User2 didn't receive the revoke moderator privileges notification",
+                "I'm not a moderator",
+                answer[5]);
+            assertEquals(
+                "User3 didn't receive user2's revoke moderator privileges notification",
+                room + "/testbot2",
+                answer[7]);
+
+            // Check that the room's owner can grant moderator privileges to a participant
+            clearAnswer(answer);
+            muc.grantModerator("testbot2");
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertNull("User2 received a false grant voice notification", answer[0]);
+            assertEquals(
+                "User2 didn't receive the grant moderator privileges notification",
+                "I'm a moderator",
+                answer[4]);
+            // Check that ParticipantStatusListener is working OK
+            assertNull("User3 received a false user2's grant voice notification", answer[2]);
+            assertEquals(
+                "User3 didn't receive user2's grant moderator privileges notification",
+                room + "/testbot2",
+                answer[6]);
+
+            // Check that the room's owner can revoke voice from a moderator
+            clearAnswer(answer);
+            muc.revokeVoice("testbot2");
+            Thread.sleep(300);
+
+            assertEquals(
+                "User2 didn't receive the revoke voice notification",
+                "cannot speak",
+                answer[1]);
+            assertEquals(
+                "User3 didn't receive user2's revoke voice notification",
+                room + "/testbot2",
+                answer[3]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testMembership() {
+        final String[] answer = new String[4];
+        try {
+
+            makeRoomModerated();
+
+            // User2 joins the new room (as a visitor)
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will listen for membership privileges            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void membershipGranted() {
+                    super.membershipGranted();
+                    answer[0] = "I'm a member";
+                }
+                public void membershipRevoked() {
+                    super.membershipRevoked();
+                    answer[1] = "I'm not a member";
+                }
+            });
+
+            // User3 joins the new room (as a visitor)
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's membership privileges            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void membershipGranted(String participant) {
+                    super.membershipGranted(participant);
+                    answer[2] = participant;
+                }
+                public void membershipRevoked(String participant) {
+                    super.membershipRevoked(participant);
+                    answer[3] = participant;
+                }
+            });
+
+            try {
+                // Check whether a visitor can grant membership privileges to another visitor
+                muc2.grantMembership(getBareJID(2));
+                fail("User2 was able to grant membership privileges");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull(
+                    "No XMPPError was received granting membership privileges",
+                    xmppError);
+                assertEquals(
+                    "A visitor was able to grant membership privileges to another visitor",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can grant membership privileges to a visitor
+            muc.grantMembership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the grant membership notification",
+                "I'm a member",
+                answer[0]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's grant membership notification",
+                room + "/testbot2",
+                answer[2]);
+
+            // Check that the room's owner can revoke membership privileges from a member
+            // and make the occupant a visitor
+            muc.revokeMembership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke membership notification",
+                "I'm not a member",
+                answer[1]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke membership notification",
+                room + "/testbot2",
+                answer[3]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testAdmin() {
+        final String[] answer = new String[8];
+        try {
+
+            makeRoomModerated();
+
+            // User2 joins the new room (as a visitor)
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will listen for admin privileges            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void membershipGranted() {
+                    super.membershipGranted();
+                    answer[0] = "I'm a member";
+                }
+                public void membershipRevoked() {
+                    super.membershipRevoked();
+                    answer[1] = "I'm not a member";
+                }
+                public void adminGranted() {
+                    super.adminGranted();
+                    answer[2] = "I'm an admin";
+                }
+                public void adminRevoked() {
+                    super.adminRevoked();
+                    answer[3] = "I'm not an admin";
+                }
+            });
+
+            // User3 joins the new room (as a visitor)
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's admin privileges            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void membershipGranted(String participant) {
+                    super.membershipGranted(participant);
+                    answer[4] = participant;
+                }
+                public void membershipRevoked(String participant) {
+                    super.membershipRevoked(participant);
+                    answer[5] = participant;
+                }
+                public void adminGranted(String participant) {
+                    super.adminGranted(participant);
+                    answer[6] = participant;
+                }
+                public void adminRevoked(String participant) {
+                    super.adminRevoked(participant);
+                    answer[7] = participant;
+                }
+            });
+
+            try {
+                // Check whether a visitor can grant admin privileges to another visitor
+                muc2.grantAdmin(getBareJID(2));
+                fail("User2 was able to grant admin privileges");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received granting admin privileges", xmppError);
+                assertEquals(
+                    "A visitor was able to grant admin privileges to another visitor",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can grant admin privileges to a visitor
+            muc.grantAdmin(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the grant admin notification",
+                "I'm an admin",
+                answer[2]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's grant admin notification",
+                room + "/testbot2",
+                answer[6]);
+
+            // Check that the room's owner can revoke admin privileges from an admin
+            // and make the occupant a visitor
+            muc.revokeMembership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke admin notification",
+                "I'm not an admin",
+                answer[3]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke admin notification",
+                room + "/testbot2",
+                answer[7]);
+
+            // Check that the room's owner can grant admin privileges to a member
+            clearAnswer(answer);
+            muc.grantMembership(getBareJID(1));
+            Thread.sleep(300);
+            muc.grantAdmin(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke membership notification",
+                "I'm not a member",
+                answer[1]);
+            assertEquals(
+                "User2 didn't receive the grant admin notification",
+                "I'm an admin",
+                answer[2]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke membership notification",
+                room + "/testbot2",
+                answer[5]);
+            assertEquals(
+                "User3 didn't receive user2's grant admin notification",
+                room + "/testbot2",
+                answer[6]);
+
+            // Check that the room's owner can revoke admin privileges from an admin
+            // and make the occupant a member
+            clearAnswer(answer);
+            muc.revokeAdmin(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke admin notification",
+                "I'm not an admin",
+                answer[3]);
+            assertEquals(
+                "User2 didn't receive the grant membership notification",
+                "I'm a member",
+                answer[0]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke admin notification",
+                room + "/testbot2",
+                answer[7]);
+            assertEquals(
+                "User3 didn't receive user2's grant membership notification",
+                room + "/testbot2",
+                answer[4]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testOwnership() {
+        final String[] answer = new String[12];
+        try {
+
+            makeRoomModerated();
+
+            // User2 joins the new room (as a visitor)
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+            // User2 will listen for ownership privileges            
+            muc2.addUserStatusListener(new DefaultUserStatusListener() {
+                public void membershipGranted() {
+                    super.membershipGranted();
+                    answer[0] = "I'm a member";
+                }
+                public void membershipRevoked() {
+                    super.membershipRevoked();
+                    answer[1] = "I'm not a member";
+                }
+                public void adminGranted() {
+                    super.adminGranted();
+                    answer[2] = "I'm an admin";
+                }
+                public void adminRevoked() {
+                    super.adminRevoked();
+                    answer[3] = "I'm not an admin";
+                }
+                public void ownershipGranted() {
+                    super.ownershipGranted();
+                    answer[4] = "I'm an owner";
+                }
+                public void ownershipRevoked() {
+                    super.ownershipRevoked();
+                    answer[5] = "I'm not an owner";
+                }
+            });
+
+            // User3 joins the new room (as a visitor)
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+            // User3 will lister for user2's ownership privileges            
+            muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void membershipGranted(String participant) {
+                    super.membershipGranted(participant);
+                    answer[6] = participant;
+                }
+                public void membershipRevoked(String participant) {
+                    super.membershipRevoked(participant);
+                    answer[7] = participant;
+                }
+                public void adminGranted(String participant) {
+                    super.adminGranted(participant);
+                    answer[8] = participant;
+                }
+                public void adminRevoked(String participant) {
+                    super.adminRevoked(participant);
+                    answer[9] = participant;
+                }
+                public void ownershipGranted(String participant) {
+                    super.ownershipGranted(participant);
+                    answer[10] = participant;
+                }
+                public void ownershipRevoked(String participant) {
+                    super.ownershipRevoked(participant);
+                    answer[11] = participant;
+                }
+            });
+
+            try {
+                // Check whether a visitor can grant ownership privileges to another visitor
+                muc2.grantOwnership(getBareJID(2));
+                fail("User2 was able to grant ownership privileges");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received granting ownership privileges", xmppError);
+                assertEquals(
+                    "A visitor was able to grant ownership privileges to another visitor",
+                    403,
+                    xmppError.getCode());
+            }
+
+            // Check that the room's owner can grant ownership privileges to a visitor
+            muc.grantOwnership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the grant ownership notification",
+                "I'm an owner",
+                answer[4]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's grant ownership notification",
+                room + "/testbot2",
+                answer[10]);
+
+            // Check that the room's owner can revoke ownership privileges from an owner
+            // and make the occupant a visitor
+            muc.revokeMembership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke ownership notification",
+                "I'm not an owner",
+                answer[5]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke ownership notification",
+                room + "/testbot2",
+                answer[11]);
+
+            // Check that the room's owner can grant ownership privileges to a member
+            clearAnswer(answer);
+            muc.grantMembership(getBareJID(1));
+            Thread.sleep(300);
+            muc.grantOwnership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke membership notification",
+                "I'm not a member",
+                answer[1]);
+            assertEquals(
+                "User2 didn't receive the grant ownership notification",
+                "I'm an owner",
+                answer[4]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke membership notification",
+                room + "/testbot2",
+                answer[7]);
+            assertEquals(
+                "User3 didn't receive user2's grant ownership notification",
+                room + "/testbot2",
+                answer[10]);
+
+            // Check that the room's owner can revoke ownership privileges from an owner
+            // and make the occupant a member
+            clearAnswer(answer);
+            muc.revokeAdmin(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke ownership notification",
+                "I'm not an owner",
+                answer[5]);
+            assertEquals(
+                "User2 didn't receive the grant membership notification",
+                "I'm a member",
+                answer[0]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke ownership notification",
+                room + "/testbot2",
+                answer[11]);
+            assertEquals(
+                "User3 didn't receive user2's grant membership notification",
+                room + "/testbot2",
+                answer[6]);
+
+            // Check that the room's owner can grant ownership privileges to an admin
+            clearAnswer(answer);
+            muc.grantAdmin(getBareJID(1));
+            Thread.sleep(300);
+            muc.grantOwnership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke admin notification",
+                "I'm not an admin",
+                answer[3]);
+            assertEquals(
+                "User2 didn't receive the grant ownership notification",
+                "I'm an owner",
+                answer[4]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke admin notification",
+                room + "/testbot2",
+                answer[9]);
+            assertEquals(
+                "User3 didn't receive user2's grant ownership notification",
+                room + "/testbot2",
+                answer[10]);
+
+            // Check that the room's owner can revoke ownership privileges from an owner
+            // and make the occupant an admin
+            clearAnswer(answer);
+            muc.revokeOwnership(getBareJID(1));
+            Thread.sleep(300);
+
+            // Check that UserStatusListener is working OK
+            assertEquals(
+                "User2 didn't receive the revoke ownership notification",
+                "I'm not an owner",
+                answer[5]);
+            assertEquals(
+                "User2 didn't receive the grant admin notification",
+                "I'm an admin",
+                answer[2]);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User3 didn't receive user2's revoke ownership notification",
+                room + "/testbot2",
+                answer[11]);
+            assertEquals(
+                "User3 didn't receive user2's grant admin notification",
+                room + "/testbot2",
+                answer[8]);
+
+            // User2 leaves the room
+            muc2.leave();
+            // User3 leaves the room
+            muc3.leave();
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testGetAffiliationList() {
+        try {
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            muc2.join("testbot2");
+
+            // User3 joins the new room
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+
+            // Grant ownership privileges to user2
+            muc.grantOwnership(getBareJID(1));
+            // Grant moderator privileges to user3
+            muc.grantModerator("testbot3");
+
+            // Check that the owner list is correct
+            Collection affiliates = muc.getOwners();
+            assertEquals("Room does not have 2 owners", 2, affiliates.size());
+            for (Iterator it =affiliates.iterator(); it.hasNext();) {
+                Affiliate affiliate = (Affiliate)it.next();
+                if (getBareJID(0).equals(affiliate.getJid())) {
+                    assertEquals("Wrong affiliation", "owner", affiliate.getAffiliation());
+                    assertEquals("Wrong role", "moderator", affiliate.getRole());
+                    assertEquals("Wrong nick", "testbot", affiliate.getNick());
+                }
+                else if (getBareJID(1).equals(affiliate.getJid())) {
+                    assertEquals("Wrong affiliation", "owner", affiliate.getAffiliation());
+                    assertEquals("Wrong role", "moderator", affiliate.getRole());
+                    assertEquals("Wrong nick", "testbot2", affiliate.getNick());
+                }
+                else {
+                    fail("Unknown owner " + affiliate.getJid());
+                }
+            }
+
+            // Check that the admin list is correct
+            affiliates = muc.getAdmins();
+            assertEquals("Room has admins", 0, affiliates.size());
+
+            // Check that the members list is correct
+            affiliates = muc.getMembers();
+            assertEquals("Room has admins", 0, affiliates.size());
+            // Grant membership privileges to user2
+            muc.grantMembership(getBareJID(1));
+            // Check that the members list is correct
+            affiliates = muc.getMembers();
+            assertEquals("Room has admins", 1, affiliates.size());
+            Affiliate affiliate = (Affiliate) affiliates.iterator().next();
+            assertEquals("Wrong member jid", getBareJID(1), affiliate.getJid());
+
+            // Check that the members list is correct
+            affiliates = muc.getOutcasts();
+            assertEquals("Room has outcasts", 0, affiliates.size());
+
+            // Check that the moderator list is correct
+            Collection occupants = muc.getModerators();
+            assertEquals("Room does not have 2 moderators", 2, occupants.size());
+            for (Iterator it =occupants.iterator(); it.hasNext();) {
+                Occupant occupant = (Occupant)it.next();
+                if (getFullJID(0).equals(occupant.getJid())) {
+                    assertEquals("Wrong affiliation", "owner", occupant.getAffiliation());
+                    assertEquals("Wrong role", "moderator", occupant.getRole());
+                    assertEquals("Wrong nick", "testbot", occupant.getNick());
+                }
+                else if (getFullJID(2).equals(occupant.getJid())) {
+                    assertEquals("Wrong affiliation", "none", occupant.getAffiliation());
+                    assertEquals("Wrong role", "moderator", occupant.getRole());
+                    assertEquals("Wrong nick", "testbot3", occupant.getNick());
+                }
+                else {
+                    fail("Unknown moderator " + occupant.getJid());
+                }
+            }
+
+            // Check that the participants list is correct
+            occupants = muc.getParticipants();
+            assertEquals("Room does not have 1 participant", 1, occupants.size());
+            Occupant occupant = (Occupant) occupants.iterator().next();
+            assertEquals("Wrong participant jid", getFullJID(1), occupant.getJid());
+
+            Thread.sleep(500);
+            
+            // Check that we can retrieve Occupant information of a given user
+            occupant = muc.getOccupant(room + "/testbot2");
+            assertNotNull("Occupant was not found", occupant);
+            assertEquals("Wrong occupant jid", getFullJID(1), occupant.getJid());
+            assertEquals("Wrong occupant affiliation", "member", occupant.getAffiliation());
+            assertEquals("Wrong occupant role", "participant", occupant.getRole());
+            assertEquals("Wrong occupant nick", "testbot2", occupant.getNick());            
+
+            try {
+                // Check whether a member can get the list of owners
+                muc2.getOwners();
+                fail("User2 was able to get the list of owners");
+            }
+            catch (XMPPException e) {
+                XMPPError xmppError = e.getXMPPError();
+                assertNotNull("No XMPPError was received getting the list of owners", xmppError);
+                assertEquals(
+                    "A member was able to get the list of owners",
+                    403,
+                    xmppError.getCode());
+            }
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Check that ParticipantStatusListener is receiving joining and leaving events correctly. 
+     */
+    public void testJoinLeftEvents() {
+        final String[] answer = new String[8];
+        try {
+            // User1 will listen for occupants joining and leaving the room
+            muc.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void joined(String participant) {
+                    super.joined(participant);
+                    if ((room + "/testbot2").equals(participant)) {
+                        answer[0] = participant;
+                    }
+                    else {
+                        answer[1] = participant;
+                    }
+                }
+                public void left(String participant) {
+                    super.left(participant);
+                    if ((room + "/testbot2").equals(participant)) {
+                        answer[2] = participant;
+                    }
+                    // Skip unavailable presences of the same user
+                    else if (!(room + "/testbot").equals(participant)) {
+                        answer[3] = participant;
+                    }
+                }
+            });
+
+            // User2 joins the new room
+            MultiUserChat muc2 = new MultiUserChat(getConnection(1), room);
+            // User2 will listen for User3 joining and leaving the room
+            muc2.addParticipantStatusListener(new DefaultParticipantStatusListener() {
+                public void joined(String participant) {
+                    super.joined(participant);
+                    if ((room + "/testbot").equals(participant)) {
+                        answer[4] = participant;
+                    }
+                    else {
+                        answer[5] = participant;
+                    }
+                }
+                public void left(String participant) {
+                    super.left(participant);
+                    if ((room + "/testbot").equals(participant)) {
+                        answer[6] = participant;
+                    }
+                    // Skip unavailable presences of the same user
+                    else if (!(room + "/testbot2").equals(participant)){
+                        answer[7] = participant;
+                    }
+                }
+            });
+            muc2.join("testbot2");
+
+            // User3 joins the new room
+            MultiUserChat muc3 = new MultiUserChat(getConnection(2), room);
+            muc3.join("testbot3");
+
+            Thread.sleep(150);
+
+            // User3 leaves the room
+            muc3.leave();
+            
+            Thread.sleep(150);
+            // User2 leaves the room
+            muc2.leave();
+
+            Thread.sleep(250);
+            // Check that ParticipantStatusListener is working OK
+            assertEquals(
+                "User1 didn't receive the event of User2 joining the room",
+                room + "/testbot2",
+                answer[0]);
+            assertEquals(
+                "User1 didn't receive the event of User3 joining the room",
+                room + "/testbot3",
+                answer[1]);
+            assertEquals(
+                "User1 didn't receive the event of User2 leaving the room",
+                room + "/testbot2",
+                answer[2]);
+            assertEquals(
+                "User1 didn't receive the event of User3 leaving the room",
+                room + "/testbot3",
+                answer[3]);
+            assertEquals(
+                "User2 didn't receive the event of User1 joining the room",
+                room + "/testbot",
+                answer[4]);
+            assertEquals(
+                "User2 didn't receive the event of User3 joining the room",
+                room + "/testbot3",
+                answer[5]);
+            assertNull(
+                "User2 received the event of User1 leaving the room",
+                answer[6]);
+            assertEquals(
+                "User2 didn't receive the event of User3 leaving the room",
+                room + "/testbot3",
+                answer[7]);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public void testManyResources() {
+        try {
+            // Create 20 more connections for user2
+            XMPPConnection[] conns = new XMPPConnection[20];
+            for (int i = 0; i < conns.length; i++) {
+                conns[i] = new XMPPConnection(getHost());
+                conns[i].login(getUsername(1), getUsername(1), "resource-" + i);
+            }
+
+            // Join the 20 connections to the same room
+            MultiUserChat[] mucs = new MultiUserChat[20];
+            for (int i = 0; i < mucs.length; i++) {
+                mucs[i] = new MultiUserChat(conns[i], room);
+                mucs[i].join("resource-" + i);
+            }
+
+            Thread.sleep(200);
+
+            // Each connection has something to say
+            for (int i = 0; i < mucs.length; i++) {
+                mucs[i].sendMessage("I'm resource-" + i);
+            }
+
+            Thread.sleep(200);
+
+            // Each connection leaves the room and closes the connection
+            for (int i = 0; i < mucs.length; i++) {
+                mucs[i].leave();
+                conns[i].close();
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+
+    }
+
+    private void makeRoomModerated() throws XMPPException {
+        // User1 (which is the room owner) converts the instant room into a moderated room
+        Form form = muc.getConfigurationForm();
+        Form answerForm = form.createAnswerForm();
+        answerForm.setAnswer("muc#roomconfig_moderatedroom", true);
+        // Keep the room owner
+        try {
+            List owners = new ArrayList();
+            owners.add(getBareJID(0));
+            answerForm.setAnswer("muc#roomconfig_roomowners", owners);
+        }
+        catch (IllegalArgumentException e) {
+            // Do nothing
+        }
+        muc.sendConfigurationForm(answerForm);
+    }
+
+    private void clearAnswer(String[] answer) {
+        for (int i = 0; i < answer.length; i++) {
+            answer[i] = null;
+        }
+    }
+
+    protected void setUp() throws Exception {
+        XMPPConnection.DEBUG_ENABLED = false;
+        super.setUp();
+        room = "fruta124@" + getMUCDomain();
+        try {
+            // User1 creates the room
+            muc = new MultiUserChat(getConnection(0), room);
+            muc.create("testbot");
+
+            // User1 sends an empty room configuration form which indicates that we want
+            // an instant room
+            muc.sendConfigurationForm(new Form(Form.TYPE_SUBMIT));
+        }
+        catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        // Destroy the new room
+        muc.destroy("The room has almost no activity...", null);
+
+        super.tearDown();
+    }
+
+    protected int getMaxConnections() {
+        return 3;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java
new file mode 100644
index 000000000..7dc4186ca
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/MessageEventTest.java
@@ -0,0 +1,171 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ *
+ * Test the MessageEvent extension using the low level API
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEventTest extends SmackTestCase {
+
+    /**
+     * Constructor for MessageEventTest.
+     * @param name
+     */
+    public MessageEventTest(String name) {
+        super(name);
+    }
+
+    /**
+     * Low level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives the 
+     * message
+     * 1. User_1 will send a message to user_2 requesting to be notified when any of these events
+     * occurs: offline, composing, displayed or delivered 
+     */
+    public void testSendMessageEventRequest() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("An interesting body comes here...");
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setComposing(true);
+        messageEvent.setDelivered(true);
+        messageEvent.setDisplayed(true);
+        messageEvent.setOffline(true);
+        msg.addExtension(messageEvent);
+
+        // Send the message that contains the notifications request
+        try {
+            chat1.sendMessage(msg);
+            // Wait half second so that the complete test can run
+            Thread.sleep(200);
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message");
+        }
+    }
+
+    /**
+     * Low level API test.
+     * This is a simple test to use with a XMPP client, check if the client receives the 
+     * message and display in the console any notification
+     * 1. User_1 will send a message to user_2 requesting to be notified when any of these events
+     * occurs: offline, composing, displayed or delivered 
+     * 2. User_2 will use a XMPP client (like Exodus) to display the message and compose a reply
+     * 3. User_1 will display any notification that receives
+     */
+    public void testSendMessageEventRequestAndDisplayNotifications() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // Create a Listener that listens for Messages with the extension "jabber:x:roster"
+        // This listener will listen on the conn2 and answer an ACK if everything is ok
+        PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event");
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                try {
+                    MessageEvent messageEvent =
+                        (MessageEvent) message.getExtension("x", "jabber:x:event");
+                    assertNotNull("Message without extension \"jabber:x:event\"", messageEvent);
+                    assertTrue(
+                        "Message event is a request not a notification",
+                        !messageEvent.isMessageEventRequest());
+                    System.out.println(messageEvent.toXML());
+                }
+                catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+            }
+        };
+        getConnection(0).addPacketListener(packetListener, packetFilter);
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("An interesting body comes here...");
+        // Create a MessageEvent Package and add it to the message
+        MessageEvent messageEvent = new MessageEvent();
+        messageEvent.setComposing(true);
+        messageEvent.setDelivered(true);
+        messageEvent.setDisplayed(true);
+        messageEvent.setOffline(true);
+        msg.addExtension(messageEvent);
+
+        // Send the message that contains the notifications request
+        try {
+            chat1.sendMessage(msg);
+            // Wait half second so that the complete test can run
+            Thread.sleep(200);
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message");
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java
new file mode 100644
index 000000000..5537d2f6a
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/RosterExchangeTest.java
@@ -0,0 +1,208 @@
+/*
+ * Created on 01/08/2003
+ *
+ */
+package org.jivesoftware.smackx.packet;
+
+import java.util.Iterator;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.*;
+
+/**
+ *
+ * Test the Roster Exchange extension using the low level API
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchangeTest extends SmackTestCase {
+
+    /**
+     * Constructor for RosterExchangeTest.
+     * @param arg0
+     */
+    public RosterExchangeTest(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Low level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives the message
+     * 1. User_1 will send his/her roster entries to user_2
+     */
+    public void testSendRosterEntries() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("This message contains roster items.");
+        // Create a RosterExchange Package and add it to the message
+        assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0);
+        RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster());
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        try {
+            chat1.sendMessage(msg);
+        } catch (Exception e) {
+            fail("An error occured sending the message with the roster");
+        }
+    }
+
+    /**
+    * Low level API test.
+    * 1. User_1 will send his/her roster entries to user_2
+    * 2. User_2 will receive the entries and iterate over them to check if everything is fine
+    * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then something is wrong
+    */
+    public void testSendAndReceiveRosterEntries() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a Listener that listens for Messages with the extension "jabber:x:roster"
+        // This listener will listen on the conn2 and answer an ACK if everything is ok
+        PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster");
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                assertNotNull("Body is null", message.getBody());
+                try {
+                    RosterExchange rosterExchange =
+                        (RosterExchange) message.getExtension("x", "jabber:x:roster");
+                    assertNotNull("Message without extension \"jabber:x:roster\"", rosterExchange);
+                    assertTrue(
+                        "Roster without entries",
+                        rosterExchange.getRosterEntries().hasNext());
+                    for (Iterator it = rosterExchange.getRosterEntries(); it.hasNext();) {
+                        RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) it.next();
+                    }
+                } catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+                try {
+                    chat2.sendMessage("ok");
+                } catch (Exception e) {
+                    fail("An error occured sending ack " + e.getMessage());
+                }
+            }
+        };
+        getConnection(1).addPacketListener(packetListener, packetFilter);
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("This message contains roster items.");
+        // Create a RosterExchange Package and add it to the message
+        assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0);
+        RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster());
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        try {
+            chat1.sendMessage(msg);
+        } catch (Exception e) {
+            fail("An error occured sending the message with the roster");
+        }
+        // Wait for 2 seconds for a reply
+        msg = chat1.nextMessage(2000);
+        assertNotNull("No reply received", msg);
+    }
+
+    /**
+     * Low level API test.
+     * 1. User_1 will send his/her roster entries to user_2
+     * 2. User_2 will automatically add the entries that receives to his/her roster in the corresponding group
+     * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then something is wrong
+     */
+    public void testSendAndAcceptRosterEntries() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a Listener that listens for Messages with the extension "jabber:x:roster"
+        // This listener will listen on the conn2, save the roster entries and answer an ACK if everything is ok
+        PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster");
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                assertNotNull("Body is null", message.getBody());
+                try {
+                    RosterExchange rosterExchange =
+                        (RosterExchange) message.getExtension("x", "jabber:x:roster");
+                    assertNotNull("Message without extension \"jabber:x:roster\"", rosterExchange);
+                    assertTrue(
+                        "Roster without entries",
+                        rosterExchange.getRosterEntries().hasNext());
+                    // Add the roster entries to user2's roster
+                    for (Iterator it = rosterExchange.getRosterEntries(); it.hasNext();) {
+                        RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) it.next();
+                        getConnection(1).getRoster().createEntry(
+                            remoteRosterEntry.getUser(),
+                            remoteRosterEntry.getName(),
+                            remoteRosterEntry.getGroupArrayNames());
+                    }
+                } catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                } catch (Exception e) {
+                    fail(e.toString());
+                }
+                try {
+                    chat2.sendMessage("ok");
+                } catch (Exception e) {
+                    fail("An error occured sending ack " + e.getMessage());
+                }
+            }
+        };
+        getConnection(1).addPacketListener(packetListener, packetFilter);
+
+        // Create the message to send with the roster
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("This message contains roster items.");
+        // Create a RosterExchange Package and add it to the message
+        assertTrue("Roster has no entries", getConnection(0).getRoster().getEntryCount() > 0);
+        RosterExchange rosterExchange = new RosterExchange(getConnection(0).getRoster());
+        msg.addExtension(rosterExchange);
+
+        // Send the message that contains the roster
+        try {
+            chat1.sendMessage(msg);
+        } catch (Exception e) {
+            fail("An error occured sending the message with the roster");
+        }
+        // Wait for 10 seconds for a reply
+        msg = chat1.nextMessage(5000);
+        assertNotNull("No reply received", msg);
+        try {
+            Thread.sleep(200);
+        } catch (Exception e) {
+        }
+        assertTrue("Roster2 has no entries", getConnection(1).getRoster().getEntryCount() > 0);
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        try {
+            getConnection(0).getRoster().createEntry(
+                getBareJID(2),
+                "gato5",
+                new String[] { "Friends, Coworker" });
+            getConnection(0).getRoster().createEntry(getBareJID(3), "gato6", null);
+            Thread.sleep(300);
+
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    protected int getMaxConnections() {
+        return 4;
+    }
+
+}
diff --git a/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java b/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java
new file mode 100644
index 000000000..b44e60d4d
--- /dev/null
+++ b/CopyOftrunk/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java
@@ -0,0 +1,259 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright (C) 2002-2003 Jive Software. All rights reserved.
+ * ====================================================================
+ * The Jive Software License (based on Apache Software License, Version 1.1)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ *    if any, must include the following acknowledgment:
+ *       "This product includes software developed by
+ *        Jive Software (http://www.jivesoftware.com)."
+ *    Alternately, this acknowledgment may appear in the software itself,
+ *    if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Smack" and "Jive Software" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please
+ *    contact webmaster@jivesoftware.com.
+ *
+ * 5. Products derived from this software may not be called "Smack",
+ *    nor may "Smack" appear in their name, without prior written
+ *    permission of Jive Software.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL JIVE SOFTWARE OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import java.util.*;
+
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Test the XHTML extension using the low level API
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLExtensionTest extends SmackTestCase {
+
+    private int bodiesSent;
+    private int bodiesReceived;
+
+    /**
+     * Constructor for XHTMLExtensionTest.
+     * @param name
+     */
+    public XHTMLExtensionTest(String name) {
+        super(name);
+    }
+
+    /**
+     * Low level API test.
+     * This is a simple test to use with a XMPP client and check if the client receives the message
+     * 1. User_1 will send a message with formatted text (XHTML) to user_2
+     */
+    public void testSendSimpleXHTMLMessage() {
+        // User1 creates a chat with user2
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("Hey John, this is my new green!!!!");
+        // Create a XHTMLExtension Package and add it to the message
+        XHTMLExtension xhtmlExtension = new XHTMLExtension();
+        xhtmlExtension.addBody(
+            "<body><p style='font-size:large'>Hey John, this is my new <span style='color:green'>green</span><em>!!!!</em></p></body>");
+        msg.addExtension(xhtmlExtension);
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            chat1.sendMessage(msg);
+            Thread.sleep(200);
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+    }
+
+    /**
+    * Low level API test.
+    * 1. User_1 will send a message with XHTML to user_2
+    * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything 
+    *    is fine
+    * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+    *    something is wrong
+    */
+    public void testSendSimpleXHTMLMessageAndDisplayReceivedXHTMLMessage() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a Listener that listens for Messages with the extension 
+        //"http://jabber.org/protocol/xhtml-im"
+        // This listener will listen on the conn2 and answer an ACK if everything is ok
+        PacketFilter packetFilter =
+            new PacketExtensionFilter("html", "http://jabber.org/protocol/xhtml-im");
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message) packet;
+                assertNotNull("Body is null", message.getBody());
+                try {
+                    XHTMLExtension xhtmlExtension =
+                        (XHTMLExtension) message.getExtension(
+                            "html",
+                            "http://jabber.org/protocol/xhtml-im");
+                    assertNotNull(
+                        "Message without extension \"http://jabber.org/protocol/xhtml-im\"",
+                        xhtmlExtension);
+                    assertTrue("Message without XHTML bodies", xhtmlExtension.getBodiesCount() > 0);
+                    for (Iterator it = xhtmlExtension.getBodies(); it.hasNext();) {
+                        String body = (String) it.next();
+                        System.out.println(body);
+                    }
+                }
+                catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+                try {
+                    chat2.sendMessage("ok");
+                }
+                catch (Exception e) {
+                    fail("An error occured sending ack " + e.getMessage());
+                }
+            }
+        };
+        getConnection(1).addPacketListener(packetListener, packetFilter);
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody("Hey John, this is my new green!!!!");
+        // Create a XHTMLExtension Package and add it to the message
+        XHTMLExtension xhtmlExtension = new XHTMLExtension();
+        xhtmlExtension.addBody(
+            "<body><p style='font-size:large'>Hey John, this is my new <span style='color:green'>green</span><em>!!!!</em></p></body>");
+        msg.addExtension(xhtmlExtension);
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            chat1.sendMessage(msg);
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+        // Wait for 2 seconds for a reply
+        msg = chat1.nextMessage(1000);
+        assertNotNull("No reply received", msg);
+    }
+
+    /**
+    * Low level API test. Test a message with two XHTML bodies and several XHTML tags.
+    * 1. User_1 will send a message with XHTML to user_2
+    * 2. User_2 will receive the message and iterate over the XHTML bodies to check if everything 
+    *    is fine
+    * 3. User_1 will wait several seconds for an ACK from user_2, if none is received then 
+    *    something is wrong
+    */
+    public void testSendComplexXHTMLMessageAndDisplayReceivedXHTMLMessage() {
+        // Create a chat for each connection
+        Chat chat1 = getConnection(0).createChat(getBareJID(1));
+        final Chat chat2 = new Chat(getConnection(1), getBareJID(0), chat1.getThreadID());
+
+        // Create a Listener that listens for Messages with the extension 
+        //"http://jabber.org/protocol/xhtml-im"
+        // This listener will listen on the conn2 and answer an ACK if everything is ok
+        PacketFilter packetFilter =
+            new PacketExtensionFilter("html", "http://jabber.org/protocol/xhtml-im");
+        PacketListener packetListener = new PacketListener() {
+            public void processPacket(Packet packet) {
+                int received = 0;
+                Message message = (Message) packet;
+                assertNotNull("Body is null", message.getBody());
+                try {
+                    XHTMLExtension xhtmlExtension =
+                        (XHTMLExtension) message.getExtension(
+                            "html",
+                            "http://jabber.org/protocol/xhtml-im");
+                    assertNotNull(
+                        "Message without extension \"http://jabber.org/protocol/xhtml-im\"",
+                        xhtmlExtension);
+                    assertTrue("Message without XHTML bodies", xhtmlExtension.getBodiesCount() > 0);
+                    for (Iterator it = xhtmlExtension.getBodies(); it.hasNext();) {
+                        received++;
+                        System.out.println((String) it.next());
+                    }
+                    bodiesReceived = received;
+                }
+                catch (ClassCastException e) {
+                    fail("ClassCastException - Most probable cause is that smack providers is misconfigured");
+                }
+            }
+        };
+        getConnection(1).addPacketListener(packetListener, packetFilter);
+
+        // User1 creates a message to send to user2
+        Message msg = chat1.createMessage();
+        msg.setSubject("Any subject you want");
+        msg.setBody(
+            "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds.");
+        // Create an XHTMLExtension and add it to the message
+        XHTMLExtension xhtmlExtension = new XHTMLExtension();
+        xhtmlExtension.addBody(
+            "<body xml:lang=\"es-ES\"><h1>impresionante!</h1><p>Como Emerson dijo una vez:</p><blockquote><p>Una consistencia rid�cula es el espantajo de mentes peque�as.</p></blockquote></body>");
+        xhtmlExtension.addBody(
+            "<body xml:lang=\"en-US\"><h1>awesome!</h1><p>As Emerson once said:</p><blockquote><p>A foolish consistency is the hobgoblin of little minds.</p></blockquote></body>");
+        msg.addExtension(xhtmlExtension);
+
+        // User1 sends the message that contains the XHTML to user2
+        try {
+            bodiesSent = xhtmlExtension.getBodiesCount();
+            bodiesReceived = 0;
+            chat1.sendMessage(msg);
+            Thread.sleep(300);
+        }
+        catch (Exception e) {
+            fail("An error occured sending the message with XHTML");
+        }
+        // Wait half second so that the complete test can run
+        assertEquals(
+            "Number of sent and received XHTMP bodies does not match",
+            bodiesSent,
+            bodiesReceived);
+    }
+
+    protected int getMaxConnections() {
+        return 2;
+    }
+
+}