aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/packet
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/packet')
-rwxr-xr-xsrc/org/jivesoftware/smackx/packet/AdHocCommandData.java279
-rw-r--r--src/org/jivesoftware/smackx/packet/AttentionExtension.java100
-rw-r--r--src/org/jivesoftware/smackx/packet/ChatStateExtension.java73
-rw-r--r--src/org/jivesoftware/smackx/packet/DataForm.java312
-rw-r--r--src/org/jivesoftware/smackx/packet/DefaultPrivateData.java137
-rw-r--r--src/org/jivesoftware/smackx/packet/DelayInfo.java105
-rw-r--r--src/org/jivesoftware/smackx/packet/DelayInformation.java149
-rw-r--r--src/org/jivesoftware/smackx/packet/DiscoverInfo.java507
-rw-r--r--src/org/jivesoftware/smackx/packet/DiscoverItems.java253
-rw-r--r--src/org/jivesoftware/smackx/packet/Header.java59
-rw-r--r--src/org/jivesoftware/smackx/packet/HeadersExtension.java69
-rw-r--r--src/org/jivesoftware/smackx/packet/LastActivity.java164
-rw-r--r--src/org/jivesoftware/smackx/packet/MUCAdmin.java237
-rw-r--r--src/org/jivesoftware/smackx/packet/MUCInitialPresence.java223
-rw-r--r--src/org/jivesoftware/smackx/packet/MUCOwner.java342
-rw-r--r--src/org/jivesoftware/smackx/packet/MUCUser.java627
-rw-r--r--src/org/jivesoftware/smackx/packet/MessageEvent.java335
-rw-r--r--src/org/jivesoftware/smackx/packet/MultipleAddresses.java205
-rw-r--r--src/org/jivesoftware/smackx/packet/Nick.java112
-rw-r--r--src/org/jivesoftware/smackx/packet/OfflineMessageInfo.java128
-rw-r--r--src/org/jivesoftware/smackx/packet/OfflineMessageRequest.java237
-rw-r--r--src/org/jivesoftware/smackx/packet/PEPEvent.java105
-rw-r--r--src/org/jivesoftware/smackx/packet/PEPItem.java92
-rw-r--r--src/org/jivesoftware/smackx/packet/PEPPubSub.java95
-rw-r--r--src/org/jivesoftware/smackx/packet/PrivateData.java52
-rw-r--r--src/org/jivesoftware/smackx/packet/RosterExchange.java181
-rw-r--r--src/org/jivesoftware/smackx/packet/SharedGroupsInfo.java92
-rw-r--r--src/org/jivesoftware/smackx/packet/StreamInitiation.java419
-rw-r--r--src/org/jivesoftware/smackx/packet/Time.java198
-rw-r--r--src/org/jivesoftware/smackx/packet/VCard.java883
-rw-r--r--src/org/jivesoftware/smackx/packet/Version.java132
-rw-r--r--src/org/jivesoftware/smackx/packet/XHTMLExtension.java126
-rw-r--r--src/org/jivesoftware/smackx/packet/package.html1
33 files changed, 7029 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/packet/AdHocCommandData.java b/src/org/jivesoftware/smackx/packet/AdHocCommandData.java
new file mode 100755
index 0000000..bceffcd
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/AdHocCommandData.java
@@ -0,0 +1,279 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2005-2008 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.commands.AdHocCommand;
+import org.jivesoftware.smackx.commands.AdHocCommand.Action;
+import org.jivesoftware.smackx.commands.AdHocCommand.SpecificErrorCondition;
+import org.jivesoftware.smackx.commands.AdHocCommandNote;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents the state and the request of the execution of an adhoc command.
+ *
+ * @author Gabriel Guardincerri
+ */
+public class AdHocCommandData extends IQ {
+
+ /* JID of the command host */
+ private String id;
+
+ /* Command name */
+ private String name;
+
+ /* Command identifier */
+ private String node;
+
+ /* Unique ID of the execution */
+ private String sessionID;
+
+ private List<AdHocCommandNote> notes = new ArrayList<AdHocCommandNote>();
+
+ private DataForm form;
+
+ /* Action request to be executed */
+ private AdHocCommand.Action action;
+
+ /* Current execution status */
+ private AdHocCommand.Status status;
+
+ private ArrayList<AdHocCommand.Action> actions = new ArrayList<AdHocCommand.Action>();
+
+ private AdHocCommand.Action executeAction;
+
+ private String lang;
+
+ public AdHocCommandData() {
+ }
+
+ @Override
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<command xmlns=\"http://jabber.org/protocol/commands\"");
+ buf.append(" node=\"").append(node).append("\"");
+ if (sessionID != null) {
+ if (!sessionID.equals("")) {
+ buf.append(" sessionid=\"").append(sessionID).append("\"");
+ }
+ }
+ if (status != null) {
+ buf.append(" status=\"").append(status).append("\"");
+ }
+ if (action != null) {
+ buf.append(" action=\"").append(action).append("\"");
+ }
+
+ if (lang != null) {
+ if (!lang.equals("")) {
+ buf.append(" lang=\"").append(lang).append("\"");
+ }
+ }
+ buf.append(">");
+
+ if (getType() == Type.RESULT) {
+ buf.append("<actions");
+
+ if (executeAction != null) {
+ buf.append(" execute=\"").append(executeAction).append("\"");
+ }
+ if (actions.size() == 0) {
+ buf.append("/>");
+ } else {
+ buf.append(">");
+
+ for (AdHocCommand.Action action : actions) {
+ buf.append("<").append(action).append("/>");
+ }
+ buf.append("</actions>");
+ }
+ }
+
+ if (form != null) {
+ buf.append(form.toXML());
+ }
+
+ for (AdHocCommandNote note : notes) {
+ buf.append("<note type=\"").append(note.getType().toString()).append("\">");
+ buf.append(note.getValue());
+ buf.append("</note>");
+ }
+
+ // TODO ERRORS
+// if (getError() != null) {
+// buf.append(getError().toXML());
+// }
+
+ buf.append("</command>");
+ return buf.toString();
+ }
+
+ /**
+ * Returns the JID of the command host.
+ *
+ * @return the JID of the command host.
+ */
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Returns the human name of the command
+ *
+ * @return the name of the command.
+ */
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the identifier of the command
+ *
+ * @return the node.
+ */
+ public String getNode() {
+ return node;
+ }
+
+ public void setNode(String node) {
+ this.node = node;
+ }
+
+ /**
+ * Returns the list of notes that the command has.
+ *
+ * @return the notes.
+ */
+ public List<AdHocCommandNote> getNotes() {
+ return notes;
+ }
+
+ public void addNote(AdHocCommandNote note) {
+ this.notes.add(note);
+ }
+
+ public void remveNote(AdHocCommandNote note) {
+ this.notes.remove(note);
+ }
+
+ /**
+ * Returns the form of the command.
+ *
+ * @return the data form associated with the command.
+ */
+ public DataForm getForm() {
+ return form;
+ }
+
+ public void setForm(DataForm form) {
+ this.form = form;
+ }
+
+ /**
+ * Returns the action to execute. The action is set only on a request.
+ *
+ * @return the action to execute.
+ */
+ public AdHocCommand.Action getAction() {
+ return action;
+ }
+
+ public void setAction(AdHocCommand.Action action) {
+ this.action = action;
+ }
+
+ /**
+ * Returns the status of the execution.
+ *
+ * @return the status.
+ */
+ public AdHocCommand.Status getStatus() {
+ return status;
+ }
+
+ public void setStatus(AdHocCommand.Status status) {
+ this.status = status;
+ }
+
+ public List<Action> getActions() {
+ return actions;
+ }
+
+ public void addAction(Action action) {
+ actions.add(action);
+ }
+
+ public void setExecuteAction(Action executeAction) {
+ this.executeAction = executeAction;
+ }
+
+ public Action getExecuteAction() {
+ return executeAction;
+ }
+
+ public void setSessionID(String sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ public String getSessionID() {
+ return sessionID;
+ }
+
+ public static class SpecificError implements PacketExtension {
+
+ public static final String namespace = "http://jabber.org/protocol/commands";
+
+ public SpecificErrorCondition condition;
+
+ public SpecificError(SpecificErrorCondition condition) {
+ this.condition = condition;
+ }
+
+ public String getElementName() {
+ return condition.toString();
+ }
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public SpecificErrorCondition getCondition() {
+ return condition;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName());
+ buf.append(" xmlns=\"").append(getNamespace()).append("\"/>");
+ return buf.toString();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/packet/AttentionExtension.java b/src/org/jivesoftware/smackx/packet/AttentionExtension.java
new file mode 100644
index 0000000..d86fa41
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/AttentionExtension.java
@@ -0,0 +1,100 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2010 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A PacketExtension that implements XEP-0224: Attention
+ *
+ * This extension is expected to be added to message stanzas of type 'headline.'
+ * Please refer to the XEP for more implementation guidelines.
+ *
+ * @author Guus der Kinderen, guus.der.kinderen@gmail.com
+ * @see <a
+ * href="http://xmpp.org/extensions/xep-0224.html">XEP-0224:&nbsp;Attention</a>
+ */
+public class AttentionExtension implements PacketExtension {
+
+ /**
+ * The XML element name of an 'attention' extension.
+ */
+ public static final String ELEMENT_NAME = "attention";
+
+ /**
+ * The namespace that qualifies the XML element of an 'attention' extension.
+ */
+ public static final String NAMESPACE = "urn:xmpp:attention:0";
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#getElementName()
+ */
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace()
+ */
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#toXML()
+ */
+ public String toXML() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("<").append(getElementName()).append(" xmlns=\"").append(
+ getNamespace()).append("\"/>");
+ return sb.toString();
+ }
+
+ /**
+ * A {@link PacketExtensionProvider} for the {@link AttentionExtension}. As
+ * Attention elements have no state/information other than the element name
+ * and namespace, this implementation simply returns new instances of
+ * {@link AttentionExtension}.
+ *
+ * @author Guus der Kinderen, guus.der.kinderen@gmail.com
+s */
+ public static class Provider implements PacketExtensionProvider {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.jivesoftware.smack.provider.PacketExtensionProvider#parseExtension
+ * (org.xmlpull.v1.XmlPullParser)
+ */
+ public PacketExtension parseExtension(XmlPullParser arg0)
+ throws Exception {
+ return new AttentionExtension();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/ChatStateExtension.java b/src/org/jivesoftware/smackx/packet/ChatStateExtension.java
new file mode 100644
index 0000000..ecc6acc
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/ChatStateExtension.java
@@ -0,0 +1,73 @@
+/**
+ * $RCSfile$
+ * $Revision: 2407 $
+ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $
+ *
+ * Copyright 2003-2007 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.smackx.ChatState;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Represents a chat state which is an extension to message packets which is used to indicate
+ * the current status of a chat participant.
+ *
+ * @author Alexander Wenckus
+ * @see org.jivesoftware.smackx.ChatState
+ */
+public class ChatStateExtension implements PacketExtension {
+
+ private ChatState state;
+
+ /**
+ * Default constructor. The argument provided is the state that the extension will represent.
+ *
+ * @param state the state that the extension represents.
+ */
+ public ChatStateExtension(ChatState state) {
+ this.state = state;
+ }
+
+ public String getElementName() {
+ return state.name();
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/chatstates";
+ }
+
+ public String toXML() {
+ return "<" + getElementName() + " xmlns=\"" + getNamespace() + "\" />";
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ ChatState state;
+ try {
+ state = ChatState.valueOf(parser.getName());
+ }
+ catch (Exception ex) {
+ state = ChatState.active;
+ }
+ return new ChatStateExtension(state);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/DataForm.java b/src/org/jivesoftware/smackx/packet/DataForm.java
new file mode 100644
index 0000000..4d12892
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DataForm.java
@@ -0,0 +1,312 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.FormField;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a form that could be use for gathering data as well as for reporting data
+ * returned from a search.
+ *
+ * @author Gaston Dombiak
+ */
+public class DataForm implements PacketExtension {
+
+ private String type;
+ private String title;
+ private List<String> instructions = new ArrayList<String>();
+ private ReportedData reportedData;
+ private final List<Item> items = new ArrayList<Item>();
+ private final List<FormField> fields = new ArrayList<FormField>();
+
+ public DataForm(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the meaning of the data within the context. The data could be part of a form
+ * to fill out, a form submission or data results.<p>
+ *
+ * Possible form types are:
+ * <ul>
+ * <li>form -> This packet contains a form to fill out. Display it to the user (if your
+ * program can).</li>
+ * <li>submit -> The form is filled out, and this is the data that is being returned from
+ * the form.</li>
+ * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
+ * <li>result -> Data results being returned from a search, or some other query.</li>
+ * </ul>
+ *
+ * @return the form's type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the description of the data. It is similar to the title on a web page or an X
+ * window. You can put a <title/> on either a form to fill out, or a set of data results.
+ *
+ * @return description of the data.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns an Iterator for the list of instructions that explain how to fill out the form and
+ * what the form is about. The dataform could include multiple instructions since each
+ * instruction could not contain newlines characters. Join the instructions together in order
+ * to show them to the user.
+ *
+ * @return an Iterator for the list of instructions that explain how to fill out the form.
+ */
+ public Iterator<String> getInstructions() {
+ synchronized (instructions) {
+ return Collections.unmodifiableList(new ArrayList<String>(instructions)).iterator();
+ }
+ }
+
+ /**
+ * Returns the fields that will be returned from a search.
+ *
+ * @return fields that will be returned from a search.
+ */
+ public ReportedData getReportedData() {
+ return reportedData;
+ }
+
+ /**
+ * Returns an Iterator for the items returned from a search.
+ *
+ * @return an Iterator for the items returned from a search.
+ */
+ public Iterator<Item> getItems() {
+ synchronized (items) {
+ return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator();
+ }
+ }
+
+ /**
+ * Returns an Iterator for the fields that are part of the form.
+ *
+ * @return an Iterator for the fields that are part of the form.
+ */
+ public Iterator<FormField> getFields() {
+ synchronized (fields) {
+ return Collections.unmodifiableList(new ArrayList<FormField>(fields)).iterator();
+ }
+ }
+
+ public String getElementName() {
+ return Form.ELEMENT;
+ }
+
+ public String getNamespace() {
+ return Form.NAMESPACE;
+ }
+
+ /**
+ * Sets the description of the data. It is similar to the title on a web page or an X window.
+ * You can put a <title/> on either a form to fill out, or a set of data results.
+ *
+ * @param title description of the data.
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ /**
+ * Sets the list of instructions that explain how to fill out the form and what the form is
+ * about. The dataform could include multiple instructions since each instruction could not
+ * contain newlines characters.
+ *
+ * @param instructions list of instructions that explain how to fill out the form.
+ */
+ public void setInstructions(List<String> instructions) {
+ this.instructions = instructions;
+ }
+
+ /**
+ * Sets the fields that will be returned from a search.
+ *
+ * @param reportedData the fields that will be returned from a search.
+ */
+ public void setReportedData(ReportedData reportedData) {
+ this.reportedData = reportedData;
+ }
+
+ /**
+ * Adds a new field as part of the form.
+ *
+ * @param field the field to add to the form.
+ */
+ public void addField(FormField field) {
+ synchronized (fields) {
+ fields.add(field);
+ }
+ }
+
+ /**
+ * Adds a new instruction to the list of instructions that explain how to fill out the form
+ * and what the form is about. The dataform could include multiple instructions since each
+ * instruction could not contain newlines characters.
+ *
+ * @param instruction the new instruction that explain how to fill out the form.
+ */
+ public void addInstruction(String instruction) {
+ synchronized (instructions) {
+ instructions.add(instruction);
+ }
+ }
+
+ /**
+ * Adds a new item returned from a search.
+ *
+ * @param item the item returned from a search.
+ */
+ public void addItem(Item item) {
+ synchronized (items) {
+ items.add(item);
+ }
+ }
+
+ /**
+ * Returns true if this DataForm has at least one FORM_TYPE field which is
+ * hidden. This method is used for sanity checks.
+ *
+ * @return
+ */
+ public boolean hasHiddenFormTypeField() {
+ boolean found = false;
+ for (FormField f : fields) {
+ if (f.getVariable().equals("FORM_TYPE") && f.getType() != null && f.getType().equals("hidden"))
+ found = true;
+ }
+ return found;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\" type=\"" + getType() +"\">");
+ if (getTitle() != null) {
+ buf.append("<title>").append(getTitle()).append("</title>");
+ }
+ for (Iterator<String> it=getInstructions(); it.hasNext();) {
+ buf.append("<instructions>").append(it.next()).append("</instructions>");
+ }
+ // Append the list of fields returned from a search
+ if (getReportedData() != null) {
+ buf.append(getReportedData().toXML());
+ }
+ // Loop through all the items returned from a search and append them to the string buffer
+ for (Iterator<Item> i = getItems(); i.hasNext();) {
+ Item item = i.next();
+ buf.append(item.toXML());
+ }
+ // Loop through all the form fields and append them to the string buffer
+ for (Iterator<FormField> i = getFields(); i.hasNext();) {
+ FormField field = i.next();
+ buf.append(field.toXML());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ /**
+ *
+ * Represents the fields that will be returned from a search. This information is useful when
+ * you try to use the jabber:iq:search namespace to return dynamic form information.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class ReportedData {
+ private List<FormField> fields = new ArrayList<FormField>();
+
+ public ReportedData(List<FormField> fields) {
+ this.fields = fields;
+ }
+
+ /**
+ * Returns the fields returned from a search.
+ *
+ * @return the fields returned from a search.
+ */
+ public Iterator<FormField> getFields() {
+ return Collections.unmodifiableList(new ArrayList<FormField>(fields)).iterator();
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<reported>");
+ // Loop through all the form items and append them to the string buffer
+ for (Iterator<FormField> i = getFields(); i.hasNext();) {
+ FormField field = i.next();
+ buf.append(field.toXML());
+ }
+ buf.append("</reported>");
+ return buf.toString();
+ }
+ }
+
+ /**
+ *
+ * Represents items of reported data.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Item {
+ private List<FormField> fields = new ArrayList<FormField>();
+
+ public Item(List<FormField> fields) {
+ this.fields = fields;
+ }
+
+ /**
+ * Returns the fields that define the data that goes with the item.
+ *
+ * @return the fields that define the data that goes with the item.
+ */
+ public Iterator<FormField> getFields() {
+ return Collections.unmodifiableList(new ArrayList<FormField>(fields)).iterator();
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item>");
+ // Loop through all the form items and append them to the string buffer
+ for (Iterator<FormField> i = getFields(); i.hasNext();) {
+ FormField field = i.next();
+ buf.append(field.toXML());
+ }
+ buf.append("</item>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/DefaultPrivateData.java b/src/org/jivesoftware/smackx/packet/DefaultPrivateData.java
new file mode 100644
index 0000000..b58fc6c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DefaultPrivateData.java
@@ -0,0 +1,137 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Default implementation of the PrivateData interface. Unless a PrivateDataProvider
+ * is registered with the PrivateDataManager class, instances of this class will be
+ * returned when getting private data.<p>
+ *
+ * This class provides a very simple representation of an XML sub-document. Each element
+ * is a key in a Map with its CDATA being the value. For example, given the following
+ * XML sub-document:
+ *
+ * <pre>
+ * &lt;foo xmlns="http://bar.com"&gt;
+ * &lt;color&gt;blue&lt;/color&gt;
+ * &lt;food&gt;pizza&lt;/food&gt;
+ * &lt;/foo&gt;</pre>
+ *
+ * In this case, getValue("color") would return "blue", and getValue("food") would
+ * return "pizza". This parsing mechanism mechanism is very simplistic and will not work
+ * as desired in all cases (for example, if some of the elements have attributes. In those
+ * cases, a custom {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used.
+ *
+ * @author Matt Tucker
+ */
+public class DefaultPrivateData implements PrivateData {
+
+ private String elementName;
+ private String namespace;
+ private Map<String, String> 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() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">");
+ for (Iterator<String> i=getNames(); i.hasNext(); ) {
+ String name = 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<String> getNames() {
+ if (map == null) {
+ return Collections.<String>emptyList().iterator();
+ }
+ return Collections.unmodifiableSet(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<String,String>();
+ }
+ map.put(name, value);
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/packet/DelayInfo.java b/src/org/jivesoftware/smackx/packet/DelayInfo.java
new file mode 100644
index 0000000..f404971
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DelayInfo.java
@@ -0,0 +1,105 @@
+/**
+ * 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.Date;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * A decorator for the {@link DelayInformation} class to transparently support
+ * both the new <b>Delay Delivery</b> specification <a href="http://xmpp.org/extensions/xep-0203.html">XEP-0203</a> and
+ * the old one <a href="http://xmpp.org/extensions/xep-0091.html">XEP-0091</a>.
+ *
+ * Existing code can be backward compatible.
+ *
+ * @author Robin Collier
+ */
+public class DelayInfo extends DelayInformation
+{
+
+ DelayInformation wrappedInfo;
+
+ /**
+ * Creates a new instance with given delay information.
+ * @param delay the delay information
+ */
+ public DelayInfo(DelayInformation delay)
+ {
+ super(delay.getStamp());
+ wrappedInfo = delay;
+ }
+
+ @Override
+ public String getFrom()
+ {
+ return wrappedInfo.getFrom();
+ }
+
+ @Override
+ public String getReason()
+ {
+ return wrappedInfo.getReason();
+ }
+
+ @Override
+ public Date getStamp()
+ {
+ return wrappedInfo.getStamp();
+ }
+
+ @Override
+ public void setFrom(String from)
+ {
+ wrappedInfo.setFrom(from);
+ }
+
+ @Override
+ public void setReason(String reason)
+ {
+ wrappedInfo.setReason(reason);
+ }
+
+ @Override
+ public String getElementName()
+ {
+ return "delay";
+ }
+
+ @Override
+ public String getNamespace()
+ {
+ return "urn:xmpp:delay";
+ }
+
+ @Override
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\"");
+ buf.append(" stamp=\"");
+ buf.append(StringUtils.formatXEP0082Date(getStamp()));
+ buf.append("\"");
+ if (getFrom() != null && getFrom().length() > 0) {
+ buf.append(" from=\"").append(getFrom()).append("\"");
+ }
+ buf.append(">");
+ if (getReason() != null && getReason().length() > 0) {
+ buf.append(getReason());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/DelayInformation.java b/src/org/jivesoftware/smackx/packet/DelayInformation.java
new file mode 100644
index 0000000..b9ab485
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DelayInformation.java
@@ -0,0 +1,149 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.DateFormat;
+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 delay.<p>
+ *
+ * For more information see <a href="http://xmpp.org/extensions/xep-0091.html">XEP-0091</a>
+ * and <a href="http://xmpp.org/extensions/xep-0203.html">XEP-0203</a>.
+ *
+ * @author Gaston Dombiak
+ */
+public class DelayInformation implements PacketExtension {
+
+ /**
+ * Date format according to the obsolete XEP-0091 specification.
+ * XEP-0091 recommends to use this old format for date-time instead of
+ * the one specified in XEP-0082.
+ * <p>
+ * Date formats are not synchronized. Since multiple threads access the format concurrently,
+ * it must be synchronized externally.
+ */
+ public static final DateFormat XEP_0091_UTC_FORMAT = new SimpleDateFormat(
+ "yyyyMMdd'T'HH:mm:ss");
+ static {
+ XEP_0091_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ private Date stamp;
+ private String from;
+ private String reason;
+
+ /**
+ * Creates a new instance with the specified timestamp.
+ * @param stamp the timestamp
+ */
+ public DelayInformation(Date stamp) {
+ super();
+ this.stamp = stamp;
+ }
+
+ /**
+ * Returns the JID of the entity that originally sent the packet or that delayed the
+ * delivery of the packet or <tt>null</tt> if this information is not available.
+ *
+ * @return the JID of the entity that originally sent the packet or that delayed the
+ * delivery of the packet.
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * Sets the JID of the entity that originally sent the packet or that delayed the
+ * delivery of the packet or <tt>null</tt> if this information is not available.
+ *
+ * @param from the JID of the entity that originally sent the packet.
+ */
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * Returns the timestamp when the packet was originally sent. The returned Date is
+ * be understood as UTC.
+ *
+ * @return the timestamp when the packet was originally sent.
+ */
+ public Date getStamp() {
+ return stamp;
+ }
+
+ /**
+ * Returns a natural-language description of the reason for the delay or <tt>null</tt> if
+ * this information is not available.
+ *
+ * @return a natural-language description of the reason for the delay or <tt>null</tt>.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Sets a natural-language description of the reason for the delay or <tt>null</tt> if
+ * this information is not available.
+ *
+ * @param reason a natural-language description of the reason for the delay or <tt>null</tt>.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ public String getElementName() {
+ return "x";
+ }
+
+ public String getNamespace() {
+ return "jabber:x:delay";
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\"");
+ buf.append(" stamp=\"");
+ synchronized (XEP_0091_UTC_FORMAT) {
+ buf.append(XEP_0091_UTC_FORMAT.format(stamp));
+ }
+ buf.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/src/org/jivesoftware/smackx/packet/DiscoverInfo.java b/src/org/jivesoftware/smackx/packet/DiscoverInfo.java
new file mode 100644
index 0000000..ba873a9
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DiscoverInfo.java
@@ -0,0 +1,507 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information
+ * to/from other XMPP entities.<p>
+ *
+ * The received information may contain one or more identities of the requested XMPP entity, and
+ * a list of supported features by the requested XMPP entity.
+ *
+ * @author Gaston Dombiak
+ */
+public class DiscoverInfo extends IQ {
+
+ public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";
+
+ private final List<Feature> features = new CopyOnWriteArrayList<Feature>();
+ private final List<Identity> identities = new CopyOnWriteArrayList<Identity>();
+ private String node;
+
+ public DiscoverInfo() {
+ super();
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param d
+ */
+ public DiscoverInfo(DiscoverInfo d) {
+ super(d);
+
+ // Set node
+ setNode(d.getNode());
+
+ // Copy features
+ synchronized (d.features) {
+ for (Feature f : d.features) {
+ addFeature(f);
+ }
+ }
+
+ // Copy identities
+ synchronized (d.identities) {
+ for (Identity i : d.identities) {
+ addIdentity(i);
+ }
+ }
+ }
+
+ /**
+ * Adds a new feature to the discovered information.
+ *
+ * @param feature the discovered feature
+ */
+ public void addFeature(String feature) {
+ addFeature(new Feature(feature));
+ }
+
+ /**
+ * Adds a collection of features to the packet. Does noting if featuresToAdd is null.
+ *
+ * @param featuresToAdd
+ */
+ public void addFeatures(Collection<String> featuresToAdd) {
+ if (featuresToAdd == null) return;
+ for (String feature : featuresToAdd) {
+ addFeature(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
+ */
+ public Iterator<Feature> getFeatures() {
+ synchronized (features) {
+ return Collections.unmodifiableList(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);
+ }
+ }
+
+ /**
+ * Adds identities to the DiscoverInfo stanza
+ *
+ * @param identitiesToAdd
+ */
+ public void addIdentities(Collection<Identity> identitiesToAdd) {
+ if (identitiesToAdd == null) return;
+ synchronized (identities) {
+ identities.addAll(identitiesToAdd);
+ }
+ }
+
+ /**
+ * Returns the discovered identities of an XMPP entity.
+ *
+ * @return an Iterator on the discoveted identities
+ */
+ public Iterator<Identity> getIdentities() {
+ synchronized (identities) {
+ return Collections.unmodifiableList(identities).iterator();
+ }
+ }
+
+ /**
+ * Returns the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @return the node attribute that supplements the 'jid' attribute
+ */
+ public String getNode() {
+ return node;
+ }
+
+ /**
+ * Sets the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @param node the node attribute that supplements the 'jid' attribute
+ */
+ public void setNode(String node) {
+ this.node = node;
+ }
+
+ /**
+ * Returns true if the specified feature is part of the discovered information.
+ *
+ * @param feature the feature to check
+ * @return true if the requestes feature has been discovered
+ */
+ public boolean containsFeature(String feature) {
+ for (Iterator<Feature> it = getFeatures(); it.hasNext();) {
+ if (feature.equals(it.next().getVar()))
+ return true;
+ }
+ return false;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"" + NAMESPACE + "\"");
+ if (getNode() != null) {
+ buf.append(" node=\"");
+ buf.append(StringUtils.escapeForXML(getNode()));
+ buf.append("\"");
+ }
+ buf.append(">");
+ synchronized (identities) {
+ for (Identity identity : identities) {
+ buf.append(identity.toXML());
+ }
+ }
+ synchronized (features) {
+ for (Feature feature : features) {
+ buf.append(feature.toXML());
+ }
+ }
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</query>");
+ return buf.toString();
+ }
+
+ /**
+ * Test if a DiscoverInfo response contains duplicate identities.
+ *
+ * @return true if duplicate identities where found, otherwise false
+ */
+ public boolean containsDuplicateIdentities() {
+ List<Identity> checkedIdentities = new LinkedList<Identity>();
+ for (Identity i : identities) {
+ for (Identity i2 : checkedIdentities) {
+ if (i.equals(i2))
+ return true;
+ }
+ checkedIdentities.add(i);
+ }
+ return false;
+ }
+
+ /**
+ * Test if a DiscoverInfo response contains duplicate features.
+ *
+ * @return true if duplicate identities where found, otherwise false
+ */
+ public boolean containsDuplicateFeatures() {
+ List<Feature> checkedFeatures = new LinkedList<Feature>();
+ for (Feature f : features) {
+ for (Feature f2 : checkedFeatures) {
+ if (f.equals(f2))
+ return true;
+ }
+ checkedFeatures.add(f);
+ }
+ return false;
+ }
+
+ /**
+ * Represents the identity of a given XMPP entity. An entity may have many identities but all
+ * the identities SHOULD have the same name.<p>
+ *
+ * Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
+ * in order to get the official registry of values for the <i>category</i> and <i>type</i>
+ * attributes.
+ *
+ */
+ public static class Identity implements Comparable<Identity> {
+
+ private String category;
+ private String name;
+ private String type;
+ private String lang; // 'xml:lang;
+
+ /**
+ * Creates a new identity for an XMPP entity.
+ *
+ * @param category the entity's category.
+ * @param name the entity's name.
+ * @deprecated As per the spec, the type field is mandatory and the 3 argument constructor should be used instead.
+ */
+ public Identity(String category, String name) {
+ this.category = category;
+ this.name = name;
+ }
+
+ /**
+ * Creates a new identity for an XMPP entity.
+ * 'category' and 'type' are required by
+ * <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a>
+ *
+ * @param category the entity's category (required as per XEP-30).
+ * @param name the entity's name.
+ * @param type the entity's type (required as per XEP-30).
+ */
+ public Identity(String category, String name, String type) {
+ if ((category == null) || (type == null))
+ throw new IllegalArgumentException("category and type cannot be null");
+
+ this.category = category;
+ this.name = name;
+ this.type = type;
+ }
+
+ /**
+ * Returns the entity's category. To get the official registry of values for the
+ * 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
+ *
+ * @return the entity's category.
+ */
+ public String getCategory() {
+ return category;
+ }
+
+ /**
+ * Returns the identity's name.
+ *
+ * @return the identity's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the entity's type. To get the official registry of values for the
+ * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
+ *
+ * @return the entity's type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the entity's type. To get the official registry of values for the
+ * 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
+ *
+ * @param type the identity's type.
+ * @deprecated As per the spec, this field is mandatory and the 3 argument constructor should be used instead.
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Sets the natural language (xml:lang) for this identity (optional)
+ *
+ * @param lang the xml:lang of this Identity
+ */
+ public void setLanguage(String lang) {
+ this.lang = lang;
+ }
+
+ /**
+ * Returns the identities natural language if one is set
+ *
+ * @return the value of xml:lang of this Identity
+ */
+ public String getLanguage() {
+ return lang;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<identity");
+ // Check if this packet has 'lang' set and maybe append it to the resulting string
+ if (lang != null)
+ buf.append(" xml:lang=\"").append(StringUtils.escapeForXML(lang)).append("\"");
+ // Category must always be set
+ buf.append(" category=\"").append(StringUtils.escapeForXML(category)).append("\"");
+ // Name must always be set
+ buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\"");
+ // Check if this packet has 'type' set and maybe append it to the resulting string
+ if (type != null) {
+ buf.append(" type=\"").append(StringUtils.escapeForXML(type)).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+
+ /**
+ * Check equality for Identity for category, type, lang and name
+ * in that order as defined by
+ * <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a>
+ *
+ */
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (obj == this)
+ return true;
+ if (obj.getClass() != getClass())
+ return false;
+
+ DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj;
+ if (!this.category.equals(other.category))
+ return false;
+
+ String otherLang = other.lang == null ? "" : other.lang;
+ String thisLang = lang == null ? "" : lang;
+ if (!otherLang.equals(thisLang))
+ return false;
+
+ // This safeguard can be removed once the deprecated constructor is removed.
+ String otherType = other.type == null ? "" : other.type;
+ String thisType = type == null ? "" : type;
+ if (!otherType.equals(thisType))
+ return false;
+
+ String otherName = other.name == null ? "" : other.name;
+ String thisName = name == null ? "" : other.name;
+ if (!thisName.equals(otherName))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 37 * result + category.hashCode();
+ result = 37 * result + (lang == null ? 0 : lang.hashCode());
+ result = 37 * result + (type == null ? 0 : type.hashCode());
+ result = 37 * result + (name == null ? 0 : name.hashCode());
+ return result;
+ }
+
+ /**
+ * Compares this identity with another one. The comparison order is:
+ * Category, Type, Lang. If all three are identical the other Identity is considered equal.
+ * Name is not used for comparision, as defined by XEP-0115
+ *
+ * @param obj
+ * @return
+ */
+ public int compareTo(DiscoverInfo.Identity other) {
+ String otherLang = other.lang == null ? "" : other.lang;
+ String thisLang = lang == null ? "" : lang;
+
+ // This can be removed once the deprecated constructor is removed.
+ String otherType = other.type == null ? "" : other.type;
+ String thisType = type == null ? "" : type;
+
+ if (category.equals(other.category)) {
+ if (thisType.equals(otherType)) {
+ if (thisLang.equals(otherLang)) {
+ // Don't compare on name, XEP-30 says that name SHOULD
+ // be equals for all identities of an entity
+ return 0;
+ } else {
+ return thisLang.compareTo(otherLang);
+ }
+ } else {
+ return thisType.compareTo(otherType);
+ }
+ } else {
+ return category.compareTo(other.category);
+ }
+ }
+ }
+
+ /**
+ * Represents the features offered by the item. This information helps requestors determine
+ * what actions are possible with regard to this item (registration, search, join, etc.)
+ * as well as specific feature types of interest, if any (e.g., for the purpose of feature
+ * negotiation).
+ */
+ public static class Feature {
+
+ private String variable;
+
+ /**
+ * Creates a new feature offered by an XMPP entity or item.
+ *
+ * @param variable the feature's variable.
+ */
+ public Feature(String variable) {
+ if (variable == null)
+ throw new IllegalArgumentException("variable cannot be null");
+ this.variable = variable;
+ }
+
+ /**
+ * Returns the feature's variable.
+ *
+ * @return the feature's variable.
+ */
+ public String getVar() {
+ return variable;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<feature var=\"").append(StringUtils.escapeForXML(variable)).append("\"/>");
+ return buf.toString();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (obj == this)
+ return true;
+ if (obj.getClass() != getClass())
+ return false;
+
+ DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj;
+ return variable.equals(other.variable);
+ }
+
+ @Override
+ public int hashCode() {
+ return 37 * variable.hashCode();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/DiscoverItems.java b/src/org/jivesoftware/smackx/packet/DiscoverItems.java
new file mode 100644
index 0000000..f6a0941
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/DiscoverItems.java
@@ -0,0 +1,253 @@
+/**
+ * $RCSfile$
+ * $Revision: 7071 $
+ * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items
+ * associated with XMPP entities.<p>
+ *
+ * The items could also be queried in order to discover if they contain items inside. Some items
+ * may be addressable by its JID and others may require to be addressed by a JID and a node name.
+ *
+ * @author Gaston Dombiak
+ */
+public class DiscoverItems extends IQ {
+
+ public static final String NAMESPACE = "http://jabber.org/protocol/disco#items";
+
+ private final List<Item> items = new CopyOnWriteArrayList<Item>();
+ 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);
+ }
+ }
+
+ /**
+ * Adds a collection of items to the discovered information. Does nothing if itemsToAdd is null
+ *
+ * @param itemsToAdd
+ */
+ public void addItems(Collection<Item> itemsToAdd) {
+ if (itemsToAdd == null) return;
+ for (Item i : itemsToAdd) {
+ addItem(i);
+ }
+ }
+
+ /**
+ * Returns the discovered items of the queried XMPP entity.
+ *
+ * @return an Iterator on the discovered entity's items
+ */
+ public Iterator<DiscoverItems.Item> getItems() {
+ synchronized (items) {
+ return Collections.unmodifiableList(items).iterator();
+ }
+ }
+
+ /**
+ * Returns the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @return the node attribute that supplements the 'jid' attribute
+ */
+ public String getNode() {
+ return node;
+ }
+
+ /**
+ * Sets the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @param node the node attribute that supplements the 'jid' attribute
+ */
+ public void setNode(String node) {
+ this.node = node;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"" + NAMESPACE + "\"");
+ if (getNode() != null) {
+ buf.append(" node=\"");
+ buf.append(StringUtils.escapeForXML(getNode()));
+ buf.append("\"");
+ }
+ buf.append(">");
+ synchronized (items) {
+ for (Item item : items) {
+ buf.append(item.toXML());
+ }
+ }
+ buf.append("</query>");
+ return buf.toString();
+ }
+
+ /**
+ * An item is associated with an XMPP Entity, usually thought of a children of the parent
+ * entity and normally are addressable as a JID.<p>
+ *
+ * An item associated with an entity may not be addressable as a JID. In order to handle
+ * such items, Service Discovery uses an optional 'node' attribute that supplements the
+ * 'jid' attribute.
+ */
+ public static class Item {
+
+ /**
+ * Request to create or update the item.
+ */
+ public static final String UPDATE_ACTION = "update";
+
+ /**
+ * Request to remove the item.
+ */
+ public static final String REMOVE_ACTION = "remove";
+
+ private String entityID;
+ private String name;
+ private String node;
+ private String action;
+
+ /**
+ * Create a new Item associated with a given entity.
+ *
+ * @param entityID the id of the entity that contains the item
+ */
+ public Item(String entityID) {
+ this.entityID = entityID;
+ }
+
+ /**
+ * Returns the entity's ID.
+ *
+ * @return the entity's ID.
+ */
+ public String getEntityID() {
+ return entityID;
+ }
+
+ /**
+ * Returns the entity's name.
+ *
+ * @return the entity's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the entity's name.
+ *
+ * @param name the entity's name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @return the node attribute that supplements the 'jid' attribute
+ */
+ public String getNode() {
+ return node;
+ }
+
+ /**
+ * Sets the node attribute that supplements the 'jid' attribute. A node is merely
+ * something that is associated with a JID and for which the JID can provide information.<p>
+ *
+ * Node attributes SHOULD be used only when trying to provide or query information which
+ * is not directly addressable.
+ *
+ * @param node the node attribute that supplements the 'jid' attribute
+ */
+ public void setNode(String node) {
+ this.node = node;
+ }
+
+ /**
+ * Returns the action that specifies the action being taken for this item. Possible action
+ * values are: "update" and "remove". Update should either create a new entry if the node
+ * and jid combination does not already exist, or simply update an existing entry. If
+ * "remove" is used as the action, the item should be removed from persistent storage.
+ *
+ * @return the action being taken for this item
+ */
+ public String getAction() {
+ return action;
+ }
+
+ /**
+ * Sets the action that specifies the action being taken for this item. Possible action
+ * values are: "update" and "remove". Update should either create a new entry if the node
+ * and jid combination does not already exist, or simply update an existing entry. If
+ * "remove" is used as the action, the item should be removed from persistent storage.
+ *
+ * @param action the action being taken for this item
+ */
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item jid=\"").append(entityID).append("\"");
+ if (name != null) {
+ buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\"");
+ }
+ if (node != null) {
+ buf.append(" node=\"").append(StringUtils.escapeForXML(node)).append("\"");
+ }
+ if (action != null) {
+ buf.append(" action=\"").append(StringUtils.escapeForXML(action)).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/Header.java b/src/org/jivesoftware/smackx/packet/Header.java
new file mode 100644
index 0000000..3fa8386
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/Header.java
@@ -0,0 +1,59 @@
+/*
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents a <b>Header</b> entry as specified by the <a href="http://xmpp.org/extensions/xep-031.html">Stanza Headers and Internet Metadata (SHIM)</a>
+
+ * @author Robin Collier
+ */
+public class Header implements PacketExtension
+{
+ private String name;
+ private String value;
+
+ public Header(String name, String value)
+ {
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public String getElementName()
+ {
+ return "header";
+ }
+
+ public String getNamespace()
+ {
+ return HeadersExtension.NAMESPACE;
+ }
+
+ public String toXML()
+ {
+ return "<header name='" + name + "'>" + value + "</header>";
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/HeadersExtension.java b/src/org/jivesoftware/smackx/packet/HeadersExtension.java
new file mode 100644
index 0000000..78564db
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/HeadersExtension.java
@@ -0,0 +1,69 @@
+/**
+ * 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.Collection;
+import java.util.Collections;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Extension representing a list of headers as specified in <a href="http://xmpp.org/extensions/xep-0131">Stanza Headers and Internet Metadata (SHIM)</a>
+ *
+ * @see Header
+ *
+ * @author Robin Collier
+ */
+public class HeadersExtension implements PacketExtension
+{
+ public static final String NAMESPACE = "http://jabber.org/protocol/shim";
+
+ private Collection<Header> headers = Collections.EMPTY_LIST;
+
+ public HeadersExtension(Collection<Header> headerList)
+ {
+ if (headerList != null)
+ headers = headerList;
+ }
+
+ public Collection<Header> getHeaders()
+ {
+ return headers;
+ }
+
+ public String getElementName()
+ {
+ return "headers";
+ }
+
+ public String getNamespace()
+ {
+ return NAMESPACE;
+ }
+
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<" + getElementName() + " xmlns='" + getNamespace() + "'>");
+
+ for (Header header : headers)
+ {
+ builder.append(header.toXML());
+ }
+ builder.append("</" + getElementName() + '>');
+
+ return builder.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/LastActivity.java b/src/org/jivesoftware/smackx/packet/LastActivity.java
new file mode 100644
index 0000000..6f4f15a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/LastActivity.java
@@ -0,0 +1,164 @@
+/**
+ * $RCSfile$
+ * $Revision: 2407 $
+ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $
+ *
+ * Copyright 2003-2007 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.io.IOException;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.StringUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * A last activity IQ for retrieving information about the last activity associated with a Jabber ID.
+ * LastActivity (XEP-0012) allows for retrieval of how long a particular user has been idle and the
+ * message the specified when doing so. Use {@link org.jivesoftware.smackx.LastActivityManager}
+ * to get the last activity of a user.
+ *
+ * @author Derek DeMoro
+ */
+public class LastActivity extends IQ {
+
+ public static final String NAMESPACE = "jabber:iq:last";
+
+ public long lastActivity = -1;
+ public String message;
+
+ public LastActivity() {
+ setType(IQ.Type.GET);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"" + NAMESPACE + "\"");
+ if (lastActivity != -1) {
+ buf.append(" seconds=\"").append(lastActivity).append("\"");
+ }
+ buf.append("></query>");
+ return buf.toString();
+ }
+
+
+ public void setLastActivity(long lastActivity) {
+ this.lastActivity = lastActivity;
+ }
+
+
+ private void setMessage(String message) {
+ this.message = message;
+ }
+
+ /**
+ * Returns number of seconds that have passed since the user last logged out.
+ * If the user is offline, 0 will be returned.
+ *
+ * @return the number of seconds that have passed since the user last logged out.
+ */
+ public long getIdleTime() {
+ return lastActivity;
+ }
+
+
+ /**
+ * Returns the status message of the last unavailable presence received from the user.
+ *
+ * @return the status message of the last unavailable presence received from the user
+ */
+ public String getStatusMessage() {
+ return message;
+ }
+
+
+ /**
+ * The IQ Provider for LastActivity.
+ *
+ * @author Derek DeMoro
+ */
+ public static class Provider implements IQProvider {
+
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws XMPPException, XmlPullParserException {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ throw new XMPPException("Parser not in proper position, or bad XML.");
+ }
+
+ LastActivity lastActivity = new LastActivity();
+ String seconds = parser.getAttributeValue("", "seconds");
+ String message = null;
+ try {
+ message = parser.nextText();
+ } catch (IOException e1) {
+ // Ignore
+ }
+ if (seconds != null) {
+ try {
+ lastActivity.setLastActivity(Long.parseLong(seconds));
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+
+ if (message != null) {
+ lastActivity.setMessage(message);
+ }
+ return lastActivity;
+ }
+ }
+
+ /**
+ * Retrieve the last activity of a particular jid.
+ * @param con the current Connection.
+ * @param jid the JID of the user.
+ * @return the LastActivity packet of the jid.
+ * @throws XMPPException thrown if a server error has occured.
+ * @deprecated This method only retreives the lapsed time since the last logout of a particular jid.
+ * Replaced by {@link org.jivesoftware.smackx.LastActivityManager#getLastActivity(Connection, String) getLastActivity}
+ */
+ public static LastActivity getLastActivity(Connection con, String jid) throws XMPPException {
+ LastActivity activity = new LastActivity();
+ jid = StringUtils.parseBareAddress(jid);
+ activity.setTo(jid);
+
+ PacketCollector collector = con.createPacketCollector(new PacketIDFilter(activity.getPacketID()));
+ con.sendPacket(activity);
+
+ LastActivity response = (LastActivity) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ return response;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/MUCAdmin.java b/src/org/jivesoftware/smackx/packet/MUCAdmin.java
new file mode 100644
index 0000000..75d17e0
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MUCAdmin.java
@@ -0,0 +1,237 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * IQ packet that serves for kicking users, granting and revoking voice, banning users,
+ * modifying the ban list, granting and revoking membership and granting and revoking
+ * moderator privileges. All these operations are scoped by the
+ * 'http://jabber.org/protocol/muc#admin' namespace.
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCAdmin extends IQ {
+
+ private List<Item> items = new ArrayList<Item>();
+
+ /**
+ * Returns an Iterator for item childs that holds information about roles, affiliation,
+ * jids and nicks.
+ *
+ * @return an Iterator for item childs that holds information about roles, affiliation,
+ * jids and nicks.
+ */
+ public Iterator<Item> getItems() {
+ synchronized (items) {
+ return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator();
+ }
+ }
+
+ /**
+ * Adds an item child that holds information about roles, affiliation, jids and nicks.
+ *
+ * @param item the item child that holds information about roles, affiliation, jids and nicks.
+ */
+ public void addItem(Item item) {
+ synchronized (items) {
+ items.add(item);
+ }
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"http://jabber.org/protocol/muc#admin\">");
+ synchronized (items) {
+ for (int i = 0; i < items.size(); i++) {
+ Item item = items.get(i);
+ buf.append(item.toXML());
+ }
+ }
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</query>");
+ return buf.toString();
+ }
+
+ /**
+ * Item child that holds information about roles, affiliation, jids and nicks.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Item {
+ private String actor;
+ private String reason;
+ private String affiliation;
+ private String jid;
+ private String nick;
+ private String role;
+
+ /**
+ * Creates a new item child.
+ *
+ * @param affiliation the actor's affiliation to the room
+ * @param role the privilege level of an occupant within a room.
+ */
+ public Item(String affiliation, String role) {
+ this.affiliation = affiliation;
+ this.role = role;
+ }
+
+ /**
+ * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @return the JID of an occupant in the room that was kicked or banned.
+ */
+ public String getActor() {
+ return actor;
+ }
+
+ /**
+ * Returns the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @return the reason for the item child.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent
+ * association or connection with a room. The possible affiliations are "owner", "admin",
+ * "member", and "outcast" (naturally it is also possible to have no affiliation). An
+ * affiliation lasts across a user's visits to a room.
+ *
+ * @return the actor's affiliation to the room
+ */
+ public String getAffiliation() {
+ return affiliation;
+ }
+
+ /**
+ * Returns the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @return the room JID by which an occupant is identified within the room.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Returns the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @return the new nickname of an occupant that is changing his/her nickname.
+ */
+ public String getNick() {
+ return nick;
+ }
+
+ /**
+ * Returns the temporary position or privilege level of an occupant within a room. The
+ * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+ * have no defined role). A role lasts only for the duration of an occupant's visit to
+ * a room.
+ *
+ * @return the privilege level of an occupant within a room.
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+ */
+ public void setActor(String actor) {
+ this.actor = actor;
+ }
+
+ /**
+ * Sets the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @param reason the reason why a user (occupant) was kicked or banned.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Sets the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @param jid the JID by which an occupant is identified within a room.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Sets the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @param nick the new nickname of an occupant that is changing his/her nickname.
+ */
+ public void setNick(String nick) {
+ this.nick = nick;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item");
+ if (getAffiliation() != null) {
+ buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+ }
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getNick() != null) {
+ buf.append(" nick=\"").append(getNick()).append("\"");
+ }
+ if (getRole() != null) {
+ buf.append(" role=\"").append(getRole()).append("\"");
+ }
+ if (getReason() == null && getActor() == null) {
+ buf.append("/>");
+ }
+ else {
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ if (getActor() != null) {
+ buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+ }
+ buf.append("</item>");
+ }
+ return buf.toString();
+ }
+ };
+}
diff --git a/src/org/jivesoftware/smackx/packet/MUCInitialPresence.java b/src/org/jivesoftware/smackx/packet/MUCInitialPresence.java
new file mode 100644
index 0000000..d3d2796
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MUCInitialPresence.java
@@ -0,0 +1,223 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Represents extended presence information whose sole purpose is to signal the ability of
+ * the occupant to speak the MUC protocol when joining a room. If the room requires a password
+ * then the MUCInitialPresence should include one.<p>
+ *
+ * The amount of discussion history provided on entering a room (perhaps because the
+ * user is on a low-bandwidth connection or is using a small-footprint client) could be managed by
+ * setting a configured History instance to the MUCInitialPresence instance.
+ * @see MUCInitialPresence#setHistory(MUCInitialPresence.History).
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCInitialPresence implements PacketExtension {
+
+ private String password;
+ private History history;
+
+ public String getElementName() {
+ return "x";
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/muc";
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ if (getPassword() != null) {
+ buf.append("<password>").append(getPassword()).append("</password>");
+ }
+ if (getHistory() != null) {
+ buf.append(getHistory().toXML());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ /**
+ * Returns the history that manages the amount of discussion history provided on
+ * entering a room.
+ *
+ * @return the history that manages the amount of discussion history provided on
+ * entering a room.
+ */
+ public History getHistory() {
+ return history;
+ }
+
+ /**
+ * Returns the password to use when the room requires a password.
+ *
+ * @return the password to use when the room requires a password.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Sets the History that manages the amount of discussion history provided on
+ * entering a room.
+ *
+ * @param history that manages the amount of discussion history provided on
+ * entering a room.
+ */
+ public void setHistory(History history) {
+ this.history = history;
+ }
+
+ /**
+ * Sets the password to use when the room requires a password.
+ *
+ * @param password the password to use when the room requires a password.
+ */
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ /**
+ * The History class controls the number of characters or messages to receive
+ * when entering a room.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class History {
+
+ private int maxChars = -1;
+ private int maxStanzas = -1;
+ private int seconds = -1;
+ private Date since;
+
+ /**
+ * Returns the total number of characters to receive in the history.
+ *
+ * @return total number of characters to receive in the history.
+ */
+ public int getMaxChars() {
+ return maxChars;
+ }
+
+ /**
+ * Returns the total number of messages to receive in the history.
+ *
+ * @return the total number of messages to receive in the history.
+ */
+ public int getMaxStanzas() {
+ return maxStanzas;
+ }
+
+ /**
+ * Returns the number of seconds to use to filter the messages received during that time.
+ * In other words, only the messages received in the last "X" seconds will be included in
+ * the history.
+ *
+ * @return the number of seconds to use to filter the messages received during that time.
+ */
+ public int getSeconds() {
+ return seconds;
+ }
+
+ /**
+ * Returns the since date to use to filter the messages received during that time.
+ * In other words, only the messages received since the datetime specified will be
+ * included in the history.
+ *
+ * @return the since date to use to filter the messages received during that time.
+ */
+ public Date getSince() {
+ return since;
+ }
+
+ /**
+ * Sets the total number of characters to receive in the history.
+ *
+ * @param maxChars the total number of characters to receive in the history.
+ */
+ public void setMaxChars(int maxChars) {
+ this.maxChars = maxChars;
+ }
+
+ /**
+ * Sets the total number of messages to receive in the history.
+ *
+ * @param maxStanzas the total number of messages to receive in the history.
+ */
+ public void setMaxStanzas(int maxStanzas) {
+ this.maxStanzas = maxStanzas;
+ }
+
+ /**
+ * Sets the number of seconds to use to filter the messages received during that time.
+ * In other words, only the messages received in the last "X" seconds will be included in
+ * the history.
+ *
+ * @param seconds the number of seconds to use to filter the messages received during
+ * that time.
+ */
+ public void setSeconds(int seconds) {
+ this.seconds = seconds;
+ }
+
+ /**
+ * Sets the since date to use to filter the messages received during that time.
+ * In other words, only the messages received since the datetime specified will be
+ * included in the history.
+ *
+ * @param since the since date to use to filter the messages received during that time.
+ */
+ public void setSince(Date since) {
+ this.since = since;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<history");
+ if (getMaxChars() != -1) {
+ buf.append(" maxchars=\"").append(getMaxChars()).append("\"");
+ }
+ if (getMaxStanzas() != -1) {
+ buf.append(" maxstanzas=\"").append(getMaxStanzas()).append("\"");
+ }
+ if (getSeconds() != -1) {
+ buf.append(" seconds=\"").append(getSeconds()).append("\"");
+ }
+ if (getSince() != null) {
+ SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ utcFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ buf.append(" since=\"").append(utcFormat.format(getSince())).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/MUCOwner.java b/src/org/jivesoftware/smackx/packet/MUCOwner.java
new file mode 100644
index 0000000..e33806e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MUCOwner.java
@@ -0,0 +1,342 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * IQ packet that serves for granting and revoking ownership privileges, granting
+ * and revoking administrative privileges and destroying a room. All these operations
+ * are scoped by the 'http://jabber.org/protocol/muc#owner' namespace.
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCOwner extends IQ {
+
+ private List<Item> items = new ArrayList<Item>();
+ private Destroy destroy;
+
+ /**
+ * Returns an Iterator for item childs that holds information about affiliation,
+ * jids and nicks.
+ *
+ * @return an Iterator for item childs that holds information about affiliation,
+ * jids and nicks.
+ */
+ public Iterator<Item> getItems() {
+ synchronized (items) {
+ return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator();
+ }
+ }
+
+ /**
+ * Returns a request to the server to destroy a room. The sender of the request
+ * should be the room's owner. If the sender of the destroy request is not the room's owner
+ * then the server will answer a "Forbidden" error.
+ *
+ * @return a request to the server to destroy a room.
+ */
+ public Destroy getDestroy() {
+ return destroy;
+ }
+
+ /**
+ * Sets a request to the server to destroy a room. The sender of the request
+ * should be the room's owner. If the sender of the destroy request is not the room's owner
+ * then the server will answer a "Forbidden" error.
+ *
+ * @param destroy the request to the server to destroy a room.
+ */
+ public void setDestroy(Destroy destroy) {
+ this.destroy = destroy;
+ }
+
+ /**
+ * Adds an item child that holds information about affiliation, jids and nicks.
+ *
+ * @param item the item child that holds information about affiliation, jids and nicks.
+ */
+ public void addItem(Item item) {
+ synchronized (items) {
+ items.add(item);
+ }
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"http://jabber.org/protocol/muc#owner\">");
+ synchronized (items) {
+ for (int i = 0; i < items.size(); i++) {
+ Item item = (Item) items.get(i);
+ buf.append(item.toXML());
+ }
+ }
+ if (getDestroy() != null) {
+ buf.append(getDestroy().toXML());
+ }
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</query>");
+ return buf.toString();
+ }
+
+ /**
+ * Item child that holds information about affiliation, jids and nicks.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Item {
+
+ private String actor;
+ private String reason;
+ private String affiliation;
+ private String jid;
+ private String nick;
+ private String role;
+
+ /**
+ * Creates a new item child.
+ *
+ * @param affiliation the actor's affiliation to the room
+ */
+ public Item(String affiliation) {
+ this.affiliation = affiliation;
+ }
+
+ /**
+ * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @return the JID of an occupant in the room that was kicked or banned.
+ */
+ public String getActor() {
+ return actor;
+ }
+
+ /**
+ * Returns the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @return the reason for the item child.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent
+ * association or connection with a room. The possible affiliations are "owner", "admin",
+ * "member", and "outcast" (naturally it is also possible to have no affiliation). An
+ * affiliation lasts across a user's visits to a room.
+ *
+ * @return the actor's affiliation to the room
+ */
+ public String getAffiliation() {
+ return affiliation;
+ }
+
+ /**
+ * Returns the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @return the room JID by which an occupant is identified within the room.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Returns the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @return the new nickname of an occupant that is changing his/her nickname.
+ */
+ public String getNick() {
+ return nick;
+ }
+
+ /**
+ * Returns the temporary position or privilege level of an occupant within a room. The
+ * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+ * have no defined role). A role lasts only for the duration of an occupant's visit to
+ * a room.
+ *
+ * @return the privilege level of an occupant within a room.
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+ */
+ public void setActor(String actor) {
+ this.actor = actor;
+ }
+
+ /**
+ * Sets the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @param reason the reason why a user (occupant) was kicked or banned.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Sets the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @param jid the JID by which an occupant is identified within a room.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Sets the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @param nick the new nickname of an occupant that is changing his/her nickname.
+ */
+ public void setNick(String nick) {
+ this.nick = nick;
+ }
+
+ /**
+ * Sets the temporary position or privilege level of an occupant within a room. The
+ * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+ * have no defined role). A role lasts only for the duration of an occupant's visit to
+ * a room.
+ *
+ * @param role the new privilege level of an occupant within a room.
+ */
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item");
+ if (getAffiliation() != null) {
+ buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+ }
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getNick() != null) {
+ buf.append(" nick=\"").append(getNick()).append("\"");
+ }
+ if (getRole() != null) {
+ buf.append(" role=\"").append(getRole()).append("\"");
+ }
+ if (getReason() == null && getActor() == null) {
+ buf.append("/>");
+ }
+ else {
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ if (getActor() != null) {
+ buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+ }
+ buf.append("</item>");
+ }
+ return buf.toString();
+ }
+ };
+
+ /**
+ * Represents a request to the server to destroy a room. The sender of the request
+ * should be the room's owner. If the sender of the destroy request is not the room's owner
+ * then the server will answer a "Forbidden" error.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Destroy {
+ private String reason;
+ private String jid;
+
+
+ /**
+ * Returns the JID of an alternate location since the current room is being destroyed.
+ *
+ * @return the JID of an alternate location.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Returns the reason for the room destruction.
+ *
+ * @return the reason for the room destruction.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Sets the JID of an alternate location since the current room is being destroyed.
+ *
+ * @param jid the JID of an alternate location.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Sets the reason for the room destruction.
+ *
+ * @param reason the reason for the room destruction.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<destroy");
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getReason() == null) {
+ buf.append("/>");
+ }
+ else {
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ buf.append("</destroy>");
+ }
+ return buf.toString();
+ }
+
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/MUCUser.java b/src/org/jivesoftware/smackx/packet/MUCUser.java
new file mode 100644
index 0000000..bfcd67c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MUCUser.java
@@ -0,0 +1,627 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents extended presence information about roles, affiliations, full JIDs,
+ * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace.
+ *
+ * @author Gaston Dombiak
+ */
+public class MUCUser implements PacketExtension {
+
+ private Invite invite;
+ private Decline decline;
+ private Item item;
+ private String password;
+ private Status status;
+ private Destroy destroy;
+
+ public String getElementName() {
+ return "x";
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/muc#user";
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ if (getInvite() != null) {
+ buf.append(getInvite().toXML());
+ }
+ if (getDecline() != null) {
+ buf.append(getDecline().toXML());
+ }
+ if (getItem() != null) {
+ buf.append(getItem().toXML());
+ }
+ if (getPassword() != null) {
+ buf.append("<password>").append(getPassword()).append("</password>");
+ }
+ if (getStatus() != null) {
+ buf.append(getStatus().toXML());
+ }
+ if (getDestroy() != null) {
+ buf.append(getDestroy().toXML());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ /**
+ * Returns the invitation for another user to a room. The sender of the invitation
+ * must be an occupant of the room. The invitation will be sent to the room which in turn
+ * will forward the invitation to the invitee.
+ *
+ * @return an invitation for another user to a room.
+ */
+ public Invite getInvite() {
+ return invite;
+ }
+
+ /**
+ * Returns the rejection to an invitation from another user to a room. The rejection will be
+ * sent to the room which in turn will forward the refusal to the inviter.
+ *
+ * @return a rejection to an invitation from another user to a room.
+ */
+ public Decline getDecline() {
+ return decline;
+ }
+
+ /**
+ * Returns the item child that holds information about roles, affiliation, jids and nicks.
+ *
+ * @return an item child that holds information about roles, affiliation, jids and nicks.
+ */
+ public Item getItem() {
+ return item;
+ }
+
+ /**
+ * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is
+ * a room that a user cannot enter without first providing the correct password.
+ *
+ * @return the password to use to enter Password-Protected Room.
+ */
+ public String getPassword() {
+ return password;
+ }
+
+ /**
+ * Returns the status which holds a code that assists in presenting notification messages.
+ *
+ * @return the status which holds a code that assists in presenting notification messages.
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Returns the notification that the room has been destroyed. After a room has been destroyed,
+ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for
+ * the room destruction if provided by the room owner.
+ *
+ * @return a notification that the room has been destroyed.
+ */
+ public Destroy getDestroy() {
+ return destroy;
+ }
+
+ /**
+ * Sets the invitation for another user to a room. The sender of the invitation
+ * must be an occupant of the room. The invitation will be sent to the room which in turn
+ * will forward the invitation to the invitee.
+ *
+ * @param invite the invitation for another user to a room.
+ */
+ public void setInvite(Invite invite) {
+ this.invite = invite;
+ }
+
+ /**
+ * Sets the rejection to an invitation from another user to a room. The rejection will be
+ * sent to the room which in turn will forward the refusal to the inviter.
+ *
+ * @param decline the rejection to an invitation from another user to a room.
+ */
+ public void setDecline(Decline decline) {
+ this.decline = decline;
+ }
+
+ /**
+ * Sets the item child that holds information about roles, affiliation, jids and nicks.
+ *
+ * @param item the item child that holds information about roles, affiliation, jids and nicks.
+ */
+ public void setItem(Item item) {
+ this.item = item;
+ }
+
+ /**
+ * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is
+ * a room that a user cannot enter without first providing the correct password.
+ *
+ * @param string the password to use to enter Password-Protected Room.
+ */
+ public void setPassword(String string) {
+ password = string;
+ }
+
+ /**
+ * Sets the status which holds a code that assists in presenting notification messages.
+ *
+ * @param status the status which holds a code that assists in presenting notification
+ * messages.
+ */
+ public void setStatus(Status status) {
+ this.status = status;
+ }
+
+ /**
+ * Sets the notification that the room has been destroyed. After a room has been destroyed,
+ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for
+ * the room destruction if provided by the room owner.
+ *
+ * @param destroy the notification that the room has been destroyed.
+ */
+ public void setDestroy(Destroy destroy) {
+ this.destroy = destroy;
+ }
+
+ /**
+ * Represents an invitation for another user to a room. The sender of the invitation
+ * must be an occupant of the room. The invitation will be sent to the room which in turn
+ * will forward the invitation to the invitee.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Invite {
+ private String reason;
+ private String from;
+ private String to;
+
+ /**
+ * Returns the bare JID of the inviter or, optionally, the room JID. (e.g.
+ * 'crone1@shakespeare.lit/desktop').
+ *
+ * @return the room's occupant that sent the invitation.
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * Returns the message explaining the invitation.
+ *
+ * @return the message explaining the invitation.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit')
+ *
+ * @return the bare JID of the invitee.
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * Sets the bare JID of the inviter or, optionally, the room JID. (e.g.
+ * 'crone1@shakespeare.lit/desktop')
+ *
+ * @param from the bare JID of the inviter or, optionally, the room JID.
+ */
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * Sets the message explaining the invitation.
+ *
+ * @param reason the message explaining the invitation.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Sets the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit')
+ *
+ * @param to the bare JID of the invitee.
+ */
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<invite ");
+ if (getTo() != null) {
+ buf.append(" to=\"").append(getTo()).append("\"");
+ }
+ if (getFrom() != null) {
+ buf.append(" from=\"").append(getFrom()).append("\"");
+ }
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ buf.append("</invite>");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Represents a rejection to an invitation from another user to a room. The rejection will be
+ * sent to the room which in turn will forward the refusal to the inviter.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Decline {
+ private String reason;
+ private String from;
+ private String to;
+
+ /**
+ * Returns the bare JID of the invitee that rejected the invitation. (e.g.
+ * 'crone1@shakespeare.lit/desktop').
+ *
+ * @return the bare JID of the invitee that rejected the invitation.
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * Returns the message explaining why the invitation was rejected.
+ *
+ * @return the message explaining the reason for the rejection.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Returns the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit')
+ *
+ * @return the bare JID of the inviter.
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * Sets the bare JID of the invitee that rejected the invitation. (e.g.
+ * 'crone1@shakespeare.lit/desktop').
+ *
+ * @param from the bare JID of the invitee that rejected the invitation.
+ */
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * Sets the message explaining why the invitation was rejected.
+ *
+ * @param reason the message explaining the reason for the rejection.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Sets the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit')
+ *
+ * @param to the bare JID of the inviter.
+ */
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<decline ");
+ if (getTo() != null) {
+ buf.append(" to=\"").append(getTo()).append("\"");
+ }
+ if (getFrom() != null) {
+ buf.append(" from=\"").append(getFrom()).append("\"");
+ }
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ buf.append("</decline>");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Item child that holds information about roles, affiliation, jids and nicks.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Item {
+ private String actor;
+ private String reason;
+ private String affiliation;
+ private String jid;
+ private String nick;
+ private String role;
+
+ /**
+ * Creates a new item child.
+ *
+ * @param affiliation the actor's affiliation to the room
+ * @param role the privilege level of an occupant within a room.
+ */
+ public Item(String affiliation, String role) {
+ this.affiliation = affiliation;
+ this.role = role;
+ }
+
+ /**
+ * Returns the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @return the JID of an occupant in the room that was kicked or banned.
+ */
+ public String getActor() {
+ return actor == null ? "" : actor;
+ }
+
+ /**
+ * Returns the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @return the reason for the item child.
+ */
+ public String getReason() {
+ return reason == null ? "" : reason;
+ }
+
+ /**
+ * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent
+ * association or connection with a room. The possible affiliations are "owner", "admin",
+ * "member", and "outcast" (naturally it is also possible to have no affiliation). An
+ * affiliation lasts across a user's visits to a room.
+ *
+ * @return the actor's affiliation to the room
+ */
+ public String getAffiliation() {
+ return affiliation;
+ }
+
+ /**
+ * Returns the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @return the room JID by which an occupant is identified within the room.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Returns the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @return the new nickname of an occupant that is changing his/her nickname.
+ */
+ public String getNick() {
+ return nick;
+ }
+
+ /**
+ * Returns the temporary position or privilege level of an occupant within a room. The
+ * possible roles are "moderator", "participant", and "visitor" (it is also possible to
+ * have no defined role). A role lasts only for the duration of an occupant's visit to
+ * a room.
+ *
+ * @return the privilege level of an occupant within a room.
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Sets the actor (JID of an occupant in the room) that was kicked or banned.
+ *
+ * @param actor the actor (JID of an occupant in the room) that was kicked or banned.
+ */
+ public void setActor(String actor) {
+ this.actor = actor;
+ }
+
+ /**
+ * Sets the reason for the item child. The reason is optional and could be used to
+ * explain the reason why a user (occupant) was kicked or banned.
+ *
+ * @param reason the reason why a user (occupant) was kicked or banned.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Sets the <room@service/nick> by which an occupant is identified within the context
+ * of a room. If the room is non-anonymous, the JID will be included in the item.
+ *
+ * @param jid the JID by which an occupant is identified within a room.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Sets the new nickname of an occupant that is changing his/her nickname. The new
+ * nickname is sent as part of the unavailable presence.
+ *
+ * @param nick the new nickname of an occupant that is changing his/her nickname.
+ */
+ public void setNick(String nick) {
+ this.nick = nick;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item");
+ if (getAffiliation() != null) {
+ buf.append(" affiliation=\"").append(getAffiliation()).append("\"");
+ }
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getNick() != null) {
+ buf.append(" nick=\"").append(getNick()).append("\"");
+ }
+ if (getRole() != null) {
+ buf.append(" role=\"").append(getRole()).append("\"");
+ }
+ if (getReason() == null && getActor() == null) {
+ buf.append("/>");
+ }
+ else {
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ if (getActor() != null) {
+ buf.append("<actor jid=\"").append(getActor()).append("\"/>");
+ }
+ buf.append("</item>");
+ }
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Status code assists in presenting notification messages. The following link provides the
+ * list of existing error codes (@link http://www.jabber.org/jeps/jep-0045.html#errorstatus).
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Status {
+ private String code;
+
+ /**
+ * Creates a new instance of Status with the specified code.
+ *
+ * @param code the code that uniquely identifies the reason of the error.
+ */
+ public Status(String code) {
+ this.code = code;
+ }
+
+ /**
+ * Returns the code that uniquely identifies the reason of the error. The code
+ * assists in presenting notification messages.
+ *
+ * @return the code that uniquely identifies the reason of the error.
+ */
+ public String getCode() {
+ return code;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<status code=\"").append(getCode()).append("\"/>");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Represents a notification that the room has been destroyed. After a room has been destroyed,
+ * the room occupants will receive a Presence packet of type 'unavailable' with the reason for
+ * the room destruction if provided by the room owner.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Destroy {
+ private String reason;
+ private String jid;
+
+
+ /**
+ * Returns the JID of an alternate location since the current room is being destroyed.
+ *
+ * @return the JID of an alternate location.
+ */
+ public String getJid() {
+ return jid;
+ }
+
+ /**
+ * Returns the reason for the room destruction.
+ *
+ * @return the reason for the room destruction.
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Sets the JID of an alternate location since the current room is being destroyed.
+ *
+ * @param jid the JID of an alternate location.
+ */
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ /**
+ * Sets the reason for the room destruction.
+ *
+ * @param reason the reason for the room destruction.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<destroy");
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getReason() == null) {
+ buf.append("/>");
+ }
+ else {
+ buf.append(">");
+ if (getReason() != null) {
+ buf.append("<reason>").append(getReason()).append("</reason>");
+ }
+ buf.append("</destroy>");
+ }
+ return buf.toString();
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/packet/MessageEvent.java b/src/org/jivesoftware/smackx/packet/MessageEvent.java
new file mode 100644
index 0000000..5e146dc
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MessageEvent.java
@@ -0,0 +1,335 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Represents message events relating to the delivery, display, composition and cancellation of
+ * messages.<p>
+ *
+ * There are four message events currently defined in this namespace:
+ * <ol>
+ * <li>Offline<br>
+ * Indicates that the message has been stored offline by the intended recipient's server. This
+ * event is triggered only if the intended recipient's server supports offline storage, has that
+ * support enabled, and the recipient is offline when the server receives the message for delivery.</li>
+ *
+ * <li>Delivered<br>
+ * Indicates that the message has been delivered to the recipient. This signifies that the message
+ * has reached the recipient's XMPP client, but does not necessarily mean that the message has
+ * been displayed. This event is to be raised by the XMPP client.</li>
+ *
+ * <li>Displayed<br>
+ * Once the message has been received by the recipient's XMPP client, it may be displayed to the
+ * user. This event indicates that the message has been displayed, and is to be raised by the
+ * XMPP client. Even if a message is displayed multiple times, this event should be raised only
+ * once.</li>
+ *
+ * <li>Composing<br>
+ * In threaded chat conversations, this indicates that the recipient is composing a reply to a
+ * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed
+ * to raise this event multiple times in response to the same request, providing the original
+ * event is cancelled first.</li>
+ * </ol>
+ *
+ * @author Gaston Dombiak
+ */
+public class MessageEvent implements PacketExtension {
+
+ public static final String OFFLINE = "offline";
+ public static final String COMPOSING = "composing";
+ public static final String DISPLAYED = "displayed";
+ public static final String DELIVERED = "delivered";
+ public static final String CANCELLED = "cancelled";
+
+ private boolean offline = false;
+ private boolean delivered = false;
+ private boolean displayed = false;
+ private boolean composing = false;
+ private boolean cancelled = true;
+
+ private String packetID = null;
+
+ /**
+ * Returns the XML element name of the extension sub-packet root element.
+ * Always returns "x"
+ *
+ * @return the XML element name of the packet extension.
+ */
+ public String getElementName() {
+ return "x";
+ }
+
+ /**
+ * Returns the XML namespace of the extension sub-packet root element.
+ * According the specification the namespace is always "jabber:x:event"
+ *
+ * @return the XML namespace of the packet extension.
+ */
+ public String getNamespace() {
+ return "jabber:x:event";
+ }
+
+ /**
+ * When the message is a request returns if the sender of the message requests to be notified
+ * when the receiver is composing a reply.
+ * When the message is a notification returns if the receiver of the message is composing a
+ * reply.
+ *
+ * @return true if the sender is requesting to be notified when composing or when notifying
+ * that the receiver of the message is composing a reply
+ */
+ public boolean isComposing() {
+ return composing;
+ }
+
+ /**
+ * When the message is a request returns if the sender of the message requests to be notified
+ * when the message is delivered.
+ * When the message is a notification returns if the message was delivered or not.
+ *
+ * @return true if the sender is requesting to be notified when delivered or when notifying
+ * that the message was delivered
+ */
+ public boolean isDelivered() {
+ return delivered;
+ }
+
+ /**
+ * When the message is a request returns if the sender of the message requests to be notified
+ * when the message is displayed.
+ * When the message is a notification returns if the message was displayed or not.
+ *
+ * @return true if the sender is requesting to be notified when displayed or when notifying
+ * that the message was displayed
+ */
+ public boolean isDisplayed() {
+ return displayed;
+ }
+
+ /**
+ * When the message is a request returns if the sender of the message requests to be notified
+ * when the receiver of the message is offline.
+ * When the message is a notification returns if the receiver of the message was offline.
+ *
+ * @return true if the sender is requesting to be notified when offline or when notifying
+ * that the receiver of the message is offline
+ */
+ public boolean isOffline() {
+ return offline;
+ }
+
+ /**
+ * When the message is a notification returns if the receiver of the message cancelled
+ * composing a reply.
+ *
+ * @return true if the receiver of the message cancelled composing a reply
+ */
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ /**
+ * Returns the unique ID of the message that requested to be notified of the event.
+ * The packet id is not used when the message is a request for notifications
+ *
+ * @return the message id that requested to be notified of the event.
+ */
+ public String getPacketID() {
+ return packetID;
+ }
+
+ /**
+ * Returns the types of events. The type of event could be:
+ * "offline", "composing","delivered","displayed", "offline"
+ *
+ * @return an iterator over all the types of events of the MessageEvent.
+ */
+ public Iterator<String> getEventTypes() {
+ ArrayList<String> allEvents = new ArrayList<String>();
+ if (isDelivered()) {
+ allEvents.add(MessageEvent.DELIVERED);
+ }
+ if (!isMessageEventRequest() && isCancelled()) {
+ allEvents.add(MessageEvent.CANCELLED);
+ }
+ if (isComposing()) {
+ allEvents.add(MessageEvent.COMPOSING);
+ }
+ if (isDisplayed()) {
+ allEvents.add(MessageEvent.DISPLAYED);
+ }
+ if (isOffline()) {
+ allEvents.add(MessageEvent.OFFLINE);
+ }
+ return allEvents.iterator();
+ }
+
+ /**
+ * When the message is a request sets if the sender of the message requests to be notified
+ * when the receiver is composing a reply.
+ * When the message is a notification sets if the receiver of the message is composing a
+ * reply.
+ *
+ * @param composing sets if the sender is requesting to be notified when composing or when
+ * notifying that the receiver of the message is composing a reply
+ */
+ public void setComposing(boolean composing) {
+ this.composing = composing;
+ setCancelled(false);
+ }
+
+ /**
+ * When the message is a request sets if the sender of the message requests to be notified
+ * when the message is delivered.
+ * When the message is a notification sets if the message was delivered or not.
+ *
+ * @param delivered sets if the sender is requesting to be notified when delivered or when
+ * notifying that the message was delivered
+ */
+ public void setDelivered(boolean delivered) {
+ this.delivered = delivered;
+ setCancelled(false);
+ }
+
+ /**
+ * When the message is a request sets if the sender of the message requests to be notified
+ * when the message is displayed.
+ * When the message is a notification sets if the message was displayed or not.
+ *
+ * @param displayed sets if the sender is requesting to be notified when displayed or when
+ * notifying that the message was displayed
+ */
+ public void setDisplayed(boolean displayed) {
+ this.displayed = displayed;
+ setCancelled(false);
+ }
+
+ /**
+ * When the message is a request sets if the sender of the message requests to be notified
+ * when the receiver of the message is offline.
+ * When the message is a notification sets if the receiver of the message was offline.
+ *
+ * @param offline sets if the sender is requesting to be notified when offline or when
+ * notifying that the receiver of the message is offline
+ */
+ public void setOffline(boolean offline) {
+ this.offline = offline;
+ setCancelled(false);
+ }
+
+ /**
+ * When the message is a notification sets if the receiver of the message cancelled
+ * composing a reply.
+ * The Cancelled event is never requested explicitly. It is requested implicitly when
+ * requesting to be notified of the Composing event.
+ *
+ * @param cancelled sets if the receiver of the message cancelled composing a reply
+ */
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ /**
+ * Sets the unique ID of the message that requested to be notified of the event.
+ * The packet id is not used when the message is a request for notifications
+ *
+ * @param packetID the message id that requested to be notified of the event.
+ */
+ public void setPacketID(String packetID) {
+ this.packetID = packetID;
+ }
+
+ /**
+ * Returns true if this MessageEvent is a request for notifications.
+ * Returns false if this MessageEvent is a notification of an event.
+ *
+ * @return true if this message is a request for notifications.
+ */
+ public boolean isMessageEventRequest() {
+ return this.packetID == null;
+ }
+
+ /**
+ * Returns the XML representation of a Message Event according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following examples:<p>
+ *
+ * Request to be notified when displayed:
+ * <pre>
+ * &lt;message
+ * to='romeo@montague.net/orchard'
+ * from='juliet@capulet.com/balcony'
+ * id='message22'&gt;
+ * &lt;x xmlns='jabber:x:event'&gt;
+ * &lt;displayed/&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ * Notification of displayed:
+ * <pre>
+ * &lt;message
+ * from='romeo@montague.net/orchard'
+ * to='juliet@capulet.com/balcony'&gt;
+ * &lt;x xmlns='jabber:x:event'&gt;
+ * &lt;displayed/&gt;
+ * &lt;id&gt;message22&lt;/id&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ // Note: Cancellation events don't specify any tag. They just send the packetID
+
+ // Add the offline tag if the sender requests to be notified of offline events or if
+ // the target is offline
+ if (isOffline())
+ buf.append("<").append(MessageEvent.OFFLINE).append("/>");
+ // Add the delivered tag if the sender requests to be notified when the message is
+ // delivered or if the target notifies that the message has been delivered
+ if (isDelivered())
+ buf.append("<").append(MessageEvent.DELIVERED).append("/>");
+ // Add the displayed tag if the sender requests to be notified when the message is
+ // displayed or if the target notifies that the message has been displayed
+ if (isDisplayed())
+ buf.append("<").append(MessageEvent.DISPLAYED).append("/>");
+ // Add the composing tag if the sender requests to be notified when the target is
+ // composing a reply or if the target notifies that he/she is composing a reply
+ if (isComposing())
+ buf.append("<").append(MessageEvent.COMPOSING).append("/>");
+ // Add the id tag only if the MessageEvent is a notification message (not a request)
+ if (getPacketID() != null)
+ buf.append("<id>").append(getPacketID()).append("</id>");
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/MultipleAddresses.java b/src/org/jivesoftware/smackx/packet/MultipleAddresses.java
new file mode 100644
index 0000000..522250a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/MultipleAddresses.java
@@ -0,0 +1,205 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Packet extension that contains the list of addresses that a packet should be sent or was sent.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleAddresses implements PacketExtension {
+
+ public static final String BCC = "bcc";
+ public static final String CC = "cc";
+ public static final String NO_REPLY = "noreply";
+ public static final String REPLY_ROOM = "replyroom";
+ public static final String REPLY_TO = "replyto";
+ public static final String TO = "to";
+
+
+ private List<Address> addresses = new ArrayList<Address>();
+
+ /**
+ * Adds a new address to which the packet is going to be sent or was sent.
+ *
+ * @param type on of the static type (BCC, CC, NO_REPLY, REPLY_ROOM, etc.)
+ * @param jid the JID address of the recipient.
+ * @param node used to specify a sub-addressable unit at a particular JID, corresponding to
+ * a Service Discovery node.
+ * @param desc used to specify human-readable information for this address.
+ * @param delivered true when the packet was already delivered to this address.
+ * @param uri used to specify an external system address, such as a sip:, sips:, or im: URI.
+ */
+ public void addAddress(String type, String jid, String node, String desc, boolean delivered,
+ String uri) {
+ // Create a new address with the specificed configuration
+ Address address = new Address(type);
+ address.setJid(jid);
+ address.setNode(node);
+ address.setDescription(desc);
+ address.setDelivered(delivered);
+ address.setUri(uri);
+ // Add the new address to the list of multiple recipients
+ addresses.add(address);
+ }
+
+ /**
+ * Indicate that the packet being sent should not be replied.
+ */
+ public void setNoReply() {
+ // Create a new address with the specificed configuration
+ Address address = new Address(NO_REPLY);
+ // Add the new address to the list of multiple recipients
+ addresses.add(address);
+ }
+
+ /**
+ * Returns the list of addresses that matches the specified type. Examples of address
+ * type are: TO, CC, BCC, etc..
+ *
+ * @param type Examples of address type are: TO, CC, BCC, etc.
+ * @return the list of addresses that matches the specified type.
+ */
+ public List<Address> getAddressesOfType(String type) {
+ List<Address> answer = new ArrayList<Address>(addresses.size());
+ for (Iterator<Address> it = addresses.iterator(); it.hasNext();) {
+ Address address = (Address) it.next();
+ if (address.getType().equals(type)) {
+ answer.add(address);
+ }
+ }
+
+ return answer;
+ }
+
+ public String getElementName() {
+ return "addresses";
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/address";
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName());
+ buf.append(" xmlns=\"").append(getNamespace()).append("\">");
+ // Loop through all the addresses and append them to the string buffer
+ for (Iterator<Address> i = addresses.iterator(); i.hasNext();) {
+ Address address = (Address) i.next();
+ buf.append(address.toXML());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ public static class Address {
+
+ private String type;
+ private String jid;
+ private String node;
+ private String description;
+ private boolean delivered;
+ private String uri;
+
+ private Address(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getJid() {
+ return jid;
+ }
+
+ private void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ public String getNode() {
+ return node;
+ }
+
+ private void setNode(String node) {
+ this.node = node;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ private void setDescription(String description) {
+ this.description = description;
+ }
+
+ public boolean isDelivered() {
+ return delivered;
+ }
+
+ private void setDelivered(boolean delivered) {
+ this.delivered = delivered;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ private void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ private String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<address type=\"");
+ // Append the address type (e.g. TO/CC/BCC)
+ buf.append(type).append("\"");
+ if (jid != null) {
+ buf.append(" jid=\"");
+ buf.append(jid).append("\"");
+ }
+ if (node != null) {
+ buf.append(" node=\"");
+ buf.append(node).append("\"");
+ }
+ if (description != null && description.trim().length() > 0) {
+ buf.append(" desc=\"");
+ buf.append(description).append("\"");
+ }
+ if (delivered) {
+ buf.append(" delivered=\"true\"");
+ }
+ if (uri != null) {
+ buf.append(" uri=\"");
+ buf.append(uri).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/Nick.java b/src/org/jivesoftware/smackx/packet/Nick.java
new file mode 100644
index 0000000..9a64eaa
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/Nick.java
@@ -0,0 +1,112 @@
+/**
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A minimalistic implementation of a {@link PacketExtension} for nicknames.
+ *
+ * @author Guus der Kinderen, guus.der.kinderen@gmail.com
+ * @see <a href="http://xmpp.org/extensions/xep-0172.html">XEP-0172: User Nickname</a>
+ */
+public class Nick implements PacketExtension {
+
+ public static final String NAMESPACE = "http://jabber.org/protocol/nick";
+
+ public static final String ELEMENT_NAME = "nick";
+
+ private String name = null;
+
+ public Nick(String name) {
+ this.name = name;
+ }
+
+ /**
+ * The value of this nickname
+ *
+ * @return the nickname
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of this nickname
+ *
+ * @param name
+ * the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#getElementName()
+ */
+ public String getElementName() {
+ return ELEMENT_NAME;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace()
+ */
+ public String getNamespace() {
+ return NAMESPACE;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.PacketExtension#toXML()
+ */
+ public String toXML() {
+ final StringBuilder buf = new StringBuilder();
+
+ buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(
+ NAMESPACE).append("\">");
+ buf.append(getName());
+ buf.append("</").append(ELEMENT_NAME).append('>');
+
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ public PacketExtension parseExtension(XmlPullParser parser)
+ throws Exception {
+
+ parser.next();
+ final String name = parser.getText();
+
+ // Advance to end of extension.
+ while(parser.getEventType() != XmlPullParser.END_TAG) {
+ parser.next();
+ }
+
+ return new Nick(name);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/src/org/jivesoftware/smackx/packet/OfflineMessageInfo.java
new file mode 100644
index 0000000..5f9954d
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/OfflineMessageInfo.java
@@ -0,0 +1,128 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * OfflineMessageInfo is an extension included in the retrieved offline messages requested by
+ * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp
+ * that uniquely identifies the offline message. This stamp may be used for deleting the offline
+ * message. The stamp may be of the form UTC timestamps but it is not required to have that format.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageInfo implements PacketExtension {
+
+ private String node = null;
+
+ /**
+ * Returns the XML element name of the extension sub-packet root element.
+ * Always returns "offline"
+ *
+ * @return the XML element name of the packet extension.
+ */
+ public String getElementName() {
+ return "offline";
+ }
+
+ /**
+ * Returns the XML namespace of the extension sub-packet root element.
+ * According the specification the namespace is always "http://jabber.org/protocol/offline"
+ *
+ * @return the XML namespace of the packet extension.
+ */
+ public String getNamespace() {
+ return "http://jabber.org/protocol/offline";
+ }
+
+ /**
+ * Returns the stamp that uniquely identifies the offline message. This stamp may
+ * be used for deleting the offline message. The stamp may be of the form UTC timestamps
+ * but it is not required to have that format.
+ *
+ * @return the stamp that uniquely identifies the offline message.
+ */
+ public String getNode() {
+ return node;
+ }
+
+ /**
+ * Sets the stamp that uniquely identifies the offline message. This stamp may
+ * be used for deleting the offline message. The stamp may be of the form UTC timestamps
+ * but it is not required to have that format.
+ *
+ * @param node the stamp that uniquely identifies the offline message.
+ */
+ public void setNode(String node) {
+ this.node = node;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ if (getNode() != null)
+ buf.append("<item node=\"").append(getNode()).append("\"/>");
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ public static class Provider implements PacketExtensionProvider {
+
+ /**
+ * Creates a new Provider.
+ * ProviderManager requires that every PacketExtensionProvider has a public,
+ * no-argument constructor
+ */
+ public Provider() {
+ }
+
+ /**
+ * Parses a OfflineMessageInfo packet (extension sub-packet).
+ *
+ * @param parser the XML parser, positioned at the starting element of the extension.
+ * @return a PacketExtension.
+ * @throws Exception if a parsing error occurs.
+ */
+ public PacketExtension parseExtension(XmlPullParser parser)
+ throws Exception {
+ OfflineMessageInfo info = new OfflineMessageInfo();
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("item"))
+ info.setNode(parser.getAttributeValue("", "node"));
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("offline")) {
+ done = true;
+ }
+ }
+ }
+
+ return info;
+ }
+
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/src/org/jivesoftware/smackx/packet/OfflineMessageRequest.java
new file mode 100644
index 0000000..1d9d096
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/OfflineMessageRequest.java
@@ -0,0 +1,237 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a request to get some or all the offline messages of a user. This class can also
+ * be used for deleting some or all the offline messages of a user.
+ *
+ * @author Gaston Dombiak
+ */
+public class OfflineMessageRequest extends IQ {
+
+ private List<Item> items = new ArrayList<Item>();
+ private boolean purge = false;
+ private boolean fetch = false;
+
+ /**
+ * Returns an Iterator for item childs that holds information about offline messages to
+ * view or delete.
+ *
+ * @return an Iterator for item childs that holds information about offline messages to
+ * view or delete.
+ */
+ public Iterator<Item> getItems() {
+ synchronized (items) {
+ return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator();
+ }
+ }
+
+ /**
+ * Adds an item child that holds information about offline messages to view or delete.
+ *
+ * @param item the item child that holds information about offline messages to view or delete.
+ */
+ public void addItem(Item item) {
+ synchronized (items) {
+ items.add(item);
+ }
+ }
+
+ /**
+ * Returns true if all the offline messages of the user should be deleted.
+ *
+ * @return true if all the offline messages of the user should be deleted.
+ */
+ public boolean isPurge() {
+ return purge;
+ }
+
+ /**
+ * Sets if all the offline messages of the user should be deleted.
+ *
+ * @param purge true if all the offline messages of the user should be deleted.
+ */
+ public void setPurge(boolean purge) {
+ this.purge = purge;
+ }
+
+ /**
+ * Returns true if all the offline messages of the user should be retrieved.
+ *
+ * @return true if all the offline messages of the user should be retrieved.
+ */
+ public boolean isFetch() {
+ return fetch;
+ }
+
+ /**
+ * Sets if all the offline messages of the user should be retrieved.
+ *
+ * @param fetch true if all the offline messages of the user should be retrieved.
+ */
+ public void setFetch(boolean fetch) {
+ this.fetch = fetch;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<offline xmlns=\"http://jabber.org/protocol/offline\">");
+ synchronized (items) {
+ for (int i = 0; i < items.size(); i++) {
+ Item item = items.get(i);
+ buf.append(item.toXML());
+ }
+ }
+ if (purge) {
+ buf.append("<purge/>");
+ }
+ if (fetch) {
+ buf.append("<fetch/>");
+ }
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</offline>");
+ return buf.toString();
+ }
+
+ /**
+ * Item child that holds information about offline messages to view or delete.
+ *
+ * @author Gaston Dombiak
+ */
+ public static class Item {
+ private String action;
+ private String jid;
+ private String node;
+
+ /**
+ * Creates a new item child.
+ *
+ * @param node the actor's affiliation to the room
+ */
+ public Item(String node) {
+ this.node = node;
+ }
+
+ public String getNode() {
+ return node;
+ }
+
+ /**
+ * Returns "view" or "remove" that indicate if the server should return the specified
+ * offline message or delete it.
+ *
+ * @return "view" or "remove" that indicate if the server should return the specified
+ * offline message or delete it.
+ */
+ public String getAction() {
+ return action;
+ }
+
+ /**
+ * Sets if the server should return the specified offline message or delete it. Possible
+ * values are "view" or "remove".
+ *
+ * @param action if the server should return the specified offline message or delete it.
+ */
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ public String getJid() {
+ return jid;
+ }
+
+ public void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<item");
+ if (getAction() != null) {
+ buf.append(" action=\"").append(getAction()).append("\"");
+ }
+ if (getJid() != null) {
+ buf.append(" jid=\"").append(getJid()).append("\"");
+ }
+ if (getNode() != null) {
+ buf.append(" node=\"").append(getNode()).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+ }
+
+ public static class Provider implements IQProvider {
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ OfflineMessageRequest request = new OfflineMessageRequest();
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("item")) {
+ request.addItem(parseItem(parser));
+ }
+ else if (parser.getName().equals("purge")) {
+ request.setPurge(true);
+ }
+ else if (parser.getName().equals("fetch")) {
+ request.setFetch(true);
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("offline")) {
+ done = true;
+ }
+ }
+ }
+
+ return request;
+ }
+
+ private Item parseItem(XmlPullParser parser) throws Exception {
+ boolean done = false;
+ Item item = new Item(parser.getAttributeValue("", "node"));
+ item.setAction(parser.getAttributeValue("", "action"));
+ item.setJid(parser.getAttributeValue("", "jid"));
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("item")) {
+ done = true;
+ }
+ }
+ }
+ return item;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/PEPEvent.java b/src/org/jivesoftware/smackx/packet/PEPEvent.java
new file mode 100644
index 0000000..48f1de2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/PEPEvent.java
@@ -0,0 +1,105 @@
+/**
+ * $RCSfile: PEPEvent.java,v $
+ * $Revision: 1.1 $
+ * $Date: 2007/11/03 00:14:32 $
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents XMPP Personal Event Protocol packets.<p>
+ *
+ * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client
+ * to subscribed clients (See XEP-163).
+ *
+ * @author Jeff Williams
+ */
+public class PEPEvent implements PacketExtension {
+
+ PEPItem item;
+
+ /**
+ * Creates a new empty roster exchange package.
+ *
+ */
+ public PEPEvent() {
+ super();
+ }
+
+ /**
+ * Creates a new empty roster exchange package.
+ *
+ */
+ public PEPEvent(PEPItem item) {
+ super();
+
+ this.item = item;
+ }
+
+ public void addPEPItem(PEPItem item) {
+ this.item = item;
+ }
+
+ /**
+ * 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 "event";
+ }
+
+ /**
+ * 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 "http://jabber.org/protocol/pubsub";
+ }
+
+ /**
+ * Returns the XML representation of a Personal Event Publish according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following example:
+ * <pre>
+ * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;subject&gt;Any subject you want&lt;/subject&gt;
+ * &lt;body&gt;This message contains roster items.&lt;/body&gt;
+ * &lt;x xmlns="jabber:x:roster"&gt;
+ * &lt;item jid="gato1@gato.home"/&gt;
+ * &lt;item jid="gato2@gato.home"/&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">");
+ buf.append(item.toXML());
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/PEPItem.java b/src/org/jivesoftware/smackx/packet/PEPItem.java
new file mode 100644
index 0000000..c3ff6f4
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/PEPItem.java
@@ -0,0 +1,92 @@
+/**
+ * $RCSfile: PEPItem.java,v $
+ * $Revision: 1.2 $
+ * $Date: 2007/11/06 02:05:09 $
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents XMPP Personal Event Protocol packets.<p>
+ *
+ * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client
+ * to subscribed clients (See XEP-163).
+ *
+ * @author Jeff Williams
+ */
+public abstract class PEPItem implements PacketExtension {
+
+ String id;
+ abstract String getNode();
+ abstract String getItemDetailsXML();
+
+ /**
+ * Creates a new PEPItem.
+ *
+ */
+ public PEPItem(String id) {
+ super();
+ this.id = id;
+ }
+
+ /**
+ * 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 "item";
+ }
+
+ /**
+ * Returns the XML namespace of the extension sub-packet root element.
+ *
+ * @return the XML namespace of the packet extension.
+ */
+ public String getNamespace() {
+ return "http://jabber.org/protocol/pubsub";
+ }
+
+ /**
+ * Returns the XML representation of a Personal Event Publish according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following example:
+ * <pre>
+ * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;subject&gt;Any subject you want&lt;/subject&gt;
+ * &lt;body&gt;This message contains roster items.&lt;/body&gt;
+ * &lt;x xmlns="jabber:x:roster"&gt;
+ * &lt;item jid="gato1@gato.home"/&gt;
+ * &lt;item jid="gato2@gato.home"/&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" id=\"").append(id).append("\">");
+ buf.append(getItemDetailsXML());
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/PEPPubSub.java b/src/org/jivesoftware/smackx/packet/PEPPubSub.java
new file mode 100644
index 0000000..420ce61
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/PEPPubSub.java
@@ -0,0 +1,95 @@
+/**
+ * $RCSfile: PEPPubSub.java,v $
+ * $Revision: 1.2 $
+ * $Date: 2007/11/03 04:46:52 $
+ *
+ * Copyright 2003-2007 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;
+
+/**
+ * Represents XMPP PEP/XEP-163 pubsub packets.<p>
+ *
+ * The 'http://jabber.org/protocol/pubsub' namespace is used to publish personal events items from one client
+ * to subscribed clients (See XEP-163).
+ *
+ * @author Jeff Williams
+ */
+public class PEPPubSub extends IQ {
+
+ PEPItem item;
+
+ /**
+ * Creates a new PubSub.
+ *
+ */
+ public PEPPubSub(PEPItem item) {
+ super();
+
+ this.item = item;
+ }
+
+ /**
+ * 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 "pubsub";
+ }
+
+ /**
+ * 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 "http://jabber.org/protocol/pubsub";
+ }
+
+ /**
+ * Returns the XML representation of a Personal Event Publish according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following example:
+ * <pre>
+ * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;subject&gt;Any subject you want&lt;/subject&gt;
+ * &lt;body&gt;This message contains roster items.&lt;/body&gt;
+ * &lt;x xmlns="jabber:x:roster"&gt;
+ * &lt;item jid="gato1@gato.home"/&gt;
+ * &lt;item jid="gato2@gato.home"/&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">");
+ buf.append("<publish node=\"").append(item.getNode()).append("\">");
+ buf.append(item.toXML());
+ buf.append("</publish>");
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/PrivateData.java b/src/org/jivesoftware/smackx/packet/PrivateData.java
new file mode 100644
index 0000000..3ddb7d5
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/PrivateData.java
@@ -0,0 +1,52 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+/**
+ * Interface to represent private data. Each private data chunk is an XML sub-document
+ * with a root element name and namespace.
+ *
+ * @see org.jivesoftware.smackx.PrivateDataManager
+ * @author Matt Tucker
+ */
+public interface PrivateData {
+
+ /**
+ * Returns the root element name.
+ *
+ * @return the element name.
+ */
+ public String getElementName();
+
+ /**
+ * Returns the root element XML namespace.
+ *
+ * @return the namespace.
+ */
+ public String getNamespace();
+
+ /**
+ * Returns the XML reppresentation of the PrivateData.
+ *
+ * @return the private data as XML.
+ */
+ public String toXML();
+}
diff --git a/src/org/jivesoftware/smackx/packet/RosterExchange.java b/src/org/jivesoftware/smackx/packet/RosterExchange.java
new file mode 100644
index 0000000..ad59146
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/RosterExchange.java
@@ -0,0 +1,181 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.Roster;
+import org.jivesoftware.smack.RosterEntry;
+import org.jivesoftware.smack.RosterGroup;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.RemoteRosterEntry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents XMPP Roster Item Exchange packets.<p>
+ *
+ * The 'jabber:x:roster' namespace (which is not to be confused with the 'jabber:iq:roster'
+ * namespace) is used to send roster items from one client to another. A roster item is sent by
+ * adding to the &lt;message/&gt; element an &lt;x/&gt; child scoped by the 'jabber:x:roster' namespace. This
+ * &lt;x/&gt; element may contain one or more &lt;item/&gt; children (one for each roster item to be sent).<p>
+ *
+ * Each &lt;item/&gt; element may possess the following attributes:<p>
+ *
+ * &lt;jid/&gt; -- The id of the contact being sent. This attribute is required.<br>
+ * &lt;name/&gt; -- A natural-language nickname for the contact. This attribute is optional.<p>
+ *
+ * Each &lt;item/&gt; element may also contain one or more &lt;group/&gt; children specifying the
+ * natural-language name of a user-specified group, for the purpose of categorizing this contact
+ * into one or more roster groups.
+ *
+ * @author Gaston Dombiak
+ */
+public class RosterExchange implements PacketExtension {
+
+ private List<RemoteRosterEntry> remoteRosterEntries = new ArrayList<RemoteRosterEntry>();
+
+ /**
+ * 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 (RosterEntry rosterEntry : roster.getEntries()) {
+ this.addRosterEntry(rosterEntry);
+ }
+ }
+
+ /**
+ * 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
+ List<String> groupNamesList = new ArrayList<String>();
+ String[] groupNames;
+ for (RosterGroup group : rosterEntry.getGroups()) {
+ groupNamesList.add(group.getName());
+ }
+ groupNames = 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<RemoteRosterEntry> getRosterEntries() {
+ synchronized (remoteRosterEntries) {
+ List<RemoteRosterEntry> entries = Collections.unmodifiableList(new ArrayList<RemoteRosterEntry>(remoteRosterEntries));
+ return entries.iterator();
+ }
+ }
+
+ /**
+ * Returns a count of the entries in the roster exchange.
+ *
+ * @return the number of entries in the roster exchange.
+ */
+ public int getEntryCount() {
+ return remoteRosterEntries.size();
+ }
+
+ /**
+ * Returns the XML representation of a Roster Item Exchange according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following example:
+ * <pre>
+ * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;subject&gt;Any subject you want&lt;/subject&gt;
+ * &lt;body&gt;This message contains roster items.&lt;/body&gt;
+ * &lt;x xmlns="jabber:x:roster"&gt;
+ * &lt;item jid="gato1@gato.home"/&gt;
+ * &lt;item jid="gato2@gato.home"/&gt;
+ * &lt;/x&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ // Loop through all roster entries and append them to the string buffer
+ for (Iterator<RemoteRosterEntry> i = getRosterEntries(); i.hasNext();) {
+ RemoteRosterEntry remoteRosterEntry = i.next();
+ buf.append(remoteRosterEntry.toXML());
+ }
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/packet/SharedGroupsInfo.java b/src/org/jivesoftware/smackx/packet/SharedGroupsInfo.java
new file mode 100644
index 0000000..59bd98e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/SharedGroupsInfo.java
@@ -0,0 +1,92 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 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.
+ */
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * IQ packet used for discovering the user's shared groups and for getting the answer back
+ * from the server.<p>
+ *
+ * Important note: This functionality is not part of the XMPP spec and it will only work
+ * with Wildfire.
+ *
+ * @author Gaston Dombiak
+ */
+public class SharedGroupsInfo extends IQ {
+
+ private List<String> groups = new ArrayList<String>();
+
+ /**
+ * Returns a collection with the shared group names returned from the server.
+ *
+ * @return collection with the shared group names returned from the server.
+ */
+ public List<String> getGroups() {
+ return groups;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<sharedgroup xmlns=\"http://www.jivesoftware.org/protocol/sharedgroup\">");
+ for (Iterator<String> it=groups.iterator(); it.hasNext();) {
+ buf.append("<group>").append(it.next()).append("</group>");
+ }
+ buf.append("</sharedgroup>");
+ return buf.toString();
+ }
+
+ /**
+ * Internal Search service Provider.
+ */
+ public static class Provider implements IQProvider {
+
+ /**
+ * Provider Constructor.
+ */
+ public Provider() {
+ super();
+ }
+
+ public IQ parseIQ(XmlPullParser parser) throws Exception {
+ SharedGroupsInfo groupsInfo = new SharedGroupsInfo();
+
+ boolean done = false;
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals("group")) {
+ groupsInfo.getGroups().add(parser.nextText());
+ }
+ else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals("sharedgroup")) {
+ done = true;
+ }
+ }
+ }
+ return groupsInfo;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/StreamInitiation.java b/src/org/jivesoftware/smackx/packet/StreamInitiation.java
new file mode 100644
index 0000000..511a02c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/StreamInitiation.java
@@ -0,0 +1,419 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2006 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.Date;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * The process by which two entities initiate a stream.
+ *
+ * @author Alexander Wenckus
+ */
+public class StreamInitiation extends IQ {
+
+ private String id;
+
+ private String mimeType;
+
+ private File file;
+
+ private Feature featureNegotiation;
+
+ /**
+ * The "id" attribute is an opaque identifier. This attribute MUST be
+ * present on type='set', and MUST be a valid string. This SHOULD NOT be
+ * sent back on type='result', since the <iq/> "id" attribute provides the
+ * only context needed. This value is generated by the Sender, and the same
+ * value MUST be used throughout a session when talking to the Receiver.
+ *
+ * @param id The "id" attribute.
+ */
+ public void setSesssionID(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * Uniquely identifies a stream initiation to the recipient.
+ *
+ * @return The "id" attribute.
+ * @see #setSesssionID(String)
+ */
+ public String getSessionID() {
+ return id;
+ }
+
+ /**
+ * The "mime-type" attribute identifies the MIME-type for the data across
+ * the stream. This attribute MUST be a valid MIME-type as registered with
+ * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as
+ * listed at <http://www.iana.org/assignments/media-types>). During
+ * negotiation, this attribute SHOULD be present, and is otherwise not
+ * required. If not included during negotiation, its value is assumed to be
+ * "binary/octect-stream".
+ *
+ * @param mimeType The valid mime-type.
+ */
+ public void setMimeType(final String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ /**
+ * Identifies the type of file that is desired to be transfered.
+ *
+ * @return The mime-type.
+ * @see #setMimeType(String)
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * Sets the file which contains the information pertaining to the file to be
+ * transfered.
+ *
+ * @param file The file identified by the stream initiator to be sent.
+ */
+ public void setFile(final File file) {
+ this.file = file;
+ }
+
+ /**
+ * Returns the file containing the information about the request.
+ *
+ * @return Returns the file containing the information about the request.
+ */
+ public File getFile() {
+ return file;
+ }
+
+ /**
+ * Sets the data form which contains the valid methods of stream neotiation
+ * and transfer.
+ *
+ * @param form The dataform containing the methods.
+ */
+ public void setFeatureNegotiationForm(final DataForm form) {
+ this.featureNegotiation = new Feature(form);
+ }
+
+ /**
+ * Returns the data form which contains the valid methods of stream
+ * neotiation and transfer.
+ *
+ * @return Returns the data form which contains the valid methods of stream
+ * neotiation and transfer.
+ */
+ public DataForm getFeatureNegotiationForm() {
+ return featureNegotiation.getData();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.jivesoftware.smack.packet.IQ#getChildElementXML()
+ */
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ if (this.getType().equals(IQ.Type.SET)) {
+ buf.append("<si xmlns=\"http://jabber.org/protocol/si\" ");
+ if (getSessionID() != null) {
+ buf.append("id=\"").append(getSessionID()).append("\" ");
+ }
+ if (getMimeType() != null) {
+ buf.append("mime-type=\"").append(getMimeType()).append("\" ");
+ }
+ buf
+ .append("profile=\"http://jabber.org/protocol/si/profile/file-transfer\">");
+
+ // Add the file section if there is one.
+ String fileXML = file.toXML();
+ if (fileXML != null) {
+ buf.append(fileXML);
+ }
+ }
+ else if (this.getType().equals(IQ.Type.RESULT)) {
+ buf.append("<si xmlns=\"http://jabber.org/protocol/si\">");
+ }
+ else {
+ throw new IllegalArgumentException("IQ Type not understood");
+ }
+ if (featureNegotiation != null) {
+ buf.append(featureNegotiation.toXML());
+ }
+ buf.append("</si>");
+ return buf.toString();
+ }
+
+ /**
+ * <ul>
+ * <li>size: The size, in bytes, of the data to be sent.</li>
+ * <li>name: The name of the file that the Sender wishes to send.</li>
+ * <li>date: The last modification time of the file. This is specified
+ * using the DateTime profile as described in Jabber Date and Time Profiles.</li>
+ * <li>hash: The MD5 sum of the file contents.</li>
+ * </ul>
+ * <p/>
+ * <p/>
+ * &lt;desc&gt; is used to provide a sender-generated description of the
+ * file so the receiver can better understand what is being sent. It MUST
+ * NOT be sent in the result.
+ * <p/>
+ * <p/>
+ * When &lt;range&gt; is sent in the offer, it should have no attributes.
+ * This signifies that the sender can do ranged transfers. When a Stream
+ * Initiation result is sent with the <range> element, it uses these
+ * attributes:
+ * <p/>
+ * <ul>
+ * <li>offset: Specifies the position, in bytes, to start transferring the
+ * file data from. This defaults to zero (0) if not specified.</li>
+ * <li>length - Specifies the number of bytes to retrieve starting at
+ * offset. This defaults to the length of the file from offset to the end.</li>
+ * </ul>
+ * <p/>
+ * <p/>
+ * Both attributes are OPTIONAL on the &lt;range&gt; element. Sending no
+ * attributes is synonymous with not sending the &lt;range&gt; element. When
+ * no &lt;range&gt; element is sent in the Stream Initiation result, the
+ * Sender MUST send the complete file starting at offset 0. More generally,
+ * data is sent over the stream byte for byte starting at the offset
+ * position for the length specified.
+ *
+ * @author Alexander Wenckus
+ */
+ public static class File implements PacketExtension {
+
+ private final String name;
+
+ private final long size;
+
+ private String hash;
+
+ private Date date;
+
+ private String desc;
+
+ private boolean isRanged;
+
+ /**
+ * Constructor providing the name of the file and its size.
+ *
+ * @param name The name of the file.
+ * @param size The size of the file in bytes.
+ */
+ public File(final String name, final long size) {
+ if (name == null) {
+ throw new NullPointerException("name cannot be null");
+ }
+
+ this.name = name;
+ this.size = size;
+ }
+
+ /**
+ * Returns the file's name.
+ *
+ * @return Returns the file's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the file's size.
+ *
+ * @return Returns the file's size.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Sets the MD5 sum of the file's contents
+ *
+ * @param hash The MD5 sum of the file's contents.
+ */
+ public void setHash(final String hash) {
+ this.hash = hash;
+ }
+
+ /**
+ * Returns the MD5 sum of the file's contents
+ *
+ * @return Returns the MD5 sum of the file's contents
+ */
+ public String getHash() {
+ return hash;
+ }
+
+ /**
+ * Sets the date that the file was last modified.
+ *
+ * @param date The date that the file was last modified.
+ */
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ /**
+ * Returns the date that the file was last modified.
+ *
+ * @return Returns the date that the file was last modified.
+ */
+ public Date getDate() {
+ return date;
+ }
+
+ /**
+ * Sets the description of the file.
+ *
+ * @param desc The description of the file so that the file reciever can
+ * know what file it is.
+ */
+ public void setDesc(final String desc) {
+ this.desc = desc;
+ }
+
+ /**
+ * Returns the description of the file.
+ *
+ * @return Returns the description of the file.
+ */
+ public String getDesc() {
+ return desc;
+ }
+
+ /**
+ * True if a range can be provided and false if it cannot.
+ *
+ * @param isRanged True if a range can be provided and false if it cannot.
+ */
+ public void setRanged(final boolean isRanged) {
+ this.isRanged = isRanged;
+ }
+
+ /**
+ * Returns whether or not the initiator can support a range for the file
+ * tranfer.
+ *
+ * @return Returns whether or not the initiator can support a range for
+ * the file tranfer.
+ */
+ public boolean isRanged() {
+ return isRanged;
+ }
+
+ public String getElementName() {
+ return "file";
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/si/profile/file-transfer";
+ }
+
+ public String toXML() {
+ StringBuilder buffer = new StringBuilder();
+
+ buffer.append("<").append(getElementName()).append(" xmlns=\"")
+ .append(getNamespace()).append("\" ");
+
+ if (getName() != null) {
+ buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" ");
+ }
+
+ if (getSize() > 0) {
+ buffer.append("size=\"").append(getSize()).append("\" ");
+ }
+
+ if (getDate() != null) {
+ buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" ");
+ }
+
+ if (getHash() != null) {
+ buffer.append("hash=\"").append(getHash()).append("\" ");
+ }
+
+ if ((desc != null && desc.length() > 0) || isRanged) {
+ buffer.append(">");
+ if (getDesc() != null && desc.length() > 0) {
+ buffer.append("<desc>").append(StringUtils.escapeForXML(getDesc())).append("</desc>");
+ }
+ if (isRanged()) {
+ buffer.append("<range/>");
+ }
+ buffer.append("</").append(getElementName()).append(">");
+ }
+ else {
+ buffer.append("/>");
+ }
+ return buffer.toString();
+ }
+ }
+
+ /**
+ * The feature negotiation portion of the StreamInitiation packet.
+ *
+ * @author Alexander Wenckus
+ *
+ */
+ public class Feature implements PacketExtension {
+
+ private final DataForm data;
+
+ /**
+ * The dataform can be provided as part of the constructor.
+ *
+ * @param data The dataform.
+ */
+ public Feature(final DataForm data) {
+ this.data = data;
+ }
+
+ /**
+ * Returns the dataform associated with the feature negotiation.
+ *
+ * @return Returns the dataform associated with the feature negotiation.
+ */
+ public DataForm getData() {
+ return data;
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/feature-neg";
+ }
+
+ public String getElementName() {
+ return "feature";
+ }
+
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf
+ .append("<feature xmlns=\"http://jabber.org/protocol/feature-neg\">");
+ buf.append(data.toXML());
+ buf.append("</feature>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/Time.java b/src/org/jivesoftware/smackx/packet/Time.java
new file mode 100644
index 0000000..5294e77
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/Time.java
@@ -0,0 +1,198 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * A Time IQ packet, which is used by XMPP clients to exchange their respective local
+ * times. Clients that wish to fully support the entitity time protocol should register
+ * a PacketListener for incoming time requests that then respond with the local time.
+ * This class can be used to request the time from other clients, such as in the
+ * following code snippet:
+ *
+ * <pre>
+ * // Request the time from a remote user.
+ * Time timeRequest = new Time();
+ * timeRequest.setType(IQ.Type.GET);
+ * timeRequest.setTo(someUser@example.com/resource);
+ *
+ * // Create a packet collector to listen for a response.
+ * PacketCollector collector = con.createPacketCollector(
+ * new PacketIDFilter(timeRequest.getPacketID()));
+ *
+ * con.sendPacket(timeRequest);
+ *
+ * // Wait up to 5 seconds for a result.
+ * IQ result = (IQ)collector.nextResult(5000);
+ * if (result != null && result.getType() == IQ.Type.RESULT) {
+ * Time timeResult = (Time)result;
+ * // Do something with result...
+ * }</pre><p>
+ *
+ * Warning: this is an non-standard protocol documented by
+ * <a href="http://www.xmpp.org/extensions/xep-0090.html">XEP-0090</a>. Because this is a
+ * non-standard protocol, it is subject to change.
+ *
+ * @author Matt Tucker
+ */
+public class Time extends IQ {
+
+ private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ private static DateFormat displayFormat = DateFormat.getDateTimeInstance();
+
+ private String utc = null;
+ private String tz = null;
+ private String display = null;
+
+ /**
+ * Creates a new Time instance with empty values for all fields.
+ */
+ public Time() {
+
+ }
+
+ /**
+ * Creates a new Time instance using the specified calendar instance as
+ * the time value to send.
+ *
+ * @param cal the time value.
+ */
+ public Time(Calendar cal) {
+ TimeZone timeZone = cal.getTimeZone();
+ tz = cal.getTimeZone().getID();
+ display = displayFormat.format(cal.getTime());
+ // Convert local time to the UTC time.
+ utc = utcFormat.format(new Date(
+ cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis())));
+ }
+
+ /**
+ * Returns the local time or <tt>null</tt> if the time hasn't been set.
+ *
+ * @return the lcocal time.
+ */
+ public Date getTime() {
+ if (utc == null) {
+ return null;
+ }
+ Date date = null;
+ try {
+ Calendar cal = Calendar.getInstance();
+ // Convert the UTC time to local time.
+ cal.setTime(new Date(utcFormat.parse(utc).getTime() +
+ cal.getTimeZone().getOffset(cal.getTimeInMillis())));
+ date = cal.getTime();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ return date;
+ }
+
+ /**
+ * Sets the time using the local time.
+ *
+ * @param time the current local time.
+ */
+ public void setTime(Date time) {
+ // Convert local time to UTC time.
+ utc = utcFormat.format(new Date(
+ time.getTime() - TimeZone.getDefault().getOffset(time.getTime())));
+ }
+
+ /**
+ * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss.
+ *
+ * @return the time as a UTC formatted String.
+ */
+ public String getUtc() {
+ return utc;
+ }
+
+ /**
+ * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss.
+ *
+ * @param utc the time using a formatted String.
+ */
+ public void setUtc(String utc) {
+ this.utc = utc;
+
+ }
+
+ /**
+ * Returns the time zone.
+ *
+ * @return the time zone.
+ */
+ public String getTz() {
+ return tz;
+ }
+
+ /**
+ * Sets the time zone.
+ *
+ * @param tz the time zone.
+ */
+ public void setTz(String tz) {
+ this.tz = tz;
+ }
+
+ /**
+ * Returns the local (non-utc) time in human-friendly format.
+ *
+ * @return the local time in human-friendly format.
+ */
+ public String getDisplay() {
+ return display;
+ }
+
+ /**
+ * Sets the local time in human-friendly format.
+ *
+ * @param display the local time in human-friendly format.
+ */
+ public void setDisplay(String display) {
+ this.display = display;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"jabber:iq:time\">");
+ if (utc != null) {
+ buf.append("<utc>").append(utc).append("</utc>");
+ }
+ if (tz != null) {
+ buf.append("<tz>").append(tz).append("</tz>");
+ }
+ if (display != null) {
+ buf.append("<display>").append(display).append("</display>");
+ }
+ buf.append("</query>");
+ return buf.toString();
+ }
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/packet/VCard.java b/src/org/jivesoftware/smackx/packet/VCard.java
new file mode 100644
index 0000000..9766db8
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/VCard.java
@@ -0,0 +1,883 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 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.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.XMPPError;
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * A VCard class for use with the
+ * <a href="http://www.jivesoftware.org/smack/" target="_blank">SMACK jabber library</a>.<p>
+ * <p/>
+ * You should refer to the
+ * <a href="http://www.jabber.org/jeps/jep-0054.html" target="_blank">JEP-54 documentation</a>.<p>
+ * <p/>
+ * Please note that this class is incomplete but it does provide the most commonly found
+ * information in vCards. Also remember that VCard transfer is not a standard, and the protocol
+ * may change or be replaced.<p>
+ * <p/>
+ * <b>Usage:</b>
+ * <pre>
+ * <p/>
+ * // To save VCard:
+ * <p/>
+ * 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");
+ * <p/>
+ * vCard.setField("TITLE", "Mr");
+ * vCard.setAddressFieldHome("STREET", "Some street");
+ * vCard.setAddressFieldWork("CTRY", "US");
+ * vCard.setPhoneWork("FAX", "3443233");
+ * <p/>
+ * vCard.save(connection);
+ * <p/>
+ * // To load VCard:
+ * <p/>
+ * VCard vCard = new VCard();
+ * vCard.load(conn); // load own VCard
+ * vCard.load(conn, "joe@foo.bar"); // load someone's VCard
+ * </pre>
+ *
+ * @author Kirill Maximov (kir@maxkir.com)
+ */
+public class VCard extends IQ {
+
+ /**
+ * Phone types:
+ * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF?
+ */
+ private Map<String, String> homePhones = new HashMap<String, String>();
+ private Map<String, String> workPhones = new HashMap<String, String>();
+
+
+ /**
+ * Address types:
+ * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?,
+ * REGION?, PCODE?, CTRY?
+ */
+ private Map<String, String> homeAddr = new HashMap<String, String>();
+ private Map<String, String> workAddr = new HashMap<String, String>();
+
+ private String firstName;
+ private String lastName;
+ private String middleName;
+
+ private String emailHome;
+ private String emailWork;
+
+ private String organization;
+ private String organizationUnit;
+
+ private String photoMimeType;
+ private String photoBinval;
+
+ /**
+ * Such as DESC ROLE GEO etc.. see JEP-0054
+ */
+ private Map<String, String> otherSimpleFields = new HashMap<String, String>();
+
+ // fields that, as they are should not be escaped before forwarding to the server
+ private Map<String, String> otherUnescapableFields = new HashMap<String, String>();
+
+ 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 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) {
+ setField(field, value, false);
+ }
+
+ /**
+ * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the
+ * value.
+ *
+ * @param value value of field
+ * @param field field to set. See {@link #getField(String)}
+ * @param isUnescapable True if the value should not be escaped, and false if it should.
+ */
+ public void setField(String field, String value, boolean isUnescapable) {
+ if (!isUnescapable) {
+ otherSimpleFields.put(field, value);
+ }
+ else {
+ otherUnescapableFields.put(field, value);
+ }
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getMiddleName() {
+ return middleName;
+ }
+
+ public void setMiddleName(String middleName) {
+ this.middleName = middleName;
+ // Update FN field
+ updateFN();
+ }
+
+ public String getNickName() {
+ return 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 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 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 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 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 workPhones.get(phoneType);
+ }
+
+ /**
+ * Set the avatar for the VCard by specifying the url to the image.
+ *
+ * @param avatarURL the url to the image(png,jpeg,gif,bmp)
+ */
+ public void setAvatar(URL avatarURL) {
+ byte[] bytes = new byte[0];
+ try {
+ bytes = getBytes(avatarURL);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ setAvatar(bytes);
+ }
+
+ /**
+ * Removes the avatar from the vCard
+ *
+ * This is done by setting the PHOTO value to the empty string as defined in XEP-0153
+ */
+ public void removeAvatar() {
+ // Remove avatar (if any)
+ photoBinval = null;
+ photoMimeType = null;
+ }
+
+ /**
+ * Specify the bytes of the JPEG for the avatar to use.
+ * If bytes is null, then the avatar will be removed.
+ * 'image/jpeg' will be used as MIME type.
+ *
+ * @param bytes the bytes of the avatar, or null to remove the avatar data
+ */
+ public void setAvatar(byte[] bytes) {
+ setAvatar(bytes, "image/jpeg");
+ }
+
+ /**
+ * Specify the bytes for the avatar to use as well as the mime type.
+ *
+ * @param bytes the bytes of the avatar.
+ * @param mimeType the mime type of the avatar.
+ */
+ public void setAvatar(byte[] bytes, String mimeType) {
+ // If bytes is null, remove the avatar
+ if (bytes == null) {
+ removeAvatar();
+ return;
+ }
+
+ // Otherwise, add to mappings.
+ String encodedImage = StringUtils.encodeBase64(bytes);
+
+ setAvatar(encodedImage, mimeType);
+ }
+
+ /**
+ * Specify the Avatar used for this vCard.
+ *
+ * @param encodedImage the Base64 encoded image as String
+ * @param mimeType the MIME type of the image
+ */
+ public void setAvatar(String encodedImage, String mimeType) {
+ photoBinval = encodedImage;
+ photoMimeType = mimeType;
+ }
+
+ /**
+ * Return the byte representation of the avatar(if one exists), otherwise returns null if
+ * no avatar could be found.
+ * <b>Example 1</b>
+ * <pre>
+ * // Load Avatar from VCard
+ * byte[] avatarBytes = vCard.getAvatar();
+ * <p/>
+ * // To create an ImageIcon for Swing applications
+ * ImageIcon icon = new ImageIcon(avatar);
+ * <p/>
+ * // To create just an image object from the bytes
+ * ByteArrayInputStream bais = new ByteArrayInputStream(avatar);
+ * try {
+ * Image image = ImageIO.read(bais);
+ * }
+ * catch (IOException e) {
+ * e.printStackTrace();
+ * }
+ * </pre>
+ *
+ * @return byte representation of avatar.
+ */
+ public byte[] getAvatar() {
+ if (photoBinval == null) {
+ return null;
+ }
+ return StringUtils.decodeBase64(photoBinval);
+ }
+
+ /**
+ * Returns the MIME Type of the avatar or null if none is set
+ *
+ * @return the MIME Type of the avatar or null
+ */
+ public String getAvatarMimeType() {
+ return photoMimeType;
+ }
+
+ /**
+ * Common code for getting the bytes of a url.
+ *
+ * @param url the url to read.
+ */
+ public static byte[] getBytes(URL url) throws IOException {
+ final String path = url.getPath();
+ final File file = new File(path);
+ if (file.exists()) {
+ return getFileBytes(file);
+ }
+
+ return null;
+ }
+
+ private static byte[] getFileBytes(File file) throws IOException {
+ BufferedInputStream bis = null;
+ try {
+ bis = new BufferedInputStream(new FileInputStream(file));
+ int bytes = (int) file.length();
+ byte[] buffer = new byte[bytes];
+ int readBytes = bis.read(buffer);
+ if (readBytes != buffer.length) {
+ throw new IOException("Entire file not read");
+ }
+ return buffer;
+ }
+ finally {
+ if (bis != null) {
+ bis.close();
+ }
+ }
+ }
+
+ /**
+ * Returns the SHA-1 Hash of the Avatar image.
+ *
+ * @return the SHA-1 Hash of the Avatar image.
+ */
+ public String getAvatarHash() {
+ byte[] bytes = getAvatar();
+ if (bytes == null) {
+ return null;
+ }
+
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA-1");
+ }
+ catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ digest.update(bytes);
+ return StringUtils.encodeHex(digest.digest());
+ }
+
+ private void updateFN() {
+ StringBuilder sb = new StringBuilder();
+ if (firstName != null) {
+ sb.append(StringUtils.escapeForXML(firstName)).append(' ');
+ }
+ if (middleName != null) {
+ sb.append(StringUtils.escapeForXML(middleName)).append(' ');
+ }
+ if (lastName != null) {
+ sb.append(StringUtils.escapeForXML(lastName));
+ }
+ setField("FN", sb.toString());
+ }
+
+ /**
+ * Save this vCard for the user connected by 'connection'. Connection should be authenticated
+ * and not anonymous.<p>
+ * <p/>
+ * NOTE: the method is asynchronous and does not wait for the returned value.
+ *
+ * @param connection the Connection to use.
+ * @throws XMPPException thrown if there was an issue setting the VCard in the server.
+ */
+ public void save(Connection connection) throws XMPPException {
+ checkAuthenticated(connection, true);
+
+ setType(IQ.Type.SET);
+ setFrom(connection.getUser());
+ PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID()));
+ connection.sendPacket(this);
+
+ Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ // Cancel the collector.
+ collector.cancel();
+ if (response == null) {
+ throw new XMPPException("No response from server on status set.");
+ }
+ if (response.getError() != null) {
+ throw new XMPPException(response.getError());
+ }
+ }
+
+ /**
+ * Load VCard information for a connected user. Connection should be authenticated
+ * and not anonymous.
+ */
+ public void load(Connection connection) throws XMPPException {
+ checkAuthenticated(connection, true);
+
+ setFrom(connection.getUser());
+ doLoad(connection, connection.getUser());
+ }
+
+ /**
+ * Load VCard information for a given user. Connection should be authenticated and not anonymous.
+ */
+ public void load(Connection connection, String user) throws XMPPException {
+ checkAuthenticated(connection, false);
+
+ setTo(user);
+ doLoad(connection, user);
+ }
+
+ private void doLoad(Connection connection, String user) throws XMPPException {
+ setType(Type.GET);
+ PacketCollector collector = connection.createPacketCollector(
+ new PacketIDFilter(getPacketID()));
+ connection.sendPacket(this);
+
+ VCard result = null;
+ try {
+ result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
+
+ if (result == null) {
+ String errorMessage = "Timeout getting VCard information";
+ throw new XMPPException(errorMessage, new XMPPError(
+ XMPPError.Condition.request_timeout, errorMessage));
+ }
+ if (result.getError() != null) {
+ throw new XMPPException(result.getError());
+ }
+ }
+ catch (ClassCastException e) {
+ System.out.println("No VCard for " + user);
+ }
+
+ copyFieldsFrom(result);
+ }
+
+ public String getChildElementXML() {
+ StringBuilder sb = new StringBuilder();
+ new VCardWriter(sb).write();
+ return sb.toString();
+ }
+
+ private void copyFieldsFrom(VCard from) {
+ Field[] fields = VCard.class.getDeclaredFields();
+ for (Field field : fields) {
+ if (field.getDeclaringClass() == VCard.class &&
+ !Modifier.isFinal(field.getModifiers())) {
+ try {
+ field.setAccessible(true);
+ field.set(this, field.get(from));
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException("This cannot happen:" + field, e);
+ }
+ }
+ }
+ }
+
+ private void checkAuthenticated(Connection connection, boolean checkForAnonymous) {
+ if (connection == null) {
+ throw new IllegalArgumentException("No connection was provided");
+ }
+ if (!connection.isAuthenticated()) {
+ throw new IllegalArgumentException("Connection is not authenticated");
+ }
+ if (checkForAnonymous && connection.isAnonymous()) {
+ throw new IllegalArgumentException("Connection cannot be anonymous");
+ }
+ }
+
+ private boolean hasContent() {
+ //noinspection OverlyComplexBooleanExpression
+ return hasNameField()
+ || hasOrganizationFields()
+ || emailHome != null
+ || emailWork != null
+ || otherSimpleFields.size() > 0
+ || otherUnescapableFields.size() > 0
+ || homeAddr.size() > 0
+ || homePhones.size() > 0
+ || workAddr.size() > 0
+ || workPhones.size() > 0
+ || photoBinval != null
+ ;
+ }
+
+ private boolean hasNameField() {
+ return firstName != null || lastName != null || middleName != null;
+ }
+
+ private boolean hasOrganizationFields() {
+ return organization != null || organizationUnit != null;
+ }
+
+ // Used in tests:
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final VCard vCard = (VCard) o;
+
+ if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) {
+ return false;
+ }
+ if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) {
+ return false;
+ }
+ if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) {
+ return false;
+ }
+ if (!homeAddr.equals(vCard.homeAddr)) {
+ return false;
+ }
+ if (!homePhones.equals(vCard.homePhones)) {
+ return false;
+ }
+ if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) {
+ return false;
+ }
+ if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) {
+ return false;
+ }
+ if (organization != null ?
+ !organization.equals(vCard.organization) : vCard.organization != null) {
+ return false;
+ }
+ if (organizationUnit != null ?
+ !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) {
+ return false;
+ }
+ if (!otherSimpleFields.equals(vCard.otherSimpleFields)) {
+ return false;
+ }
+ if (!workAddr.equals(vCard.workAddr)) {
+ return false;
+ }
+ if (photoBinval != null ? !photoBinval.equals(vCard.photoBinval) : vCard.photoBinval != null) {
+ return false;
+ }
+
+ return workPhones.equals(vCard.workPhones);
+ }
+
+ public int hashCode() {
+ int result;
+ result = homePhones.hashCode();
+ result = 29 * result + workPhones.hashCode();
+ result = 29 * result + homeAddr.hashCode();
+ result = 29 * result + workAddr.hashCode();
+ result = 29 * result + (firstName != null ? firstName.hashCode() : 0);
+ result = 29 * result + (lastName != null ? lastName.hashCode() : 0);
+ result = 29 * result + (middleName != null ? middleName.hashCode() : 0);
+ result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0);
+ result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0);
+ result = 29 * result + (organization != null ? organization.hashCode() : 0);
+ result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0);
+ result = 29 * result + otherSimpleFields.hashCode();
+ result = 29 * result + (photoBinval != null ? photoBinval.hashCode() : 0);
+ return result;
+ }
+
+ public String toString() {
+ return getChildElementXML();
+ }
+
+ //==============================================================
+
+ private class VCardWriter {
+
+ private final StringBuilder sb;
+
+ VCardWriter(StringBuilder sb) {
+ this.sb = sb;
+ }
+
+ public void write() {
+ appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() {
+ public void addTagContent() {
+ buildActualContent();
+ }
+ });
+ }
+
+ private void buildActualContent() {
+ if (hasNameField()) {
+ appendN();
+ }
+
+ appendOrganization();
+ appendGenericFields();
+ appendPhoto();
+
+ appendEmail(emailWork, "WORK");
+ appendEmail(emailHome, "HOME");
+
+ appendPhones(workPhones, "WORK");
+ appendPhones(homePhones, "HOME");
+
+ appendAddress(workAddr, "WORK");
+ appendAddress(homeAddr, "HOME");
+ }
+
+ private void appendPhoto() {
+ if (photoBinval == null)
+ return;
+
+ appendTag("PHOTO", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("BINVAL", photoBinval); // No need to escape photoBinval, as it's already Base64 encoded
+ appendTag("TYPE", StringUtils.escapeForXML(photoMimeType));
+ }
+ });
+ }
+ private void appendEmail(final String email, final String type) {
+ if (email != null) {
+ appendTag("EMAIL", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(type);
+ appendEmptyTag("INTERNET");
+ appendEmptyTag("PREF");
+ appendTag("USERID", StringUtils.escapeForXML(email));
+ }
+ });
+ }
+ }
+
+ private void appendPhones(Map<String, String> phones, final String code) {
+ Iterator<Map.Entry<String, String>> it = phones.entrySet().iterator();
+ while (it.hasNext()) {
+ final Map.Entry<String,String> entry = it.next();
+ appendTag("TEL", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(entry.getKey());
+ appendEmptyTag(code);
+ appendTag("NUMBER", StringUtils.escapeForXML(entry.getValue()));
+ }
+ });
+ }
+ }
+
+ private void appendAddress(final Map<String, String> addr, final String code) {
+ if (addr.size() > 0) {
+ appendTag("ADR", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendEmptyTag(code);
+
+ Iterator<Map.Entry<String, String>> it = addr.entrySet().iterator();
+ while (it.hasNext()) {
+ final Entry<String, String> entry = it.next();
+ appendTag(entry.getKey(), StringUtils.escapeForXML(entry.getValue()));
+ }
+ }
+ });
+ }
+ }
+
+ private void appendEmptyTag(Object tag) {
+ sb.append('<').append(tag).append("/>");
+ }
+
+ private void appendGenericFields() {
+ Iterator<Map.Entry<String, String>> it = otherSimpleFields.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, String> entry = it.next();
+ appendTag(entry.getKey().toString(),
+ StringUtils.escapeForXML(entry.getValue()));
+ }
+
+ it = otherUnescapableFields.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, String> entry = it.next();
+ appendTag(entry.getKey().toString(),entry.getValue());
+ }
+ }
+
+ private void appendOrganization() {
+ if (hasOrganizationFields()) {
+ appendTag("ORG", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("ORGNAME", StringUtils.escapeForXML(organization));
+ appendTag("ORGUNIT", StringUtils.escapeForXML(organizationUnit));
+ }
+ });
+ }
+ }
+
+ private void appendN() {
+ appendTag("N", true, new ContentBuilder() {
+ public void addTagContent() {
+ appendTag("FAMILY", StringUtils.escapeForXML(lastName));
+ appendTag("GIVEN", StringUtils.escapeForXML(firstName));
+ appendTag("MIDDLE", StringUtils.escapeForXML(middleName));
+ }
+ });
+ }
+
+ private void appendTag(String tag, String attr, String attrValue, boolean hasContent,
+ ContentBuilder builder) {
+ sb.append('<').append(tag);
+ if (attr != null) {
+ sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\'');
+ }
+
+ if (hasContent) {
+ sb.append('>');
+ builder.addTagContent();
+ sb.append("</").append(tag).append(">\n");
+ }
+ else {
+ sb.append("/>\n");
+ }
+ }
+
+ private void appendTag(String tag, boolean hasContent, ContentBuilder builder) {
+ appendTag(tag, null, null, hasContent, builder);
+ }
+
+ private void appendTag(String tag, final String tagText) {
+ if (tagText == null) return;
+ final ContentBuilder contentBuilder = new ContentBuilder() {
+ public void addTagContent() {
+ sb.append(tagText.trim());
+ }
+ };
+ appendTag(tag, true, contentBuilder);
+ }
+
+ }
+
+ //==============================================================
+
+ private interface ContentBuilder {
+
+ void addTagContent();
+ }
+
+ //==============================================================
+}
+
diff --git a/src/org/jivesoftware/smackx/packet/Version.java b/src/org/jivesoftware/smackx/packet/Version.java
new file mode 100644
index 0000000..41ee419
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/Version.java
@@ -0,0 +1,132 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+
+/**
+ * A Version IQ packet, which is used by XMPP clients to discover version information
+ * about the software running at another entity's JID.<p>
+ *
+ * An example to discover the version of the server:
+ * <pre>
+ * // Request the version from the server.
+ * Version versionRequest = new Version();
+ * timeRequest.setType(IQ.Type.GET);
+ * timeRequest.setTo("example.com");
+ *
+ * // Create a packet collector to listen for a response.
+ * PacketCollector collector = con.createPacketCollector(
+ * new PacketIDFilter(versionRequest.getPacketID()));
+ *
+ * con.sendPacket(versionRequest);
+ *
+ * // Wait up to 5 seconds for a result.
+ * IQ result = (IQ)collector.nextResult(5000);
+ * if (result != null && result.getType() == IQ.Type.RESULT) {
+ * Version versionResult = (Version)result;
+ * // Do something with result...
+ * }</pre><p>
+ *
+ * @author Gaston Dombiak
+ */
+public class Version extends IQ {
+
+ private String name;
+ private String version;
+ private String os;
+
+ /**
+ * Returns the natural-language name of the software. This property will always be
+ * present in a result.
+ *
+ * @return the natural-language name of the software.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the natural-language name of the software. This message should only be
+ * invoked when parsing the XML and setting the property to a Version instance.
+ *
+ * @param name the natural-language name of the software.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the specific version of the software. This property will always be
+ * present in a result.
+ *
+ * @return the specific version of the software.
+ */
+ public String getVersion() {
+ return version;
+ }
+
+ /**
+ * Sets the specific version of the software. This message should only be
+ * invoked when parsing the XML and setting the property to a Version instance.
+ *
+ * @param version the specific version of the software.
+ */
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ /**
+ * Returns the operating system of the queried entity. This property will always be
+ * present in a result.
+ *
+ * @return the operating system of the queried entity.
+ */
+ public String getOs() {
+ return os;
+ }
+
+ /**
+ * Sets the operating system of the queried entity. This message should only be
+ * invoked when parsing the XML and setting the property to a Version instance.
+ *
+ * @param os operating system of the queried entity.
+ */
+ public void setOs(String os) {
+ this.os = os;
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"jabber:iq:version\">");
+ if (name != null) {
+ buf.append("<name>").append(name).append("</name>");
+ }
+ if (version != null) {
+ buf.append("<version>").append(version).append("</version>");
+ }
+ if (os != null) {
+ buf.append("<os>").append(os).append("</os>");
+ }
+ buf.append("</query>");
+ return buf.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/packet/XHTMLExtension.java b/src/org/jivesoftware/smackx/packet/XHTMLExtension.java
new file mode 100644
index 0000000..ba5e676
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/XHTMLExtension.java
@@ -0,0 +1,126 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2007 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An XHTML sub-packet, which is used by XMPP clients to exchange formatted text. The XHTML
+ * extension is only a subset of XHTML 1.0.<p>
+ *
+ * The following link summarizes the requirements of XHTML IM:
+ * <a href="http://www.jabber.org/jeps/jep-0071.html#sect-id2598018">Valid tags</a>.<p>
+ *
+ * Warning: this is an non-standard protocol documented by
+ * <a href="http://www.jabber.org/jeps/jep-0071.html">JEP-71</a>. Because this is a
+ * non-standard protocol, it is subject to change.
+ *
+ * @author Gaston Dombiak
+ */
+public class XHTMLExtension implements PacketExtension {
+
+ private List<String> bodies = new ArrayList<String>();
+
+ /**
+ * Returns the XML element name of the extension sub-packet root element.
+ * Always returns "html"
+ *
+ * @return the XML element name of the packet extension.
+ */
+ public String getElementName() {
+ return "html";
+ }
+
+ /**
+ * Returns the XML namespace of the extension sub-packet root element.
+ * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im"
+ *
+ * @return the XML namespace of the packet extension.
+ */
+ public String getNamespace() {
+ return "http://jabber.org/protocol/xhtml-im";
+ }
+
+ /**
+ * Returns the XML representation of a XHTML extension according the specification.
+ *
+ * Usually the XML representation will be inside of a Message XML representation like
+ * in the following example:
+ * <pre>
+ * &lt;message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;subject&gt;Any subject you want&lt;/subject&gt;
+ * &lt;body&gt;This message contains something interesting.&lt;/body&gt;
+ * &lt;html xmlns="http://jabber.org/protocol/xhtml-im"&gt;
+ * &lt;body&gt;&lt;p style='font-size:large'&gt;This message contains something &lt;em&gt;interesting&lt;/em&gt;.&lt;/p&gt;&lt;/body&gt;
+ * &lt;/html&gt;
+ * &lt;/message&gt;
+ * </pre>
+ *
+ */
+ public String toXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
+ "\">");
+ // Loop through all the bodies and append them to the string buffer
+ for (Iterator<String> i = getBodies(); i.hasNext();) {
+ buf.append(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<String> getBodies() {
+ synchronized (bodies) {
+ return Collections.unmodifiableList(new ArrayList<String>(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/src/org/jivesoftware/smackx/packet/package.html b/src/org/jivesoftware/smackx/packet/package.html
new file mode 100644
index 0000000..490d1d7
--- /dev/null
+++ b/src/org/jivesoftware/smackx/packet/package.html
@@ -0,0 +1 @@
+<body>XML packets that are part of the XMPP extension protocols.</body> \ No newline at end of file