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 @@
+
+
+
+
+
+ * + * 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:
+ * + *
Emotion | ASCII | Image |
Happy | :) or :-) | happy.gif |
Sad | :( or :-( | sad.gif |
Grin | :D | grin.gif |
Love | :x | love.gif |
Mischief | ;\ | mischief.gif |
Cool | B-) | cool.gif |
Devil | ]:) | devil.gif |
Silly | :p | silly.gif |
Angry | X-( | angry.gif |
Laugh | :^O | laugh.gif |
Wink | ;) or ;-) | wink.gif |
Blush | :8} | blush.gif |
Cry | :_| | cry.gif |
Confused | ?:| | confused.gif |
Shocked | :O | shocked.gif |
Plain | :| | plain.gif |
", preformatStartCount); + string = replaceIgnoreCase(string, "[/pre]", "", preformatEndCount); + int preStartCount = preformatStartCount[0]; + int preEndCount = preformatEndCount[0]; + + while (preStartCount > preEndCount) { + string = string.concat(""); + 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.
+ *
+ * The default set of patterns recognized are ftp://path-of-url
,
+ * http://path-of-url
, https://path-of-url
but can be expanded upon.
+ *
+ * In addition, the following patterns are also recognized.
+ *
+ * [url path-of-url]descriptive text[/url]
and
+ * [url=path-of-url]descriptive text[/url]
.
+ *
+ * The [url]
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("");
+ if (block.getDescription().length() > 0) {
+ filtered.append(block.getDescription());
+ }
+ else {
+ filtered.append(block.getUrl());
+ }
+ filtered.append("");
+ }
+
+ 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.
+ *
+ * The default set of patterns recognized are ftp://path-of-url
,
+ * http://path-of-url
, https://path-of-url
but can be expanded upon.
+ *
+ * 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.
+ *
+ * @author loki der quaeler
+ */
+public class URLTranscoder {
+
+ static protected final String A_HREF_PREFIX = "";
+ static protected final String A_HREF_CLOSING_TAG = "";
+
+
+ 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: http://dict.leo.org/
+ * 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(); }
+%>
+
+
+
+
+
+
|
+
+
|
+
+ +This document provides detailed information for developers that wish to +compile and make changes to the Smack source code. + +
For additional developer resources, please visit: + +http://www.jivesoftware.org/smack/. The Smack build process is based on Ant. Visit the +Ant website +for more information. There is no need to download and install Ant - a version of it is included +in this distribution. +
+This documentation is divided into two sections: +
+ +Getting your machine ready for Smack development requires a few steps. Wherever +possible, instructions are provided for both Unix/Linux and Windows users. +
+ Important! -- 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: +
+
+ 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.
+
+ The JAVA_HOME variable should now be configured correctly.
+
+ export JAVA_HOME=/usr/local/jdk1.3
+
+
+ source .profile
+
+
+
+
+ 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.
+
+ set JAVA_HOME=c:\jdk1.3
+
+
+
+ +
+
+Linux/Unix users only:You must make the ant script
+executable. From the build directory, type:
+
+chmod u+x ant
+
+ |
+Now, invoke the build tool to compile the Smack source code + +
+Windows:
+
+If the build tool is invoked correctly and Smack compiles, you've correctly
+configured your copy of the Smack developer distribution.
+
+ ant
+Unix/Linux:
+ ./ant
+
+
Finished!
+
+
+ The list of build tasks is below. All build commands should be
+ run from the "build" directory of your Smack distribution.
+
+
+
+ For a list of the commands and a brief description from the command line, type
+ ant -projecthelp
. For more complete help, read the documentation below.
+
+
+
+ To execute a build task, type ant [options] targetname
where "targetname" is
+ one of the targets listed below:
+
+
+Each task is documented with a syntax guide and description. Optional paramaters +for each task are enclosed with braces. + + +
Default +
+
+ Description:
+ant
+
+
+
compile +
+
+ Description:
+ant compile
+
+
+
jar +
+
+ Description:
+ant jar
+
+
+
javadoc +
+
+ Description:
+ant javadoc
+
+
+
clean +
+
+ Description:
+
+
+ant clean
+
+
+
+
version: | +1.5.1 | +
released: | +August 12, 2005 | +
+Thank you for downloading Smack! +
+ +Start off by viewing the documentation +that can be found in the "documentation" directory included with this distribution. +
+Further information can be found on the +Smack website. If you need help using or would like to make contributions or +fixes to the code, please visit the +online forum. + +
About the Distribution
+ +The smack.jar file in the main distribution folder is the only binary file +required for embedding XMPP functionality into client applications. The optional +smackx.jar contains the Smack extensions +while smackx-debug.jar contains an enhanced debugger.
+ +If you downloaded the developer release, the full source of the library is included in +the source directory and can be compiled using the build scripts found in the +build directory (please see the README file in the build directory for further details). + +
Changelog and Upgrading
+ +View the changelog for a list of changes since the +last release. + +
License Agreements
+
+ 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. +
+1.5.1 -- August 12, 2005 +
+
+1.5.0 -- March 30, 2005 +
+
+1.4.1 - November 15, 2004 +
+
+1.4.0 - August 10, 2004 +
+
+1.3.0 - March 11, 2004 +
+
+1.2.1 - September 28, 2003 +
+
+1.2.0 - August 29, 2003 +
+
+1.1.1 - June 25, 2003 +
+
+1.1.0 - June 19, 2003 +
+
+1.0.1 - April 30, 2003 +
+
+1.0.0 - April 25, 2003 +
+
+Smack includes two built-in debugging consoles that will let you track all XML traffic between +the client and server. A lite debugger which is part of the smack.jar +and an enhanced debugger contained in smackx-debug.jar. +
+ ++Debugging mode can be enabled in two different ways: +
+ ++ XMPPConnection.DEBUG_ENABLED = true;
+ +
+ java -Dsmack.debugEnabled=true SomeApp +
+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: +
+ ++XMPPConnection.DEBUG_ENABLED = false; +
+ ++Smack uses the following logic to decide the debugger console to use: +
+ ++ java -Dsmack.debuggerClass=my.company.com.MyDebugger SomeApp
+ +
+ +
+ +Allows to exchange structured data between users and applications for common +tasks such as registration and searching using Forms. + +
+JEP related: JEP-4 + ++ +Description
+ +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.
+ +Usage+ +In order to create a Form to fill out use the Form's constructor passing the constant +Form.TYPE_FORM 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 FormField use the FormField's +constructor specifying the variable name of the field as the parameter. Then use setType(String type) +to set the field's type (e.g. FormField.TYPE_HIDDEN, FormField.TYPE_TEXT_SINGLE). Once we have the +Form instance and the FormFields the last step is to send addField(FormField field) +for each field that we want to add to the form.
+ +Once the form to fill out is finished we will want to send it in a message. Send getDataFormToSend() to +the form and add the answer as an extension to the message to send.
+ +Examples
+
+In this example we can see how to create and send a form to fill out:
+
++ +// Create a new form to gather data + 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 to the form + 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 to the form + field = new FormField(); + field.addValue("Section 1: Case description"); + formToSend.addField(field); + // Add a text-single variable to the form + 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 to the form + field = new FormField("description"); + field.setLabel("Enter a description"); + field.setType(FormField.TYPE_TEXT_MULTI); + formToSend.addField(field); + + // Create a chat with "user2@host.com" + 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"); + // Add the form to fill out to the message to send + msg.addExtension(formToSend.getDataFormToSend()); + + // Send the message with the form to fill out + chat.sendMessage(msg); ++
+ +Description
+ +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.
+ +Usage+ +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.
+ +In order to create a new Form to complete based on the original Form just send +createAnswerForm() to the original Form. Once you have a valid form that could be actually +completed all you have to do is send setAnswer(String variable, String value) to the form where variable +is the variable of the FormField 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 setAnswer(String variable, List values) +where values is a List of Strings.
+ +Once the form has been completed we will want to send it back in a message. Send getDataFormToSend() to +the form and add the answer as an extension to the message to send back.
+ +Examples
+
+In this example we can see how to retrieve a form to fill out, complete the form and send it back:
+
++ + + \ 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 @@ + + +// Get the message with the form to fill out + Message msg2 = chat2.nextMessage(); + // Retrieve the form to fill out from the message + Form formToRespond = Form.getFormFrom(msg2); + // Obtain the form to send with the replies + Form completedForm = formToRespond.createAnswerForm(); + // Add the answers to the form + 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"); + // Add the completed form to the message to send back + msg2.addExtension(completedForm.getDataFormToSend()); + // Send the message with the completed form + chat2.sendMessage(msg2); ++
+ +The service discovery extension allows to discover items and information about XMPP +entities. Follow these links to learn how to use this extension. + +
+ +Description
+ +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.
+ +Usage
+
+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 getInstanceFor(connection) to the class ServiceDiscoveryManager where
+connection is your XMPPConnection.
Once you have your ServiceDiscoveryManager you will be able to manage the supported features. To +register a new feature send addFeature(feature) to your ServiceDiscoveryManager +where feature is a String that represents the supported feature. To remove a supported feature send +removeFeature(feature) to your ServiceDiscoveryManager where feature is a +String that represents the feature to remove.
+ +Examples
+
+In this example we can see how to add and remove supported features:
+
++ +// Obtain the ServiceDiscoveryManager associated with my XMPPConnection + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + // Register that a new feature is supported by this XMPP entity + discoManager.addFeature(namespace1); + + // Remove the specified feature from the supported features by this XMPP entity + discoManager.removeFeature(namespace2); ++
+ +Description
+ +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.
+ +Usage
+
+In order to configure the associated nodes within the Smack client you will need to create a
+NodeInformationProvider and register it with the ServiceDiscoveryManager. To get
+your ServiceDiscoveryManager send getInstanceFor(connection) to the class ServiceDiscoveryManager
+where connection is your XMPPConnection.
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 setNodeInformationProvider(String node, NodeInformationProvider listener) +to your ServiceDiscoveryManager where node is the item non-addressable as a JID and +listener is the NodeInformationProvider to register. To unregister a NodeInformationProvider +send removeNodeInformationProvider(String node) to your ServiceDiscoveryManager where +node is the item non-addressable as a JID whose information provider we want to unregister.
+ +Examples
+
+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":
+
++ +// Set the NodeInformationProvider that will provide information about the + // joined rooms whenever a disco request is received + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( + "http://jabber.org/protocol/muc#rooms", + 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(); + } + }); ++
+ +Description
+ +In order to obtain information about a specific item you have to first discover the items available +in an XMPP entity.
+ +Usage+ +
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 discoverItems(entityID) +to your ServiceDiscoveryManager where entityID is the ID of the entity. The message +discoverItems(entityID) will answer an instance of DiscoverItems that contains +the discovered items.
+ +Examples
+
+In this example we can see how to discover the items associated with an online catalog service:
+
++ +// Obtain the ServiceDiscoveryManager associated with my XMPPConnection + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + // Get the items of a given XMPP entity + // This example gets the items associated with online catalog service + DiscoverItems discoItems = discoManager.discoverItems("plays.shakespeare.lit"); + + // Get the discovered items of the queried XMPP entity + Iterator it = discoItems.getItems(); + // Display the items of the remote XMPP entity + 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()); + } ++
+ +Description
+ +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.
+ +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).
+ +Usage+ +
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 discoverInfo(entityID) +to your ServiceDiscoveryManager where entityID is the ID of the entity. The message +discoverInfo(entityID) will answer an instance of DiscoverInfo that contains +the discovered information.
+ +Examples
+
+In this example we can see how to discover the information of a conference room:
+
++ +// Obtain the ServiceDiscoveryManager associated with my XMPPConnection + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + // Get the information of a given XMPP entity + // This example gets the information of a conference room + DiscoverInfo discoInfo = discoManager.discoverInfo("balconyscene@plays.shakespeare.lit"); + + // Get the discovered identities of the remote XMPP entity + Iterator it = discoInfo.getIdentities(); + // Display the identities of the remote XMPP entity + 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()); + } + + // Check if room is password protected + discoInfo.containsFeature("muc_passwordprotected"); ++
+ +Description
+ +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).
+ +Usage+ +
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 DiscoverItems and configure it with the items to publish. Then you will have to +send publishItems(String entityID, DiscoverItems discoverItems) to your ServiceDiscoveryManager +where entityID is the address of the XMPP entity that will persist the items and discoverItems contains the items +to publish.
+ +Examples
+
+In this example we can see how to publish new items:
+
++ + + \ 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 @@ + + + +// Obtain the ServiceDiscoveryManager associated with my XMPPConnection + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + // Create a DiscoverItems with the items to publish + 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); + + // Publish the new items by sending them to the server + discoManager.publishItems("host", itemsToPublish); ++
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.
+ +This manual provides details about each of the "smackx" extensions, including what + it is, how to use it, and some simple example code.
+ +
+ +
Name | JEP # | Description | +
Private Data | +JEP-49 | +Manages private data. | +
XHTML Messages | +JEP-71 | +Allows send and receiving formatted messages using XHTML. | +
Message Events | +JEP-22 | +Requests and responds to message events. | +
Data Forms | +JEP-4 | +Allows to gather data using Forms. | +
Multi User Chat | +JEP-45 | +Allows configuration of, participation in, and administration of individual text-based conference rooms. | +
Roster Item Exchange | +JEP-93 | +Allows roster data to be shared between users. | +
Time Exchange | +JEP-90 | +Allows local time information to be shared between users. | +
Group Chat Invitations | +N/A | +Send invitations to other users to join a group chat room. | +
Service Discovery | +JEP-30 | +Allows to discover services in XMPP entities. | +
+ +The group chat invitation packet extension is used to invite other +users to a group chat room. + +
+ ++JEP related: N/A -- this protocol is outdated now that the Multi-User Chat (MUC) JEP is available +(JEP-45). However, most +existing clients still use this older protocol. Once MUC support becomes more +widespread, this API may be deprecated. + +
+ +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: + +
+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); ++ +The XML generated for the invitation portion of the code above would be: + +
+<x xmlns="jabber:x:conference" jid="room@chat.example.com"/> +
+ +
+ +To listen for group chat invitations, use a PacketExtensionFilter for the +x element name and jabber:x:conference namespace, as in the +following code example: + +
+PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference"); +// Create a packet collector or packet listeners using the filter... ++ + + + \ 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 @@ + + +
+ +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:
For more information on each stage please follow these links:
++Description
+ +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.
+ +Usage+ +The class MessageEventManager 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. +
Use the static method MessageEventManager.addNotificationsRequests(Message message, boolean offline, boolean +delivered, boolean displayed, boolean composing) for requesting event notifications. +
+ +Example+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. +
++ +// Connect to the server and log in + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + // Create a chat with user2 + Chat chat1 = conn1.createChat(user2); + + // Create a message to send + 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 + chat1.sendMessage(msg); ++
+ +Description
+ +You can receive notification requests for the following events: delivered, displayed, composing and offline. You +must listen for these requests and react accordingly.
+ +Usage+ +The general idea is to create a new DefaultMessageEventRequestListener that will listen to the event notifications +requests and react with custom logic. Then you will have to add the listener to the +MessageEventManager that works on +the desired XMPPConnection. +
Note that DefaultMessageEventRequestListener is a default implementation of the +MessageEventRequestListener interface. +The class DefaultMessageEventRequestListener 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 MessageEventRequestListener interface, please remember to send the delivered notification.
++ +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 +DefaultMessageEventRequestListener +to a MessageEventManager that will listen and react to the event notification requested by the other user. +
++ +// Connect to the server and log in the users + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + conn2 = new XMPPConnection(host); + conn2.login(server_user2, pass2); + + // User2 creates a MessageEventManager + MessageEventManager messageEventManager = new MessageEventManager(conn2); + // User2 adds the listener that will react to the event notifications requests + messageEventManager.addMessageEventRequestListener(new DefaultMessageEventRequestListener() { + public void deliveredNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.deliveredNotificationRequested(from, packetID, messageEventManager); + // DefaultMessageEventRequestListener automatically responds that the message was delivered when receives this request + System.out.println("Delivered Notification Requested (" + from + ", " + packetID + ")"); + } + + public void displayedNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.displayedNotificationRequested(from, packetID, messageEventManager); + // Send to the message's sender that the message was displayed + messageEventManager.sendDisplayedNotification(from, packetID); + } + + public void composingNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.composingNotificationRequested(from, packetID, messageEventManager); + // Send to the message's sender that the message's receiver is composing a reply + messageEventManager.sendComposingNotification(from, packetID); + } + + public void offlineNotificationRequested( + String from, + String packetID, + MessageEventManager messageEventManager) { + super.offlineNotificationRequested(from, packetID, messageEventManager); + // The XMPP server should take care of this request. Do nothing. + System.out.println("Offline Notification Requested (" + from + ", " + packetID + ")"); + } + }); + + // User1 creates a chat with user2 + Chat chat1 = conn1.createChat(user2); + + // User1 creates a message to send to user2 + Message msg = chat1.createMessage(); + msg.setSubject("Any subject you want"); + msg.setBody("An interesting body comes here..."); + // User1 adds to the message all the notifications requests (offline, delivered, displayed, + // composing) + MessageEventManager.addNotificationsRequests(msg, true, true, true, true); + + // User1 sends the message that contains the notifications request + chat1.sendMessage(msg); + Thread.sleep(500); + // User2 sends to the message's sender that the message's receiver cancelled composing a reply + messageEventManager.sendCancelledNotification(user1, msg.getPacketID()); ++
+ +Description
+ +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.
+ +Usage+ +The general idea is to create a new MessageEventNotificationListener that will listen to the event notifications +and react with custom logic. Then you will have to add the listener to the MessageEventManager that works on +the desired XMPPConnection. +
+Below you can find an example that logs in a user to the server, adds a MessageEventNotificationListener +to a MessageEventManager that will listen and react to the event notifications, creates a message, adds +the requests for notifications and sends the message. +
++ + + + \ 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 @@ + + +// Connect to the server and log in + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + // Create a MessageEventManager + MessageEventManager messageEventManager = new MessageEventManager(conn1); + // Add the listener that will react to the event notifications + messageEventManager.addMessageEventNotificationListener(new MessageEventNotificationListener() { + public void deliveredNotification(String from, String packetID) { + System.out.println("The message has been delivered (" + from + ", " + packetID + ")"); + } + + public void displayedNotification(String from, String packetID) { + System.out.println("The message has been displayed (" + from + ", " + packetID + ")"); + } + + public void composingNotification(String from, String packetID) { + System.out.println("The message's receiver is composing a reply (" + from + ", " + packetID + ")"); + } + + public void offlineNotification(String from, String packetID) { + System.out.println("The message's receiver is offline (" + from + ", " + packetID + ")"); + } + + public void cancelledNotification(String from, String packetID) { + System.out.println("The message's receiver cancelled composing a reply (" + from + ", " + packetID + ")"); + } + }); + + // Create a chat with user2 + Chat chat1 = conn1.createChat(user2); + + // Create a message to send + 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 + chat1.sendMessage(msg); ++
+ +Allows configuration of, participation in, and administration of individual text-based conference rooms.
+ +
+ +Description
+ +Allowed users may create new rooms. There are two types of rooms that you can create. Instant rooms +which are available for immediate access and are automatically created based on some default +configuration and Reserved rooms which are manually configured by the room creator before +anyone is allowed to enter.
+ +Usage+ +In order to create a room you will need to first create an instance of MultiUserChat. The +room name passed to the constructor will be the name of the room to create. The next step is to send +create(String nickname) to the MultiUserChat instance where nickname is the nickname +to use when joining the room.
+ +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 sendConfigurationForm(Form form) 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.
+ +Examples
+
+In this example we can see how to create an instant room:
+
++ +In this example we can see how to create a reserved room. The form is completed with default values:// Create a MultiUserChat using an XMPPConnection for a room + MultiUserChat muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + + // 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)); ++
++ +// Create a MultiUserChat using an XMPPConnection for a room + MultiUserChat muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + + // Create the room + muc.create("testbot"); + + // Get the the room's configuration form + Form form = muc.getConfigurationForm(); + // 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()); + } + } + // Sets the new owner of the room + List owners = new ArrayList(); + owners.add("johndoe@jabber.org"); + submitForm.setAnswer("muc#roomconfig_roomowners", owners); + // Send the completed form (with default values) to the server to configure the room + muc.sendConfigurationForm(submitForm); ++
+ +Description
+ +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.
+ +Usage+ +In order to join a room you will need to first create an instance of MultiUserChat. The +room name passed to the constructor will be the name of the room to join. The next step is to send +join(...) to the MultiUserChat 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 join(String nickname) where nickname if your nickname in +the room. In case the room requires a password in order to join you could then use +join(String nickname, String password). And finally, the most complete way to join a room is to send +join(String nickname, String password, DiscussionHistory history, long timeout) +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.
+ +Examples
+
+In this example we can see how to join a room with a given nickname:
+
++ +In this example we can see how to join a room with a given nickname and password:// Create a MultiUserChat using an XMPPConnection for a room + MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + + // User2 joins the new room + // The room service will decide the amount of history to send + muc2.join("testbot2"); ++
++ +In this example we can see how to join a room with a given nickname specifying the amount of history +to receive:// Create a MultiUserChat using an XMPPConnection for a room + MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + + // User2 joins the new room using a password + // The room service will decide the amount of history to send + muc2.join("testbot2", "password"); ++
++ +// Create a MultiUserChat using an XMPPConnection for a room + MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + + // User2 joins the new room using a password and specifying + // the amount of history to receive. In this example we are requesting the last 5 messages. + DiscussionHistory history = new DiscussionHistory(); + history.setMaxStanzas(5); + muc2.join("testbot2", "password", history, SmackConfiguration.getPacketReplyTimeout()); ++
+ +Description
+ +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.
+ +Usage+ +In order to invite another user to a room you must be already joined to the room. Once you are +joined just send invite(String participant, String reason) to the MultiUserChat +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.
+ +If potential invitees want to listen for room invitations then the invitee must add an InvitationListener +to the MultiUserChat class. Since the InvitationListener is an interface, +it is necessary to create a class that implements this interface. If an inviter wants to +listen for room invitation rejections, just add an InvitationRejectionListener +to the MultiUserChat. InvitationRejectionListener is also an +interface so you will need to create a class that implements this interface.
+ +Examples
+
+In this example we can see how to invite another user to the room and lister for possible rejections:
+
++ +In this example we can see how to listen for room invitations and decline invitations:// User2 joins the room + MultiUserChat muc2 = new MultiUserChat(conn2, room); + muc2.join("testbot2"); + + // User2 listens for invitation rejections + muc2.addInvitationRejectionListener(new InvitationRejectionListener() { + public void invitationDeclined(String invitee, String reason) { + // Do whatever you need here... + } + }); + + // User2 invites user3 to join to the room + muc2.invite("user3@host.org/Smack", "Meet me in this excellent room"); ++
++ +// User3 listens for MUC invitations + MultiUserChat.addInvitationListener(conn3, new InvitationListener() { + public void invitationReceived(XMPPConnection conn, String room, String inviter, String reason, String password) { + // Reject the invitation + MultiUserChat.decline(conn, room, inviter, "I'm busy right now"); + } + }); ++
+ +Description
+ +A user may want to discover if one of the user's contacts supports the Multi-User Chat protocol.
+ +Usage+ +In order to discover if one of the user's contacts supports MUC just send +isServiceEnabled(XMPPConnection connection, String user) to the MultiUserChat +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.
+ +Examples
+
+In this example we can see how to discover support of MUC:
+
++ +// Discover whether user3@host.org supports MUC or not + boolean supports = MultiUserChat.isServiceEnabled(conn, "user3@host.org/Smack"); ++
+ +Description
+ +A user may also want to query a contact regarding which rooms the contact is in.
+ +Usage+ +In order to get the rooms where a user is in just send +getJoinedRooms(XMPPConnection connection, String user) to the MultiUserChat +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.
+ +Examples
+
+In this example we can see how to get the rooms where a user is in:
+
++ +// Get the rooms where user3@host.org has joined + Iterator joinedRooms = MultiUserChat.getJoinedRooms(conn, "user3@host.org/Smack"); ++
+ +Description
+ +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.
+ +Usage+ +In order to discover information about a room just send getRoomInfo(XMPPConnection connection, String room) +to the MultiUserChat 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.
+ +Examples
+
+In this example we can see how to discover information about a room:
+
++ +// Discover information about the room roomName@conference.myserver + RoomInfo info = MultiUserChat.getRoomInfo(conn, "roomName@conference.myserver"); + System.out.println("Number of occupants:" + info.getOccupantsCount()); + System.out.println("Room Subject:" + info.getSubject()); ++
+ +Description
+ +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.
+ +Usage+ +To create a private chat with another room occupant just send createPrivateChat(String participant) +to the MultiUserChat 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 Chat object that you can use to chat with the other room occupant.
+ +Examples
+
+In this example we can see how to start a private chat with another room occupant:
+
++ +// Start a private chat with another participant + Chat chat = muc2.createPrivateChat("myroom@conference.jabber.org/johndoe"); + chat.sendMessage("Hello there"); ++
+ +Description
+ +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.
+ +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.
+ +Usage+ +In order to modify the room's subject just send changeSubject(String subject) to the +MultiUserChat 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 +SubjectUpdatedListener to the MultiUserChat by sending +addSubjectUpdatedListener(SubjectUpdatedListener listener) to the MultiUserChat. +Since the SubjectUpdatedListener is an interface, it is necessary to create a class +that implements this interface.
+ +Examples
+
+In this example we can see how to change the room's subject and react whenever the room's subject is
+modified:
+
++ +// An occupant wants to be notified every time the room's subject is changed + muc3.addSubjectUpdatedListener(new SubjectUpdatedListener() { + public void subjectUpdated(String subject, String from) { + .... + } + }); + + // A room's owner changes the room's subject + muc2.changeSubject("New Subject"); ++
+ +Description
+ +There are four defined roles that an occupant can have:
++ +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.
+ +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.
+ +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.
+ +Usage+ +In order to grant voice (i.e. make someone a participant) just send the message +grantVoice(String nickname) to MultiUserChat. Use revokeVoice(String nickname) +to revoke the occupant's voice (i.e. make the occupant a visitor).
+ +In order to grant moderator privileges to a participant or visitor just send the message +grantModerator(String nickname) to MultiUserChat. Use revokeModerator(String nickname) +to revoke the moderator privilege from the occupant thus making the occupant a participant.
+ +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 ParticipantStatusListener. But if you are interested +in listening for your own role modification events, use the listener UserStatusListener. Both listeners +should be added to the MultiUserChat by using +addParticipantStatusListener(ParticipantStatusListener listener) or +addUserStatusListener(UserStatusListener listener) 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 DefaultUserStatusListener +and DefaultParticipantStatusListener. Below you will find the sent messages to the listeners whenever +an occupant's role has changed.
+ +These are the triggered events when the role has been upgraded: +
+Old | New | Events |
None | Visitor | -- |
Visitor | Participant | voiceGranted |
Participant | Moderator | moderatorGranted |
None | Participant | voiceGranted |
None | Moderator | voiceGranted + moderatorGranted |
Visitor | Moderator | voiceGranted + moderatorGranted |
+ +These are the triggered events when the role has been downgraded: +
+Old | New | Events |
Moderator | Participant | moderatorRevoked |
Participant | Visitor | voiceRevoked |
Visitor | None | kicked |
Moderator | Visitor | voiceRevoked + moderatorRevoked |
Moderator | None | kicked |
Participant | None | kicked |
+
+In this example we can see how to grant voice to a visitor and listen for the notification events:
+
++ +// User1 creates a room + muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + muc.create("testbot"); + + // User1 (which is the room owner) configures the room as a moderated room + Form form = muc.getConfigurationForm(); + Form answerForm = form.createAnswerForm(); + answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); + muc.sendConfigurationForm(answerForm); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(conn2, "myroom@conference.jabber.org"); + muc2.join("testbot2"); + // User2 will listen for his own "voice" notification events + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void voiceGranted() { + super.voiceGranted(); + ... + } + public void voiceRevoked() { + super.voiceRevoked(); + ... + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(conn3, "myroom@conference.jabber.org"); + muc3.join("testbot3"); + // User3 will lister for other occupants "voice" notification events + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void voiceGranted(String participant) { + super.voiceGranted(participant); + ... + } + + public void voiceRevoked(String participant) { + super.voiceRevoked(participant); + ... + } + }); + + // The room's owner grants voice to user2 + muc.grantVoice("testbot2"); ++
+ +Description
+ +There are five defined affiliations that a user can have in relation to a room:
++ +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.
+ +If a user without a defined affiliation enters a room, the user's affiliation is defined as "none"; +however, this affiliation does not persist across visits.
+ +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 "none" or "member") before such actions could be performed +on them.
+ +The member affiliation provides a way for a room owner or admin to specify a "whitelist" 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).
+ +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.
+ +Usage+ +In order to grant membership to a room, administrator privileges or owner priveliges just send +grantMembership(String jid), grantAdmin(String jid) or grantOwnership(String jid) +to MultiUserChat respectively. Use revokeMembership(String jid), revokeAdmin(String jid) +or revokeOwnership(String jid) to revoke the membership to a room, administrator privileges or +owner priveliges respectively.
+ +In order to ban a user from the room just send the message banUser(String jid, String reason) to +MultiUserChat.
+ +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 ParticipantStatusListener. But if you are interested +in listening for your own affiliation modification events, use the listener UserStatusListener. Both listeners +should be added to the MultiUserChat by using +addParticipantStatusListener(ParticipantStatusListener listener) or +addUserStatusListener(UserStatusListener listener) 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 DefaultUserStatusListener +and DefaultParticipantStatusListener. Below you will find the sent messages to the listeners whenever +a user's affiliation has changed.
+ +These are the triggered events when the affiliation has been upgraded: +
+Old | New | Events |
None | Member | membershipGranted |
Member | Admin | membershipRevoked + adminGranted |
Admin | Owner | adminRevoked + ownershipGranted |
None | Admin | adminGranted |
None | Owner | ownershipGranted |
Member | Owner | membershipRevoked + ownershipGranted |
+ +These are the triggered events when the affiliation has been downgraded: +
+Old | New | Events |
Owner | Admin | ownershipRevoked + adminGranted |
Admin | Member | adminRevoked + membershipGranted |
Member | None | membershipRevoked |
Owner | Member | ownershipRevoked + membershipGranted |
Owner | None | ownershipRevoked |
Admin | None | adminRevoked |
Anyone | Outcast | banned |
+
+In this example we can see how to grant admin privileges to a user and listen for the notification events:
+
++ + + \ 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 @@ + + +// User1 creates a room + muc = new MultiUserChat(conn1, "myroom@conference.jabber.org"); + muc.create("testbot"); + + // User1 (which is the room owner) configures the room as a moderated room + Form form = muc.getConfigurationForm(); + Form answerForm = form.createAnswerForm(); + answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); + muc.sendConfigurationForm(answerForm); + + // User2 joins the new room (as a visitor) + MultiUserChat muc2 = new MultiUserChat(conn2, "myroom@conference.jabber.org"); + muc2.join("testbot2"); + // User2 will listen for his own admin privileges + muc2.addUserStatusListener(new DefaultUserStatusListener() { + public void membershipRevoked() { + super.membershipRevoked(); + ... + } + public void adminGranted() { + super.adminGranted(); + ... + } + }); + + // User3 joins the new room (as a visitor) + MultiUserChat muc3 = new MultiUserChat(conn3, "myroom@conference.jabber.org"); + muc3.join("testbot3"); + // User3 will lister for other users admin privileges + muc3.addParticipantStatusListener(new DefaultParticipantStatusListener() { + public void membershipRevoked(String participant) { + super.membershipRevoked(participant); + ... + } + public void adminGranted(String participant) { + super.adminGranted(participant); + ... + } + }); + + // The room's owner grants admin privileges to user2 + muc.grantAdmin("user2@jabber.org"); ++
+ +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: + +
+<color xmlns="http://example.com/xmpp/color"> + <favorite>blue</blue> + <leastFavorite>puce</leastFavorite> +</color> +
+ +JEP related: JEP-49 + +
+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. +
Follow these links to learn how to send and receive roster items:
+ +JEP related: JEP-93 + ++ +Description
+ +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.
+ +Usage+ +Create an instance of RosterExchangeManager and use the #send(Roster, String) +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.
+ +Example+ +In this example we can see how user1 sends his roster to user2. +
++ +// Connect to the server and log in + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + // Create a new roster exchange manager on conn1 + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + // Send user1's roster to user2 + rosterExchangeManager.send(conn1.getRoster(), user2); ++
+ +Description
+ +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.
+ +Usage+ +Create an instance of RosterExchangeManager and use the #send(RosterGroup, String) +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.
+ +Example+ +In this example we can see how user1 sends his roster groups to user2. +
++ +// Connect to the server and log in + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + // Create a new roster exchange manager on conn1 + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + // Send user1's RosterGroups to user2 + for (Iterator it = conn1.getRoster().getGroups(); it.hasNext(); ) + rosterExchangeManager.send((RosterGroup)it.next(), user2); ++
+ +Description
+ +Sometimes you may need to send a single roster entry to another XMPP client. Smack also lets you send +items at this granularity level.
+ +Usage+ +Create an instance of RosterExchangeManager and use the #send(RosterEntry, String) +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.
+ +Example+ +In this example we can see how user1 sends a roster entry to user2. +
++ +// Connect to the server and log in + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + + // Create a new roster exchange manager on conn1 + RosterExchangeManager rosterExchangeManager = new RosterExchangeManager(conn1); + // Send a roster entry (any) to user2 + rosterExchangeManager1.send((RosterEntry)conn1.getRoster().getEntries().next(), user2); ++
+ +Description
+ +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.
+ +Usage+ +
+ +In this example we can see how user1 sends a roster entry to user2 and user2 adds the received +entries to his roster. +
++ + + \ 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 @@ + + +// Connect to the server and log in the users + conn1 = new XMPPConnection(host); + conn1.login(server_user1, pass1); + conn2 = new XMPPConnection(host); + conn2.login(server_user2, pass2); + final Roster user2_roster = conn2.getRoster(); + + // Create a RosterExchangeManager that will help user2 to listen and accept + the entries received + RosterExchangeManager rosterExchangeManager2 = new RosterExchangeManager(conn2); + // Create a RosterExchangeListener that will iterate over the received roster entries + RosterExchangeListener rosterExchangeListener = new RosterExchangeListener() { + public void entriesReceived(String from, Iterator remoteRosterEntries) { + while (remoteRosterEntries.hasNext()) { + try { + // Get the received entry + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) remoteRosterEntries.next(); + // Display the remote entry on the console + System.out.println(remoteRosterEntry); + // Add the entry to the user2's roster + user2_roster.createEntry( + remoteRosterEntry.getUser(), + remoteRosterEntry.getName(), + remoteRosterEntry.getGroupArrayNames()); + } + catch (XMPPException e) { + e.printStackTrace(); + } + } + } + }; + // Add the RosterExchangeListener to the RosterExchangeManager that user2 is using + rosterExchangeManager2.addRosterListener(rosterExchangeListener); + + // Create a RosterExchangeManager that will help user1 to send his roster + RosterExchangeManager rosterExchangeManager1 = new RosterExchangeManager(conn1); + // Send user1's roster to user2 + rosterExchangeManager1.send(conn1.getRoster(), user2); ++
+ +Supports a protocol that XMPP clients use to exchange their respective local +times and time zones.
+ +JEP related: JEP-90 + +
+ +
+
+Private Data
+XHTML Messages
+Message Events
+Data Forms
+Multi User Chat
+Roster Item Exchange
+Time Exchange
+Group Chat Invitations
+Service Discovery
+
+ +Provides the ability to send and receive formatted messages using XHTML. + +
Follow these links to learn how to compose, send, receive and discover support for +XHTML messages:
++ +Description
+ +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.
+ ++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.
+ +Usage+ +Create an instance of XHTMLText 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 #append(String textToAppend).
+ +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 +#addBody(Message message, String body) to the XHTMLManager class where message +is the message that will receive the XHTML body and body is the string to add as an XHTML body to +the message.
+ +Example
+
+In this example we can see how to compose the following XHTML message:
+<body><p style='font-size:large'>Hey John, this is my new <span
+ style='color:green'>green</span><em>!!!!</em></p></body>
+
++ +// Create a message to send + Message msg = chat.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()); + ++
+ +Description
+ +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.
+ +Usage+ +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 #send(Message) of Chat or you can use +the message #send(Packet) of XMPPConnection.
+ +Example+ +In this example we can see how to send a message with XHTML content as part of a chat. +
++ +// Create a message to send + Message msg = chat.createMessage(); + // Obtain the XHTML text to send from somewhere + String xhtmlBody = getXHTMLTextToSend(); + + // Add the XHTML text to the message + XHTMLManager.addBody(msg, xhtmlBody); + + // Send the message that contains the XHTML + chat.sendMessage(msg); ++
+ +Description
+ +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.
+ +Usage+ +To get the XHTML bodies of a given message just send the message #getBodies(Message) + to the class XHTMLManager. The answer of this message will be an + Iterator with the different XHTML bodies of the message or null if none.
+ +Example+ +In this example we can see how to create a PacketListener that obtains the XHTML bodies of any received message. +
++ +// Create a listener for the chat and display any XHTML content + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + // Obtain the XHTML bodies of the message + Iterator it = XHTMLManager.getBodies(message); + if (it != null) { + // Display the bodies on the console + while (it.hasNext()) { + String body = (String) it.next(); + System.out.println(body); + } + } + }; + chat.addMessageListener(packetListener); + ++
+ +Description
+ +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.
+ +Usage+ +In order to discover if a remote user supports XHTML messages send #isServiceEnabled(XMPPConnection +connection, String userID) to the class XHTMLManager 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.
+ +Example+ +In this example we can see how to discover if a remote user supports XHTML Messages. +
++ + + 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 @@ + + +Message msg = chat.createMessage(); + // Include a normal body in the message + msg.setBody(getTextToSend()); + // Check if the other user supports XHTML messages + if (XHTMLManager.isServiceEnabled(connection, chat.getParticipant())) { + // Obtain the XHTML text to send from somewhere + String xhtmlBody = getXHTMLTextToSend(); + + // Include an XHTML body in the message + XHTMLManager.addBody(msg, xhtmlBody); + } + + // Send the message + chat.sendMessage(msg); ++
+This document will introduce you to the Smack API and provide an overview of +important classes and concepts. +
+ ++Requirements +
+ +The only requirement for Smack is JDK 1.2 or later +1. +An XML parser is embedded in the smack.jar file and no other third party +libraries are required.+ +1 JDK 1.2 and 1.3 users that wish to use SSL connections must have the +JSSE library in their classpath. + +
+Establishing a Connection +
+ +The XMPPConnection 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:+ +
+// Create a connection to the jabber.org server. +XMPPConnection conn1 = new XMPPConnection("jabber.org"); + +// Create a connection to the jabber.org server on a specific port. +XMPPConnection conn2 = new XMPPConnection("jabber.org", 5222); + +// Create an SSL connection to jabber.org. +XMPPConnection connection = new SSLXMPPConnection("jabber.org"); +
Once you've created a connection, you should login using a username and password +with the XMPPConnection.login(String username, String password) method. +Once you've logged in, you can being chatting with other users by creating +new Chat or GroupChat objects. + +
+Working with the Roster +
+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.+ +Retrieve the roster using the XMPPConnection.getRoster() 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. + +
+Reading and Writing Packets +
+ +Each message to the XMPP server from a client is called a packet and is +sent as XML. The org.jivesoftware.smack.packet package contains +classes that encapsulate the three different basic packet types allowed by +XMPP (message, presence, and IQ). Classes such as Chat and GroupChat +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":+ +
+// Create a new presence. Pass in false to indicate we're unavailable. +Presence presence = new Presence(Presence.Type.UNAVAILABLE); +presence.setStatus("Gone fishing"); +// Send the packet (assume we have a XMPPConnection instance called "con"). +con.sendPacket(presence); +
+ +Smack provides two ways to read incoming packets: PacketListener, and +PacketCollector. Both use PacketFilter 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. + + +
+ + + \ 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 @@ + + +
+![]() | +Smack Documentation + |
+Contents: +
+ ++
+Sending messages back and forth is at the core of instant messaging. Two classes +aid in sending and receiving messages: +
+Chat +
+ +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:+ +
+// Assume we've created an XMPPConnection name "connection". +Chat newChat = connection.createChat("jsmith@jivesoftware.com"); +newChat.sendMessage("Howdy!"); +
+ +The Chat.sendMessage(String) 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 +Chat.createMessage() and Chat.sendMessage(Message) methods, as in the +following code snippet:
+ +
+// Assume we've created an XMPPConnection name "connection". +Chat newChat = connection.createChat("jsmith@jivesoftware.com"); +Message newMessage = newChat.createMessage(); +newMessage.setBody("Howdy!"); +message.setProperty("favoriteColor", "red"); +newChat.sendMessage(newMessage); +
+ +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.
+ +
+// Assume we've created an XMPPConnection name "connection". +Chat newChat = connection.createChat("jsmith@jivesoftware.com"); +newMessage.setBody("Hi, I'm an annoying parrot-bot! Type something back to me."); +while (true) { + // Wait for the next message the user types to us. + Message message = newChat.nextMessage(); + // Send back the same text the other user sent us. + newChat.sendMessage(message.getBody()); +} +
+ +The code above uses the Chat.nextMessage() 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. + +
+GroupChat +
+ +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.+ +
+// Assume we've created an XMPPConnection name "connection". +GroupChat newGroupChat = connection.createGroupChat("test@jivesoftware.com"); +// Join the group chat using the nickname "jsmith". +newGroupChat.join("jsmith"); +// Send a message to all the other people in the chat room. +newGroupChat.sendMessage("Howdy!"); +
+ +In general, sending and receiving messages in a group chat works very similarly to +the Chat class. Method are also provided to get the list of the other +users in the room.
+
+
+
+
+ +Smack is a library for communicating with XMPP servers to perform +instant messaging and chat.
+ +
+Smack Key Advantages +
+ ++XMPPConnection connection = new XMPPConnection("jabber.org"); +connection.login("mtucker", "password"); +connection.createChat("jsmith@jivesoftware.com").sendMessage("Howdy!"); +
+About XMPP +
+ +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 (http://www.jabber.org). + ++How To Use This Documentation +
+ +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. + ++ +Smack provides a flexible framework for processing incoming packets using two constructs: +
+ +The org.jivesoftware.smack.filter.PacketFilter interface determines which +specific packets will be delivered to a PacketCollector or PacketListener. +Many pre-defined filters can be found in the org.jivesoftware.smack.filter package. + +
+The following code snippet demonstrates registering both a packet collector and a packet +listener:
+ +
+// Create a packet filter to listen for new messages from a particular +// user. We use an AndFilter to combine two other filters. +PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class), + new FromContainsFilter("mary@jivesoftware.com")); +// Assume we've created an XMPPConnection name "connection". + +// First, register a packet collector using the filter we created. +PacketCollector myCollector = connection.createPacketCollector(filter); +// Normally, you'd do something with the collector, like wait for new packets. + +// Next, create a packet listener. We use an anonymous inner class for brevity. +PacketListener myListener = new PacketListener() { + public void processPacket(Packet packet) { + // Do something with the incoming packet here. + } + }; +// Register the listener. +connection.addPacketListener(myListener, filter); +
+ +
+Standard Packet Filters +
+ +A rich set of packet filters are included with Smack, or you can create your own filters by coding +to the PacketFilter interface. The default set of filters includes: ++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). +
+ ++Using the API +
+ ++All major objects have property support, such as Message objects. The following code +demonstrates how to set properties: +
+ ++Message message = chat.createMessage(); +// Add a Color object as a property. +message.setProperty("favoriteColor", new Color(0, 0, 255)); +// Add an int as a property. +message.setProperty("favoriteNumber", 4); +chat.sendMessage(message); +
+Getting those same properties would use the following code: +
+ ++Message message = chat.nextMessage(); +// Get a Color object property. +Color favoriteColor = (Color)message.getProperty("favoriteColor"); +// 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. +int favoriteNumber = ((Integer)message.getProperty("favoriteNumber")).intValue(); +
+Objects as Properties +
+ ++Using objects as property values is a very powerful and easy way to exchange data. However, +you should keep the following in mind: +
+ ++XML Format +
+ ++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): +
+ ++<!-- All properties are in a x block. --> +<properties xmlns="http://www.jivesoftware.com/xmlns/xmpp/properties"> + <!-- First, a property named "prop1" that's an integer. --> + <property> + <name>prop1</name> + <value type="integer">123</value> + <property> + <!-- Next, a Java object that's been serialized and then converted + from binary data to base-64 encoded text. --> + <property> + <name>blah2</name> + <value type="java-object">adf612fna9nab</value> + <property> +</properties> +
+The currently supported types are: integer, long, float, +double, boolean, string, and java-object. +
+ + + + + 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 @@ + + ++ +The Smack provider architecture is a system for plugging in +custom XML parsing of packet extensions and IQ packets. The +standard Smack Extensions +are built using the provider architecture. Two types of +providers exist:
IQProvider
+ +By default, Smack only knows how to process IQ packets with sub-packets that +are in a few namespaces such as:+ <?xml version="1.0"?> + <smackProviders> + <iqProvider> + <elementName>query</elementName> + <namespace>jabber:iq:time</namespace> + <className>org.jivesoftware.smack.packet.Time</className> + </iqProvider> + </smackProviders>+ +Each IQ provider is associated with an element name and a namespace. In the +example above, the element name is query and the namespace is +abber:iq:time. 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: + +
+<iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> + <query xmlns='jabber:iq:time'> + <utc>20020910T17:58:35</utc> + <tz>MDT</tz> + <display>Tue Sep 10 12:58:35 2002</display> + </query> +</iq>+ +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.
+ +
PacketExtensionProvider
+ +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: + ++<?xml version="1.0"?> +<smackProviders> + <extensionProvider> + <elementName>x</elementName> + <namespace>jabber:iq:event</namespace> + <className>org.jivesoftware.smack.packet.MessageEvent</className> + </extensionProvider> +</smackProviders>+ +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.
+
+
+
+
+
+ +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.
+ +A Roster instance is obtained using the XMPPConnection.getRoster() +method, but only after successfully logging into a server. + +
Roster Entries
+ ++Every user in a roster is represented by a RosterEntry, which consists of:
+Roster roster = con.getRoster(); +for (Iterator i=roster.getEntries(); i.hasNext(); ) { + System.out.println(i.next()); +} ++ +Methods also exist to get individual entries, the list of unfiled entries, or to get one or +all roster groups. + +
Presence
+ +Every entry in the roster has presence associated with it. The +Roster.getPresence(String user) method will return a Presence object with +the user's presence or null if the user is not online or you are not +subscribed to the user's presence. Note: typically, presence +subscription is always tied to the user being on the roster, but this is not +true in all cases.
+ +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.
+ +Listening for Roster and Presence Changes
+ +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.
+ +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.
+
+
+
+
+final Roster roster = con.getRoster(); +roster.addRosterListener(new RosterListener() { + public void rosterModified() { + // Ignore event for this example. + } + + public void presenceChanged(String user) { + // If the presence is unavailable then "null" will be printed, + // which is fine for this example. + System.out.println("Presence changed: " + roster.getPresence(user)); + } +}); ++ +
Adding Entries to the Roster
+ +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.
+ +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:Add contact to roster | +![]() |
+ |||||
+ | ||||||
|
+
Smack Demo Application | +|||||
+ <% if (error != null) { %>
+ <%= error %> + <%} else {%> + + <%}%> + |
+ |||||
|
+
<%= StringUtils.parseName(conn.getUser())%>'s roster | +
|
+ |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
+
+ * + * 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 null 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 null 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 null 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 null 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.
+ * + * 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 + * false 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 + * thread field of XMPP messages. This method may return null + * 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: + * + *
+ * Message message = chat.createMessage(); + * message.setBody(messageText); + * chat.sendMessage(message); + *+ * + * @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 null 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 + * null 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 timeout has elapased. + * If the timeout elapses without a result, null will be returned. + * + * @param timeout the maximum amount of time to wait for the next message. + * @return the next message, or null 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.
+ * + * 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 null if not + * currently joined. + * + * @return the nickname currently being used. + */ + public String getNickname() { + return nickname; + } + + /** + * Returns the number of participants in the group chat.
+ * + * 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 null 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 + * null 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 timeout has elapased. + * If the timeout elapses without a result, null will be returned. + * + * @param timeout the maximum amount of time to wait for the next message. + * @return the next message, or null 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.
+ * + * 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 null, 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 null if no packets are currently in the + * result queue. + * + * @return the next packet result, or null 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 timeout has elapased. If the + * timeout elapses without a result, null 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.
+ * + * 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.
+ *
+ * @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
+ *
+ * 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}.
+ *
+ * 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.
+ *
+ * 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 null 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
+ *
+ * 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 null 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 null 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 null 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 null 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 null 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
+ *
+ * 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:
+ *
+ *
+ * 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. jivesoftware.com.
+ * @param port the port on the server that should be used; e.g. 5222.
+ * @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
+ * null 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 sendPresence
+ * 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 true 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
+ * null.
+ *
+ * @return the user's roster, or null 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 jdoe@jivesoftware.com or
+ * jdoe@jivesoftware.com/work.
+ *
+ * @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 room@chat.example.com.
+ *
+ * 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 null 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 null 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.
+ *
+ *
+ * Every implementation of this interface must 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 @@
+ setType(IQ.Type.GET);
+ */
+ public Authentication() {
+ setType(IQ.Type.SET);
+ }
+
+ /**
+ * Returns the username, or null 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 null 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 null 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 null 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("
+ *
+ * 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:
+ *
+ *
+ *
+ * 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:
+ *
+ * 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:
+ *
+ *
+ *
+ *
+ * A number of attributes are optional:
+ *
+ *
+ * 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 null 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 null.
+ *
+ * @param mode the mode.
+ */
+ public void setMode(Mode mode) {
+ this.mode = mode;
+ }
+
+ public String toXML() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("
+ *
+ *
+ *
+ * By default, Smack only knows how to process IQ packets with sub-packets that
+ * are in a few namespaces such as:
+ *
+ * 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:
+ *
+ * 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:
+ *
+ * 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 @@
+
+ * 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.
+ *
+ * Method by Santeri Paavolainen, Helsinki Finland 1996
+ *
+ * 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
+ *
+ * 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:
+ *
+ *
+ * Possible form types are:
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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
+ *
+ * Possible form types are:
+ *
+ *
+ * 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.
+ *
+ * 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:
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * 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.
+ *
+ * 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 OfflineMessageHeader 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 OfflineMessageHeader 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 Messages 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 Messages 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 Messages 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 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 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:
+ *
+ *
+ *
+ * Warning: this is an non-standard protocol documented by
+ * JEP-49. 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:
+ *
+ * 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 null, 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.
+ *
+ * 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("
+ *
+ * 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 null 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("
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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("");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that an anchor section ends.
+ *
+ */
+ public void appendCloseAnchorTag() {
+ text.append("");
+ }
+
+ /**
+ * 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(" ");
+ 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("
+ *
+ * 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(
+ "
+ *
+ * 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.
+ *
+ * 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 @@
+
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You can use some or all of these variable to control the amount of history to receive:
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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)}
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * To control the amount of history to receive while joining a room you will need to provide
+ * a configured DiscussionHistory object.
+ *
+ * A password is required when joining password protected rooms. If the room does
+ * not require a password there is no need to provide one.
+ *
+ * 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 null 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
+ * null 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 null if no registration is possible. Some rooms may restrict the
+ * privilege to register members and allow only room admins to add new members.
+ *
+ * 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 null 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 null 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.
+ *
+ * To be notified every time the room's subject change you should add a listener
+ * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}
+ *
+ * To change the room's subject use {@link #changeSubject(String)}.
+ *
+ * @return the room's subject or null 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 null 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 null 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.
+ *
+ * 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 null if the user
+ * is not in the room.
+ *
+ * @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 null 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 null 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.
+ *
+ * @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 null 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
+ *
+ * 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.
+ *
+ * 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 @@
+
+ *
+ * Possible form types are:
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * For more information see JEP-91.
+ *
+ * @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 null 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 null 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 null if
+ * this information is not available.
+ *
+ * @return a natural-language description of the reason for the delay or null.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Sets a natural-language description of the reason for the delay or null if
+ * this information is not available.
+ *
+ * @param reason a natural-language description of the reason for the delay or null.
+ */
+ 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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("
+ *
+ * Refer to Jabber::Registrar
+ * in order to get the official registry of values for the category and type
+ * 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 Jabber::Registrar
+ *
+ * @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 Jabber::Registrar
+ *
+ * @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 Jabber::Registrar
+ *
+ * @param type the identity's type.
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String toXML() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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("
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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("
+ *
+ * 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("
+ *
+ * There are four message events currently defined in this namespace:
+ *
+ *
+ * Request to be notified when displayed:
+ *
+ *
+ * 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 <message/> element an <x/> child scoped by the 'jabber:x:roster' namespace. This
+ * <x/> element may contain one or more <item/> children (one for each roster item to be sent).
+ *
+ * Each <item/> element may possess the following attributes:
+ *
+ * <jid/> -- The id of the contact being sent. This attribute is required.
+ *
+ * Each <item/> element may also contain one or more <group/> 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:
+ *
+ *
+ * Warning: this is an non-standard protocol documented by
+ * JEP-90. 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 null 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("
+ *
+ * You should refer to the
+ * JEP-54 documentation.
+ *
+ * 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.
+ *
+ * Usage:
+ *
+ *
+ *
+ * An example to discover the version of the server:
+ *
+ *
+ * @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("
+ *
+ * The following link summarizes the requirements of XHTML IM:
+ * Valid tags.
+ *
+ * Warning: this is an non-standard protocol documented by
+ * JEP-71. 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:
+ *
+The {@link org.jivesoftware.smack.XMPPConnection} class is the main entry point for the API.
+
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 @@
+
+
+
+ *
+ * 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
+ *
+ * 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.
+ *
+ * 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 this 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 Hey John, this is my new green!!!! Hey John, this is my new green!!!! Como Emerson dijo una vez: Una consistencia ridícula es el espantajo de mentes pequeñas. As Emerson once said: A foolish consistency is the hobgoblin of little minds.
+ *
+ *
+ * @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}.
+ *
+ *
+ * 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.
+ * // 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?");
+ *
+ *
+ * @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:
+ *
+ *
+ * Debugging can be enabled by setting this field to true, or by setting the Java system
+ * property smack.debugEnabled 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. jivesoftware.com.
+ * @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. jivesoftware.com.
+ * @param port the port on the server that should be used; e.g. 5222.
+ * @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.
+ * // 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);
+ *
+ *
+ * @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 packet 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:
+ *
+ *
+ *
+ * @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 packetType.
+ *
+ * @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 @@
+Allows {@link org.jivesoftware.smack.PacketCollector} and {@link org.jivesoftware.smack.PacketListener} instances to filter for packets with particular attributes.
\ 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 @@
+Core classes of the Smack API.
\ 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":
+ *
+ *
+ * <foo xmlns="http://bar.com">
+ * <color>blue</color>
+ * <food>pizza</food>
+ * </foo>
+ *
+ * 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".
+ *
+ *
+ *
+ * @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("
+ *
+ */
+ 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:
+ *
+ *
+ *
+ *
+ * For each message type, different message fields are typically used as follows:
+ *
+ *
+ *
+ * @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, null will be returned.
+ *
+ * @return the thread id of the message, or null 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 type
+ * Field Normal Chat Group Chat Headline XMPPError
+ * subject SHOULD SHOULD NOT SHOULD NOT SHOULD NOT SHOULD NOT
+ * thread OPTIONAL SHOULD OPTIONAL OPTIONAL SHOULD NOT
+ * body SHOULD SHOULD SHOULD SHOULD SHOULD NOT
+ * error MUST NOT MUST NOT MUST NOT MUST NOT MUST
+ *
+ *
+ *
+ *
+ * @author Matt Tucker
+ */
+public class Registration extends IQ {
+
+ private String instructions = null;
+ private Map attributes = null;
+
+ /**
+ * Returns the registration instructions, or null 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 null 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("
+ *
+ *
+ * @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("
+ * Code Description
+ * 302 Redirect
+ * 400 Bad Request
+ * 401 Unauthorized
+ * 402 Payment Required
+ * 403 Forbidden
+ * 404 Not Found
+ * 405 Not Allowed
+ * 406 Not Acceptable
+ * 407 Registration Required
+ * 408 Request Timeout
+ * 409 Conflict
+ * 500 Internal Server XMPPError
+ * 501 Not Implemented
+ * 502 Remote Server Error
+ * 503 Service Unavailable
+ * 504 Remote Server Timeout
+ *
+ *
+ * IQProvider
+ *
+ *
+ * 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:
+ *
+ *
+ * <?xml version="1.0"?>
+ * <smackProviders>
+ * <iqProvider>
+ * <elementName>query</elementName>
+ * <namespace>jabber:iq:time</namespace>
+ * <className>org.jivesoftware.smack.packet.Time</className>
+ * </iqProvider>
+ * </smackProviders>
+ *
+ * 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:
+ *
+ * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
+ * <query xmlns='jabber:iq:time'>
+ * <utc>20020910T17:58:35</utc>
+ * <tz>MDT</tz>
+ * <display>Tue Sep 10 12:58:35 2002</display>
+ * </query>
+ * </iq>
+ *
+ * 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.
+ * <?xml version="1.0"?>
+ * <smackProviders>
+ * <extensionProvider>
+ * <elementName>x</elementName>
+ * <namespace>jabber:iq:event</namespace>
+ * <className>org.jivesoftware.smack.packet.MessageEvent</className>
+ * </extensionProvider>
+ * </smackProviders>
+ *
+ * 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
+ * <message to='romeo@montague.net' id='message_1'>
+ * <body>Art thou not Romeo, and a Montague?</body>
+ * <x xmlns='jabber:x:event'>
+ * <composing/>
+ * </x>
+ * </message>
+ *
+ *
+ * (c) Santeri Paavolainen, Helsinki Finland 1996
+ * 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.
+ *
+ *
+ * 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.
+ *
+ *
+ * @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.
+ *
+ *
+ * @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
+ *
+ *
+ * @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.
+ *
+ *
+ * @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("
+ * 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);
+ *
+ *
+ * To listen for group chat invitations, use a PacketExtensionFilter for the
+ * x element name and jabber:x:conference namespace, as in the
+ * following code example:
+ *
+ *
+ * PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference");
+ * // Create a packet collector or packet listeners using the filter...
+ *
+ *
+ * Note: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available
+ * (JEP-45). 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 room@service,
+ * where service is the name of groupchat server, such as
+ * chat.example.com.
+ *
+ * @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 room@service, where service is
+ * the name of groupchat server, such as chat.example.com.
+ *
+ * @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:event'>
+ * <offline/>
+ * <delivered/>
+ * <composing/>
+ * </x>
+ *
+ *
+ * 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.
+ * <color xmlns="http://example.com/xmpp/color">
+ * <favorite>blue</blue>
+ * <leastFavorite>puce</leastFavorite>
+ * </color>
+ *
+ *
+ * {@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.
+ * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
+ * <query xmlns='jabber:iq:private'>
+ * <prefs xmlns='http://www.xmppclient.com/prefs'>
+ * <value1>ABC</value1>
+ * <value2>XYZ</value2>
+ * </prefs>
+ * </query>
+ * </iq>
+ *
+ *
+ *
+ *
+ * @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:
+ *
+ *
+ *
+ * @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: Jabber::Registrar.
+ *
+ * @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: Jabber::Registrar.
+ *
+ * @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 ");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that a blockquote section ends.
+ *
+ */
+ public void appendCloseBlockQuoteTag() {
+ text.append("
");
+ }
+
+ /**
+ * 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("");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that a body section ends.
+ *
+ */
+ private String closeBodyTag() {
+ return "";
+ }
+
+ /**
+ * Appends a tag that inserts a single carriage return.
+ *
+ */
+ public void appendBrTag() {
+ text.append("
");
+ }
+
+ /**
+ * Appends a tag that indicates a reference to work, such as a book, report or web site.
+ *
+ */
+ public void appendOpenCiteTag() {
+ text.append("");
+ }
+
+ /**
+ * Appends a tag that indicates text that is the code for a program.
+ *
+ */
+ public void appendOpenCodeTag() {
+ text.append("");
+ }
+
+ /**
+ * Appends a tag that indicates end of text that is the code for a program.
+ *
+ */
+ public void appendCloseCodeTag() {
+ text.append("
");
+ }
+
+ /**
+ * Appends a tag that indicates emphasis.
+ *
+ */
+ public void appendOpenEmTag() {
+ text.append("");
+ }
+
+ /**
+ * Appends a tag that indicates end of emphasis.
+ *
+ */
+ public void appendCloseEmTag() {
+ text.append("");
+ }
+
+ /**
+ * 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("");
+ 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("
");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that an ordered list section ends.
+ *
+ */
+ public void appendCloseOrderedListTag() {
+ text.append("
");
+ }
+
+ /**
+ * 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("");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that an unordered list section ends.
+ *
+ */
+ public void appendCloseUnorderedListTag() {
+ text.append("
");
+ }
+
+ /**
+ * 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("");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that an inlined quote section ends.
+ *
+ */
+ public void appendCloseInlinedQuoteTag() {
+ text.append("
");
+ }
+
+ /**
+ * 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("");
+ text.append(sb.toString());
+ }
+
+ /**
+ * Appends a tag that indicates that a span section ends.
+ *
+ */
+ public void appendCloseSpanTag() {
+ text.append("");
+ }
+
+ /**
+ * Appends a tag that indicates text which should be more forceful than surrounding text.
+ *
+ */
+ public void appendOpenStrongTag() {
+ text.append("");
+ }
+
+ /**
+ * Appends a tag that indicates that a strong section ends.
+ *
+ */
+ public void appendCloseStrongTag() {
+ text.append("");
+ }
+
+ /**
+ * 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.
+ *
+ *
+ * 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 MultiUserChat
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.Affiliate
with the room owners.
+ *
+ * @return a collection of Affiliate
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 Affiliate
with the room administrators.
+ *
+ * @return a collection of Affiliate
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 Affiliate
with the room members.
+ *
+ * @return a collection of Affiliate
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 Affiliate
with the room outcasts.
+ *
+ * @return a collection of Affiliate
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 Affiliate
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 Affiliate
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 Affiliate
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 Affiliate
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 Occupant
with the room moderators.
+ *
+ * @return a collection of Occupant
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 Occupant
with the room participants.
+ *
+ * @return a collection of Occupant
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 Occupant
that have the specified room role.
+ *
+ * @param role the role of the occupant in the room.
+ * @return a collection of Occupant
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 null 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
+ * null 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 timeout has elapased.
+ * If the timeout elapses without a result, null will be returned.
+ *
+ * @param timeout the maximum amount of time to wait for the next message.
+ * @return the next message, or null 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 null 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 UserStatusListeners
added to this
+ * MultiUserChat
will be fired. On the other hand, if the occupant that changed
+ * his role is not yours then the ParticipantStatusListeners
added to this
+ * MultiUserChat
will be fired. The following table shows the events that will
+ * be fired depending on the previous and new role of the occupant.
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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
+ *
+ *
+ * Old New Events
+ * None Visitor --
+ * Visitor Participant voiceGranted
+ *
+ * Participant Moderator moderatorGranted
+ * None Participant voiceGranted
+ * None Moderator voiceGranted + moderatorGranted
+ *
+ * Visitor Moderator voiceGranted + moderatorGranted
+ * Moderator Participant moderatorRevoked
+ * Participant Visitor voiceRevoked
+ *
+ * Visitor None kicked
+ * Moderator Visitor voiceRevoked + moderatorRevoked
+ * Moderator None kicked
+ * Participant None kicked UserStatusListeners
added to this MultiUserChat
will be fired.
+ * On the other hand, if the occupant that changed his affiliation is not yours then the
+ * ParticipantStatusListeners
added to this MultiUserChat
will be
+ * fired. The following table shows the events that will be fired depending on the previous
+ * and new affiliation of the occupant.
+ *
+ *
+ *
+ *
+ *
+ *
+ * @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.
+ *
+ * Old New Events
+ * None Member membershipGranted
+ * Member Admin membershipRevoked + adminGranted
+ *
+ * Admin Owner adminRevoked + ownershipGranted
+ * None Admin adminGranted
+ * None Owner ownershipGranted
+ *
+ * Member Owner membershipRevoked + ownershipGranted
+ * Owner Admin ownershipRevoked + adminGranted
+ * Admin Member adminRevoked + membershipGranted
+ *
+ * Member None membershipRevoked
+ * Owner Member ownershipRevoked + membershipGranted
+ * Owner None ownershipRevoked
+ * Admin None adminRevoked
+ * Anyone Outcast banned
+ *
+ *
+ * @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
+ * <foo xmlns="http://bar.com">
+ * <color>blue</color>
+ * <food>pizza</food>
+ * </foo>
+ *
+ * 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.
+ *
+ *
+ * @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:
+ * 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.
+ * 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.
+ * 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.
+ * 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.
+ * <message
+ * to='romeo@montague.net/orchard'
+ * from='juliet@capulet.com/balcony'
+ * id='message22'>
+ * <x xmlns='jabber:x:event'>
+ * <displayed/>
+ * </x>
+ * </message>
+ *
+ *
+ * Notification of displayed:
+ *
+ * <message
+ * from='romeo@montague.net/orchard'
+ * to='juliet@capulet.com/balcony'>
+ * <x xmlns='jabber:x:event'>
+ * <displayed/>
+ * <id>message22</id>
+ * </x>
+ * </message>
+ *
+ *
+ */
+ 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("
+ * <name/> -- A natural-language nickname for the contact. This attribute is optional.
+ * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
+ * <subject>Any subject you want</subject>
+ * <body>This message contains roster items.</body>
+ * <x xmlns="jabber:x:roster">
+ * <item jid="gato1@gato.home"/>
+ * <item jid="gato2@gato.home"/>
+ * </x>
+ * </message>
+ *
+ *
+ */
+ 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:
+ *
+ *
+ * // 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...
+ * }
+ *
+ * // 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
+ *
+ *
+ * @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.
+ * // 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...
+ * }
+ * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
+ * <subject>Any subject you want</subject>
+ * <body>This message contains something interesting.</body>
+ * <html xmlns="http://jabber.org/protocol/xhtml-im">
+ * <body><p style='font-size:large'>This message contains something <em>interesting</em>.</p></body>
+ * </html>
+ * </message>
+ *
+ *
+ */
+ 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 @@
+XML packets that are part of the XMPP extension protocols.
\ 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 must 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
+ * must 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 @@
+Provides pluggable parsing logic for Smack extensions.
\ 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 @@
+API specification for Smack, an Open Source XMPP client library.
+impresionante!
");
+ xhtmlExtension.addBody(
+ "awesome!
");
+ 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;
+ }
+
+}