aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smack/packet/Packet.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smack/packet/Packet.java')
-rw-r--r--src/org/jivesoftware/smack/packet/Packet.java509
1 files changed, 509 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smack/packet/Packet.java b/src/org/jivesoftware/smack/packet/Packet.java
new file mode 100644
index 0000000..3f1185e
--- /dev/null
+++ b/src/org/jivesoftware/smack/packet/Packet.java
@@ -0,0 +1,509 @@
+/**
+ * $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.smack.packet;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.jivesoftware.smack.util.StringUtils;
+
+/**
+ * Base class for XMPP packets. Every packet has a unique ID (which is automatically
+ * generated, but can be overriden). Optionally, the "to" and "from" fields can be set,
+ * as well as an arbitrary number of properties.
+ *
+ * Properties provide an easy mechanism for clients to share data. Each property has a
+ * String name, and a value that is a Java primitive (int, long, float, double, boolean)
+ * or any Serializable object (a Java object is Serializable when it implements the
+ * Serializable interface).
+ *
+ * @author Matt Tucker
+ */
+public abstract class Packet {
+
+ protected static final String DEFAULT_LANGUAGE =
+ java.util.Locale.getDefault().getLanguage().toLowerCase();
+
+ private static String DEFAULT_XML_NS = null;
+
+ /**
+ * Constant used as packetID to indicate that a packet has no id. To indicate that a packet
+ * has no id set this constant as the packet's id. When the packet is asked for its id the
+ * answer will be <tt>null</tt>.
+ */
+ public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";
+
+ /**
+ * Date format as defined in XEP-0082 - XMPP Date and Time Profiles.
+ * The time zone is set to UTC.
+ * <p>
+ * Date formats are not synchronized. Since multiple threads access the format concurrently,
+ * it must be synchronized externally.
+ */
+ public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat(
+ "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+ static {
+ XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+
+ /**
+ * A prefix helps to make sure that ID's are unique across mutliple instances.
+ */
+ private static String prefix = StringUtils.randomString(5) + "-";
+
+ /**
+ * Keeps track of the current increment, which is appended to the prefix to
+ * forum a unique ID.
+ */
+ private static long id = 0;
+
+ private String xmlns = DEFAULT_XML_NS;
+
+ /**
+ * Returns the next unique id. Each id made up of a short alphanumeric
+ * prefix along with a unique numeric value.
+ *
+ * @return the next id.
+ */
+ public static synchronized String nextID() {
+ return prefix + Long.toString(id++);
+ }
+
+ public static void setDefaultXmlns(String defaultXmlns) {
+ DEFAULT_XML_NS = defaultXmlns;
+ }
+
+ private String packetID = null;
+ private String to = null;
+ private String from = null;
+ private final List<PacketExtension> packetExtensions
+ = new CopyOnWriteArrayList<PacketExtension>();
+
+ private final Map<String,Object> properties = new HashMap<String, Object>();
+ private XMPPError error = null;
+
+ public Packet() {
+ }
+
+ public Packet(Packet p) {
+ packetID = p.getPacketID();
+ to = p.getTo();
+ from = p.getFrom();
+ xmlns = p.xmlns;
+ error = p.error;
+
+ // Copy extensions
+ for (PacketExtension pe : p.getExtensions()) {
+ addExtension(pe);
+ }
+ }
+
+ /**
+ * Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
+ * ID_NOT_AVAILABLE was set as the packet's id.
+ *
+ * @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
+ */
+ public String getPacketID() {
+ if (ID_NOT_AVAILABLE.equals(packetID)) {
+ return null;
+ }
+
+ if (packetID == null) {
+ packetID = nextID();
+ }
+ return packetID;
+ }
+
+ /**
+ * Sets the unique ID of the packet. To indicate that a packet has no id
+ * pass the constant ID_NOT_AVAILABLE as the packet's id value.
+ *
+ * @param packetID the unique ID for the packet.
+ */
+ public void setPacketID(String packetID) {
+ this.packetID = packetID;
+ }
+
+ /**
+ * Returns who the packet is being sent "to", or <tt>null</tt> if
+ * the value is not set. The XMPP protocol often makes the "to"
+ * attribute optional, so it does not always need to be set.<p>
+ *
+ * The StringUtils class provides several useful methods for dealing with
+ * XMPP addresses such as parsing the
+ * {@link StringUtils#parseBareAddress(String) bare address},
+ * {@link StringUtils#parseName(String) user name},
+ * {@link StringUtils#parseServer(String) server}, and
+ * {@link StringUtils#parseResource(String) resource}.
+ *
+ * @return who the packet is being sent to, or <tt>null</tt> if the
+ * value has not been set.
+ */
+ public String getTo() {
+ return to;
+ }
+
+ /**
+ * Sets who the packet is being sent "to". The XMPP protocol often makes
+ * the "to" attribute optional, so it does not always need to be set.
+ *
+ * @param to who the packet is being sent to.
+ */
+ public void setTo(String to) {
+ this.to = to;
+ }
+
+ /**
+ * Returns who the packet is being sent "from" or <tt>null</tt> if
+ * the value is not set. The XMPP protocol often makes the "from"
+ * attribute optional, so it does not always need to be set.<p>
+ *
+ * The StringUtils class provides several useful methods for dealing with
+ * XMPP addresses such as parsing the
+ * {@link StringUtils#parseBareAddress(String) bare address},
+ * {@link StringUtils#parseName(String) user name},
+ * {@link StringUtils#parseServer(String) server}, and
+ * {@link StringUtils#parseResource(String) resource}.
+ *
+ * @return who the packet is being sent from, or <tt>null</tt> if the
+ * value has not been set.
+ */
+ public String getFrom() {
+ return from;
+ }
+
+ /**
+ * Sets who the packet is being sent "from". The XMPP protocol often
+ * makes the "from" attribute optional, so it does not always need to
+ * be set.
+ *
+ * @param from who the packet is being sent to.
+ */
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ /**
+ * Returns the error associated with this packet, or <tt>null</tt> if there are
+ * no errors.
+ *
+ * @return the error sub-packet or <tt>null</tt> if there isn't an error.
+ */
+ public XMPPError getError() {
+ return error;
+ }
+
+ /**
+ * Sets the error for this packet.
+ *
+ * @param error the error to associate with this packet.
+ */
+ public void setError(XMPPError error) {
+ this.error = error;
+ }
+
+ /**
+ * Returns an unmodifiable collection of the packet extensions attached to the packet.
+ *
+ * @return the packet extensions.
+ */
+ public synchronized Collection<PacketExtension> getExtensions() {
+ if (packetExtensions == null) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableList(new ArrayList<PacketExtension>(packetExtensions));
+ }
+
+ /**
+ * Returns the first extension of this packet that has the given namespace.
+ *
+ * @param namespace the namespace of the extension that is desired.
+ * @return the packet extension with the given namespace.
+ */
+ public PacketExtension getExtension(String namespace) {
+ return getExtension(null, namespace);
+ }
+
+ /**
+ * Returns the first packet extension that matches the specified element name and
+ * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null
+ * than only the provided namespace is attempted to be matched. Packet extensions are
+ * are arbitrary XML sub-documents in standard XMPP packets. By default, a
+ * DefaultPacketExtension instance will be returned for each extension. However,
+ * PacketExtensionProvider instances can be registered with the
+ * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
+ * class to handle custom parsing. In that case, the type of the Object
+ * will be determined by the provider.
+ *
+ * @param elementName the XML element name of the packet extension. (May be null)
+ * @param namespace the XML element namespace of the packet extension.
+ * @return the extension, or <tt>null</tt> if it doesn't exist.
+ */
+ public PacketExtension getExtension(String elementName, String namespace) {
+ if (namespace == null) {
+ return null;
+ }
+ for (PacketExtension ext : packetExtensions) {
+ if ((elementName == null || elementName.equals(ext.getElementName()))
+ && namespace.equals(ext.getNamespace()))
+ {
+ return ext;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a packet extension to the packet. Does nothing if extension is null.
+ *
+ * @param extension a packet extension.
+ */
+ public void addExtension(PacketExtension extension) {
+ if (extension == null) return;
+ packetExtensions.add(extension);
+ }
+
+ /**
+ * Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
+ *
+ * @param extensions a collection of packet extensions
+ */
+ public void addExtensions(Collection<PacketExtension> extensions) {
+ if (extensions == null) return;
+ packetExtensions.addAll(extensions);
+ }
+
+ /**
+ * Removes a packet extension from the packet.
+ *
+ * @param extension the packet extension to remove.
+ */
+ public void removeExtension(PacketExtension extension) {
+ packetExtensions.remove(extension);
+ }
+
+ /**
+ * Returns the packet property with the specified name or <tt>null</tt> if the
+ * property doesn't exist. Property values that were originally primitives will
+ * be returned as their object equivalent. For example, an int property will be
+ * returned as an Integer, a double as a Double, etc.
+ *
+ * @param name the name of the property.
+ * @return the property, or <tt>null</tt> if the property doesn't exist.
+ */
+ public synchronized Object getProperty(String name) {
+ if (properties == null) {
+ return null;
+ }
+ return properties.get(name);
+ }
+
+ /**
+ * Sets a property with an Object as the value. The value must be Serializable
+ * or an IllegalArgumentException will be thrown.
+ *
+ * @param name the name of the property.
+ * @param value the value of the property.
+ */
+ public synchronized void setProperty(String name, Object value) {
+ if (!(value instanceof Serializable)) {
+ throw new IllegalArgumentException("Value must be serialiazble");
+ }
+ properties.put(name, value);
+ }
+
+ /**
+ * Deletes a property.
+ *
+ * @param name the name of the property to delete.
+ */
+ public synchronized void deleteProperty(String name) {
+ if (properties == null) {
+ return;
+ }
+ properties.remove(name);
+ }
+
+ /**
+ * Returns an unmodifiable collection of all the property names that are set.
+ *
+ * @return all property names.
+ */
+ public synchronized Collection<String> getPropertyNames() {
+ if (properties == null) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(new HashSet<String>(properties.keySet()));
+ }
+
+ /**
+ * Returns the packet as XML. Every concrete extension of Packet must implement
+ * this method. In addition to writing out packet-specific data, every sub-class
+ * should also write out the error and the extensions data if they are defined.
+ *
+ * @return the XML format of the packet as a String.
+ */
+ public abstract String toXML();
+
+ /**
+ * Returns the extension sub-packets (including properties data) as an XML
+ * String, or the Empty String if there are no packet extensions.
+ *
+ * @return the extension sub-packets as XML or the Empty String if there
+ * are no packet extensions.
+ */
+ protected synchronized String getExtensionsXML() {
+ StringBuilder buf = new StringBuilder();
+ // Add in all standard extension sub-packets.
+ for (PacketExtension extension : getExtensions()) {
+ buf.append(extension.toXML());
+ }
+ // Add in packet properties.
+ if (properties != null && !properties.isEmpty()) {
+ buf.append("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">");
+ // Loop through all properties and write them out.
+ for (String name : getPropertyNames()) {
+ Object value = getProperty(name);
+ buf.append("<property>");
+ buf.append("<name>").append(StringUtils.escapeForXML(name)).append("</name>");
+ buf.append("<value type=\"");
+ if (value instanceof Integer) {
+ buf.append("integer\">").append(value).append("</value>");
+ }
+ else if (value instanceof Long) {
+ buf.append("long\">").append(value).append("</value>");
+ }
+ else if (value instanceof Float) {
+ buf.append("float\">").append(value).append("</value>");
+ }
+ else if (value instanceof Double) {
+ buf.append("double\">").append(value).append("</value>");
+ }
+ else if (value instanceof Boolean) {
+ buf.append("boolean\">").append(value).append("</value>");
+ }
+ else if (value instanceof String) {
+ buf.append("string\">");
+ buf.append(StringUtils.escapeForXML((String)value));
+ buf.append("</value>");
+ }
+ // Otherwise, it's a generic Serializable object. Serialized objects are in
+ // a binary format, which won't work well inside of XML. Therefore, we base-64
+ // encode the binary data before adding it.
+ else {
+ ByteArrayOutputStream byteStream = null;
+ ObjectOutputStream out = null;
+ try {
+ byteStream = new ByteArrayOutputStream();
+ out = new ObjectOutputStream(byteStream);
+ out.writeObject(value);
+ buf.append("java-object\">");
+ String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray());
+ buf.append(encodedVal).append("</value>");
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ if (out != null) {
+ try {
+ out.close();
+ }
+ catch (Exception e) {
+ // Ignore.
+ }
+ }
+ if (byteStream != null) {
+ try {
+ byteStream.close();
+ }
+ catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+ }
+ buf.append("</property>");
+ }
+ buf.append("</properties>");
+ }
+ return buf.toString();
+ }
+
+ public String getXmlns() {
+ return this.xmlns;
+ }
+
+ /**
+ * Returns the default language used for all messages containing localized content.
+ *
+ * @return the default language
+ */
+ public static String getDefaultLanguage() {
+ return DEFAULT_LANGUAGE;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Packet packet = (Packet) o;
+
+ if (error != null ? !error.equals(packet.error) : packet.error != null) { return false; }
+ if (from != null ? !from.equals(packet.from) : packet.from != null) { return false; }
+ if (!packetExtensions.equals(packet.packetExtensions)) { return false; }
+ if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) {
+ return false;
+ }
+ if (properties != null ? !properties.equals(packet.properties)
+ : packet.properties != null) {
+ return false;
+ }
+ if (to != null ? !to.equals(packet.to) : packet.to != null) { return false; }
+ return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null);
+ }
+
+ public int hashCode() {
+ int result;
+ result = (xmlns != null ? xmlns.hashCode() : 0);
+ result = 31 * result + (packetID != null ? packetID.hashCode() : 0);
+ result = 31 * result + (to != null ? to.hashCode() : 0);
+ result = 31 * result + (from != null ? from.hashCode() : 0);
+ result = 31 * result + packetExtensions.hashCode();
+ result = 31 * result + properties.hashCode();
+ result = 31 * result + (error != null ? error.hashCode() : 0);
+ return result;
+ }
+}