001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack.packet;
019
020import org.jivesoftware.smack.util.StringUtils;
021import org.jivesoftware.smack.util.XmlStringBuilder;
022
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.List;
027import java.util.Locale;
028import java.util.concurrent.CopyOnWriteArrayList;
029
030/**
031 * Base class for XMPP packets. Every packet has a unique ID (which is automatically
032 * generated, but can be overridden). Optionally, the "to" and "from" fields can be set.
033 *
034 * @author Matt Tucker
035 */
036public abstract class Packet {
037
038    protected static final String DEFAULT_LANGUAGE =
039            java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);
040
041    private static String DEFAULT_XML_NS = null;
042
043    /**
044     * Constant used as packetID to indicate that a packet has no id. To indicate that a packet
045     * has no id set this constant as the packet's id. When the packet is asked for its id the
046     * answer will be <tt>null</tt>.
047     */
048    public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE";
049
050    /**
051     * A prefix helps to make sure that ID's are unique across mutliple instances.
052     */
053    private static String prefix = StringUtils.randomString(5) + "-";
054
055    /**
056     * Keeps track of the current increment, which is appended to the prefix to
057     * forum a unique ID.
058     */
059    private static long id = 0;
060
061    private String xmlns = DEFAULT_XML_NS;
062
063    /**
064     * Returns the next unique id. Each id made up of a short alphanumeric
065     * prefix along with a unique numeric value.
066     *
067     * @return the next id.
068     */
069    public static synchronized String nextID() {
070        return prefix + Long.toString(id++);
071    }
072
073    public static void setDefaultXmlns(String defaultXmlns) {
074        DEFAULT_XML_NS = defaultXmlns;
075    }
076
077    private String packetID = null;
078    private String to = null;
079    private String from = null;
080    private final List<PacketExtension> packetExtensions
081            = new CopyOnWriteArrayList<PacketExtension>();
082
083    private XMPPError error = null;
084
085    public Packet() {
086    }
087
088    public Packet(Packet p) {
089        packetID = p.getPacketID();
090        to = p.getTo();
091        from = p.getFrom();
092        xmlns = p.xmlns;
093        error = p.error;
094
095        // Copy extensions
096        for (PacketExtension pe : p.getExtensions()) {
097            addExtension(pe);
098        }
099    }
100
101    /**
102     * Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
103     * ID_NOT_AVAILABLE was set as the packet's id.
104     *
105     * @return the packet's unique ID or <tt>null</tt> if the packet's id is not available.
106     */
107    public String getPacketID() {
108        if (ID_NOT_AVAILABLE.equals(packetID)) {
109            return null;
110        }
111
112        if (packetID == null) {
113            packetID = nextID();
114        }
115        return packetID;
116    }
117
118    /**
119     * Sets the unique ID of the packet. To indicate that a packet has no id
120     * pass the constant ID_NOT_AVAILABLE as the packet's id value.
121     *
122     * @param packetID the unique ID for the packet.
123     */
124    public void setPacketID(String packetID) {
125        this.packetID = packetID;
126    }
127
128    /**
129     * Returns who the packet is being sent "to", or <tt>null</tt> if
130     * the value is not set. The XMPP protocol often makes the "to"
131     * attribute optional, so it does not always need to be set.<p>
132     *
133     * The StringUtils class provides several useful methods for dealing with
134     * XMPP addresses such as parsing the
135     * {@link StringUtils#parseBareAddress(String) bare address},
136     * {@link StringUtils#parseName(String) user name},
137     * {@link StringUtils#parseServer(String) server}, and
138     * {@link StringUtils#parseResource(String) resource}.  
139     *
140     * @return who the packet is being sent to, or <tt>null</tt> if the
141     *      value has not been set.
142     */
143    public String getTo() {
144        return to;
145    }
146
147    /**
148     * Sets who the packet is being sent "to". The XMPP protocol often makes
149     * the "to" attribute optional, so it does not always need to be set.
150     *
151     * @param to who the packet is being sent to.
152     */
153    public void setTo(String to) {
154        this.to = to;
155    }
156
157    /**
158     * Returns who the packet is being sent "from" or <tt>null</tt> if
159     * the value is not set. The XMPP protocol often makes the "from"
160     * attribute optional, so it does not always need to be set.<p>
161     *
162     * The StringUtils class provides several useful methods for dealing with
163     * XMPP addresses such as parsing the
164     * {@link StringUtils#parseBareAddress(String) bare address},
165     * {@link StringUtils#parseName(String) user name},
166     * {@link StringUtils#parseServer(String) server}, and
167     * {@link StringUtils#parseResource(String) resource}.  
168     *
169     * @return who the packet is being sent from, or <tt>null</tt> if the
170     *      value has not been set.
171     */
172    public String getFrom() {
173        return from;
174    }
175
176    /**
177     * Sets who the packet is being sent "from". The XMPP protocol often
178     * makes the "from" attribute optional, so it does not always need to
179     * be set.
180     *
181     * @param from who the packet is being sent to.
182     */
183    public void setFrom(String from) {
184        this.from = from;
185    }
186
187    /**
188     * Returns the error associated with this packet, or <tt>null</tt> if there are
189     * no errors.
190     *
191     * @return the error sub-packet or <tt>null</tt> if there isn't an error.
192     */
193    public XMPPError getError() {
194        return error;
195    }
196
197    /**
198     * Sets the error for this packet.
199     *
200     * @param error the error to associate with this packet.
201     */
202    public void setError(XMPPError error) {
203        this.error = error;
204    }
205
206    /**
207     * Returns an unmodifiable collection of the packet extensions attached to the packet.
208     *
209     * @return the packet extensions.
210     */
211    public synchronized Collection<PacketExtension> getExtensions() {
212        if (packetExtensions == null) {
213            return Collections.emptyList();
214        }
215        return Collections.unmodifiableList(new ArrayList<PacketExtension>(packetExtensions));
216    }
217
218    /**
219     * Returns the first extension of this packet that has the given namespace.
220     *
221     * @param namespace the namespace of the extension that is desired.
222     * @return the packet extension with the given namespace.
223     */
224    public PacketExtension getExtension(String namespace) {
225        return getExtension(null, namespace);
226    }
227
228    /**
229     * Returns the first packet extension that matches the specified element name and
230     * namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null,
231     * only the namespace is matched. Packet extensions are
232     * are arbitrary XML sub-documents in standard XMPP packets. By default, a 
233     * DefaultPacketExtension instance will be returned for each extension. However, 
234     * PacketExtensionProvider instances can be registered with the 
235     * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}
236     * class to handle custom parsing. In that case, the type of the Object
237     * will be determined by the provider.
238     *
239     * @param elementName the XML element name of the packet extension. (May be null)
240     * @param namespace the XML element namespace of the packet extension.
241     * @return the extension, or <tt>null</tt> if it doesn't exist.
242     */
243    @SuppressWarnings("unchecked")
244    public <PE extends PacketExtension> PE getExtension(String elementName, String namespace) {
245        if (namespace == null) {
246            return null;
247        }
248        for (PacketExtension ext : packetExtensions) {
249            if ((elementName == null || elementName.equals(ext.getElementName()))
250                    && namespace.equals(ext.getNamespace()))
251            {
252                return (PE) ext;
253            }
254        }
255        return null;
256    }
257
258    /**
259     * Adds a packet extension to the packet. Does nothing if extension is null.
260     *
261     * @param extension a packet extension.
262     */
263    public void addExtension(PacketExtension extension) {
264        if (extension == null) return;
265        packetExtensions.add(extension);
266    }
267
268    /**
269     * Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
270     * 
271     * @param extensions a collection of packet extensions
272     */
273    public void addExtensions(Collection<PacketExtension> extensions) {
274        if (extensions == null) return;
275        packetExtensions.addAll(extensions);
276    }
277
278    /**
279     * Removes a packet extension from the packet.
280     *
281     * @param extension the packet extension to remove.
282     */
283    public void removeExtension(PacketExtension extension)  {
284        packetExtensions.remove(extension);
285    }
286
287    /**
288     * Returns the packet as XML. Every concrete extension of Packet must implement
289     * this method. In addition to writing out packet-specific data, every sub-class
290     * should also write out the error and the extensions data if they are defined.
291     *
292     * @return the XML format of the packet as a String.
293     */
294    public abstract CharSequence toXML();
295
296    /**
297     * Returns the extension sub-packets (including properties data) as an XML
298     * String, or the Empty String if there are no packet extensions.
299     *
300     * @return the extension sub-packets as XML or the Empty String if there
301     * are no packet extensions.
302     */
303    protected synchronized CharSequence getExtensionsXML() {
304        XmlStringBuilder xml = new XmlStringBuilder();
305        // Add in all standard extension sub-packets.
306        for (PacketExtension extension : getExtensions()) {
307            xml.append(extension.toXML());
308        }
309        return xml;
310    }
311
312    public String getXmlns() {
313        return this.xmlns;
314    }
315
316    /**
317     * Returns the default language used for all messages containing localized content.
318     * 
319     * @return the default language
320     */
321    public static String getDefaultLanguage() {
322        return DEFAULT_LANGUAGE;
323    }
324
325    @Override
326    public boolean equals(Object o) {
327        if (this == o) return true;
328        if (o == null || getClass() != o.getClass()) return false;
329
330        Packet packet = (Packet) o;
331
332        if (error != null ? !error.equals(packet.error) : packet.error != null) { return false; }
333        if (from != null ? !from.equals(packet.from) : packet.from != null) { return false; }
334        if (!packetExtensions.equals(packet.packetExtensions)) { return false; }
335        if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) {
336            return false;
337        }
338        if (to != null ? !to.equals(packet.to) : packet.to != null)  { return false; }
339        return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null);
340    }
341
342    @Override
343    public int hashCode() {
344        int result;
345        result = (xmlns != null ? xmlns.hashCode() : 0);
346        result = 31 * result + (packetID != null ? packetID.hashCode() : 0);
347        result = 31 * result + (to != null ? to.hashCode() : 0);
348        result = 31 * result + (from != null ? from.hashCode() : 0);
349        result = 31 * result + packetExtensions.hashCode();
350        result = 31 * result + (error != null ? error.hashCode() : 0);
351        return result;
352    }
353
354    @Override
355    public String toString() {
356        return toXML().toString();
357    }
358
359    /**
360     * Add to, from and id attributes
361     *
362     * @param xml
363     */
364    protected void addCommonAttributes(XmlStringBuilder xml) {
365        xml.optAttribute("id", getPacketID());
366        xml.optAttribute("to", getTo());
367        xml.optAttribute("from", getFrom());
368    }
369}