diff options
Diffstat (limited to 'src/org/jivesoftware/smack/packet/Packet.java')
-rw-r--r-- | src/org/jivesoftware/smack/packet/Packet.java | 509 |
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; + } +} |