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