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 java.util.Locale;
021
022import org.jivesoftware.smack.packet.id.StanzaIdUtil;
023import org.jivesoftware.smack.util.Objects;
024import org.jivesoftware.smack.util.StringUtils;
025import org.jivesoftware.smack.util.TypedCloneable;
026import org.jivesoftware.smack.util.XmlStringBuilder;
027
028import org.jxmpp.jid.Jid;
029
030/**
031 * Represents XMPP presence packets. Every presence stanza has a type, which is one of
032 * the following values:
033 * <ul>
034 *      <li>{@link Presence.Type#available available} -- (Default) indicates the user is available to
035 *          receive messages.
036 *      <li>{@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages.
037 *      <li>{@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence.
038 *      <li>{@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence.
039 *      <li>{@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to
040 *          sender's presence.
041 *      <li>{@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to
042 *          sender's presence.
043 *      <li>{@link Presence.Type#error error} -- the presence stanza contains an error message.
044 * </ul><p>
045 *
046 * A number of attributes are optional:
047 * <ul>
048 *      <li>Status -- free-form text describing a user's presence (i.e., gone to lunch).
049 *      <li>Priority -- non-negative numerical priority of a sender's resource. The
050 *          highest resource priority is the default recipient of packets not addressed
051 *          to a particular resource.
052 *      <li>Mode -- one of five presence modes: {@link Mode#available available} (the default),
053 *          {@link Mode#chat chat}, {@link Mode#away away}, {@link Mode#xa xa} (extended away), and
054 *          {@link Mode#dnd dnd} (do not disturb).
055 * </ul><p>
056 *
057 * Presence packets are used for two purposes. First, to notify the server of
058 * the user's current presence status. Second, they are used to subscribe and
059 * unsubscribe users from the roster.
060 *
061 * @author Matt Tucker
062 */
063public final class Presence extends Stanza implements TypedCloneable<Presence> {
064
065    public static final String ELEMENT = "presence";
066
067    private Type type = Type.available;
068    private String status = null;
069
070    /**
071     * The priority of the presence. The magic value {@link Integer#MIN_VALUE} is used to indicate that the original
072     * presence stanza did not had an explicit priority set. In which case the priority defaults to 0.
073     *
074     * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3.</a>
075     */
076    private int priority = Integer.MIN_VALUE;
077
078    private Mode mode = null;
079
080    /**
081     * Creates a new presence update. Status, priority, and mode are left un-set.
082     *
083     * @param type the type.
084     */
085    public Presence(Type type) {
086        // Ensure that the stanza ID is set by calling super().
087        super();
088        setType(type);
089    }
090
091    /**
092     * Creates a new presence with the given type and using the given XMPP address as recipient.
093     *
094     * @param to the recipient.
095     * @param type the type.
096     * @since 4.2
097     */
098    public Presence(Jid to, Type type) {
099        this(type);
100        setTo(to);
101    }
102
103    /**
104     * Creates a new presence update with a specified status, priority, and mode.
105     *
106     * @param type the type.
107     * @param status a text message describing the presence update.
108     * @param priority the priority of this presence update.
109     * @param mode the mode type for this presence update.
110     */
111    public Presence(Type type, String status, int priority, Mode mode) {
112        // Ensure that the stanza ID is set by calling super().
113        super();
114        setType(type);
115        setStatus(status);
116        setPriority(priority);
117        setMode(mode);
118    }
119
120    /**
121     * Copy constructor.
122     * <p>
123     * This does not perform a deep clone, as extension elements are shared between the new and old
124     * instance.
125     * </p>
126     *
127     * @param other
128     */
129    public Presence(Presence other) {
130        super(other);
131        this.type = other.type;
132        this.status = other.status;
133        this.priority = other.priority;
134        this.mode = other.mode;
135    }
136
137    /**
138     * Returns true if the {@link Type presence type} is available (online) and
139     * false if the user is unavailable (offline), or if this is a presence packet
140     * involved in a subscription operation. This is a convenience method
141     * equivalent to <tt>getType() == Presence.Type.available</tt>. Note that even
142     * when the user is available, their presence mode may be {@link Mode#away away},
143     * {@link Mode#xa extended away} or {@link Mode#dnd do not disturb}. Use
144     * {@link #isAway()} to determine if the user is away.
145     *
146     * @return true if the presence type is available.
147     */
148    public boolean isAvailable() {
149        return type == Type.available;
150    }
151
152    /**
153     * Returns true if the presence type is {@link Type#available available} and the presence
154     * mode is {@link Mode#away away}, {@link Mode#xa extended away}, or
155     * {@link Mode#dnd do not disturb}. False will be returned when the type or mode
156     * is any other value, including when the presence type is unavailable (offline).
157     * This is a convenience method equivalent to
158     * <tt>type == Type.available &amp;&amp; (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd)</tt>.
159     *
160     * @return true if the presence type is available and the presence mode is away, xa, or dnd.
161     */
162    public boolean isAway() {
163        return type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd);
164    }
165
166    /**
167     * Returns the type of this presence packet.
168     *
169     * @return the type of the presence packet.
170     */
171    public Type getType() {
172        return type;
173    }
174
175    /**
176     * Sets the type of the presence packet.
177     *
178     * @param type the type of the presence packet.
179     */
180    public void setType(Type type) {
181        this.type = Objects.requireNonNull(type, "Type cannot be null");
182    }
183
184    /**
185     * Returns the status message of the presence update, or <tt>null</tt> if there
186     * is not a status. The status is free-form text describing a user's presence
187     * (i.e., "gone to lunch").
188     *
189     * @return the status message.
190     */
191    public String getStatus() {
192        return status;
193    }
194
195    /**
196     * Sets the status message of the presence update. The status is free-form text
197     * describing a user's presence (i.e., "gone to lunch").
198     *
199     * @param status the status message.
200     */
201    public void setStatus(String status) {
202        this.status = status;
203    }
204
205    /**
206     * Returns the priority of the presence, or Integer.MIN_VALUE if no priority has been set.
207     *
208     * @return the priority.
209     * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
210     */
211    public int getPriority() {
212        if (priority == Integer.MIN_VALUE) {
213            return 0;
214        }
215        return priority;
216    }
217
218    /**
219     * Sets the priority of the presence. The valid range is -128 through 127.
220     *
221     * @param priority the priority of the presence.
222     * @throws IllegalArgumentException if the priority is outside the valid range.
223     * @see <a href="https://tools.ietf.org/html/rfc6121#section-4.7.2.3">RFC 6121 § 4.7.2.3. Priority Element</a>
224     */
225    public void setPriority(int priority) {
226        if (priority < -128 || priority > 127) {
227            throw new IllegalArgumentException("Priority value " + priority +
228                    " is not valid. Valid range is -128 through 127.");
229        }
230        this.priority = priority;
231    }
232
233    /**
234     * Returns the mode of the presence update.
235     *
236     * @return the mode.
237     */
238    public Mode getMode() {
239        if (mode == null) {
240            return Mode.available;
241        }
242        return mode;
243    }
244
245    /**
246     * Sets the mode of the presence update. A null presence mode value is interpreted
247     * to be the same thing as {@link Presence.Mode#available}.
248     *
249     * @param mode the mode.
250     */
251    public void setMode(Mode mode) {
252        this.mode = mode;
253    }
254
255    @Override
256    public String toString() {
257        StringBuilder sb = new StringBuilder();
258        sb.append("Presence Stanza [");
259        logCommonAttributes(sb);
260        sb.append("type=").append(type).append(',');
261        if (mode != null) {
262            sb.append("mode=").append(mode).append(',');
263        }
264        if (!StringUtils.isNullOrEmpty(status)) {
265            sb.append("status=").append(status).append(',');
266        }
267        if (priority != Integer.MIN_VALUE) {
268            sb.append("prio=").append(priority).append(',');
269        }
270        sb.append(']');
271        return sb.toString();
272    }
273
274    @Override
275    public XmlStringBuilder toXML(String enclosingNamespace) {
276        XmlStringBuilder buf = new XmlStringBuilder(enclosingNamespace);
277        buf.halfOpenElement(ELEMENT);
278        addCommonAttributes(buf, enclosingNamespace);
279        if (type != Type.available) {
280            buf.attribute("type", type);
281        }
282        buf.rightAngleBracket();
283
284        buf.optElement("status", status);
285        if (priority != Integer.MIN_VALUE) {
286            buf.element("priority", Integer.toString(priority));
287        }
288        if (mode != null && mode != Mode.available) {
289            buf.element("show", mode);
290        }
291
292        buf.append(getExtensions(), enclosingNamespace);
293
294        // Add the error sub-packet, if there is one.
295        appendErrorIfExists(buf, enclosingNamespace);
296
297        buf.closeElement(ELEMENT);
298
299        return buf;
300    }
301
302    /**
303     * Creates and returns a copy of this presence stanza.
304     * <p>
305     * This does not perform a deep clone, as extension elements are shared between the new and old
306     * instance.
307     * </p>
308     * @return a clone of this presence.
309     */
310    @Override
311    public Presence clone() {
312        return new Presence(this);
313    }
314
315    /**
316     * Clone this presence and set a newly generated stanza ID as the clone's ID.
317     *
318     * @return a "clone" of this presence  with a different stanza ID.
319     * @since 4.1.2
320     */
321    public Presence cloneWithNewId() {
322        Presence clone = clone();
323        clone.setStanzaId(StanzaIdUtil.newStanzaId());
324        return clone;
325    }
326
327    /**
328     * An enum to represent the presence type. Note that presence type is often confused
329     * with presence mode. Generally, if a user is signed in to a server, they have a presence
330     * type of {@link #available available}, even if the mode is {@link Mode#away away},
331     * {@link Mode#dnd dnd}, etc. The presence type is only {@link #unavailable unavailable} when
332     * the user is signing out of the server.
333     */
334    public enum Type {
335
336       /**
337        * The user is available to receive messages (default).
338        */
339        available,
340
341        /**
342         * The user is unavailable to receive messages.
343         */
344        unavailable,
345
346        /**
347         * Request subscription to recipient's presence.
348         */
349        subscribe,
350
351        /**
352         * Grant subscription to sender's presence.
353         */
354        subscribed,
355
356        /**
357         * Request removal of subscription to sender's presence.
358         */
359        unsubscribe,
360
361        /**
362         * Grant removal of subscription to sender's presence.
363         */
364        unsubscribed,
365
366        /**
367         * The presence stanza contains an error message.
368         */
369        error,
370
371        /**
372         * A presence probe as defined in section 4.3 of RFC 6121.
373         */
374        probe,
375        ;
376
377        /**
378         * Converts a String into the corresponding types. Valid String values that can be converted
379         * to types are: "available", "unavailable", "subscribe", "subscribed", "unsubscribe",
380         * "unsubscribed" and "error".
381         *
382         * @param string the String value to covert.
383         * @return the corresponding Type.
384         * @throws IllegalArgumentException when not able to parse the string parameter
385         * @throws NullPointerException if the string is null
386         */
387        public static Type fromString(String string) {
388            return Type.valueOf(string.toLowerCase(Locale.US));
389        }
390    }
391
392    /**
393     * An enum to represent the presence mode.
394     */
395    public enum Mode {
396
397        /**
398         * Free to chat.
399         */
400        chat,
401
402        /**
403         * Available (the default).
404         */
405        available,
406
407        /**
408         * Away.
409         */
410        away,
411
412        /**
413         * Away for an extended period of time.
414         */
415        xa,
416
417        /**
418         * Do not disturb.
419         */
420        dnd;
421
422        /**
423         * Converts a String into the corresponding types. Valid String values that can be converted
424         * to types are: "chat", "available", "away", "xa", and "dnd".
425         *
426         * @param string the String value to covert.
427         * @return the corresponding Type.
428         * @throws IllegalArgumentException when not able to parse the string parameter
429         * @throws NullPointerException if the string is null
430         */
431        public static Mode fromString(String string) {
432            return Mode.valueOf(string.toLowerCase(Locale.US));
433        }
434    }
435}