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.smackx.muc.packet;
019
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024
025import javax.xml.namespace.QName;
026
027import org.jivesoftware.smack.packet.ExtensionElement;
028import org.jivesoftware.smack.packet.NamedElement;
029import org.jivesoftware.smack.packet.Stanza;
030import org.jivesoftware.smack.util.XmlStringBuilder;
031
032import org.jxmpp.jid.EntityBareJid;
033import org.jxmpp.jid.EntityFullJid;
034import org.jxmpp.jid.EntityJid;
035
036/**
037 * Represents extended presence information about roles, affiliations, full JIDs,
038 * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace.
039 *
040 * @author Gaston Dombiak
041 */
042public class MUCUser implements ExtensionElement {
043
044    public static final String ELEMENT = "x";
045    public static final String NAMESPACE = MUCInitialPresence.NAMESPACE + "#user";
046    public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
047
048    private final Set<Status> statusCodes = new HashSet<>(4);
049
050    private Invite invite;
051    private Decline decline;
052    private MUCItem item;
053    private String password;
054    private Destroy destroy;
055
056    @Override
057    public String getElementName() {
058        return ELEMENT;
059    }
060
061    @Override
062    public String getNamespace() {
063        return NAMESPACE;
064    }
065
066    @Override
067    public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
068        XmlStringBuilder xml = new XmlStringBuilder(this);
069        xml.rightAngleBracket();
070        xml.optElement(getInvite());
071        xml.optElement(getDecline());
072        xml.optElement(getItem());
073        xml.optElement("password", getPassword());
074        xml.append(statusCodes);
075        xml.optElement(getDestroy());
076        xml.closeElement(this);
077        return xml;
078    }
079
080    /**
081     * Returns the invitation for another user to a room. The sender of the invitation
082     * must be an occupant of the room. The invitation will be sent to the room which in turn
083     * will forward the invitation to the invitee.
084     *
085     * @return an invitation for another user to a room.
086     */
087    public Invite getInvite() {
088        return invite;
089    }
090
091    /**
092     * Returns the rejection to an invitation from another user to a room. The rejection will be
093     * sent to the room which in turn will forward the refusal to the inviting user.
094     *
095     * @return a rejection to an invitation from another user to a room.
096     */
097    public Decline getDecline() {
098        return decline;
099    }
100
101    /**
102     * Returns the item child that holds information about roles, affiliation, jids and nicks.
103     *
104     * @return an item child that holds information about roles, affiliation, jids and nicks.
105     */
106    public MUCItem getItem() {
107        return item;
108    }
109
110    /**
111     * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is
112     * a room that a user cannot enter without first providing the correct password.
113     *
114     * @return the password to use to enter Password-Protected Room.
115     */
116    public String getPassword() {
117        return password;
118    }
119
120    /**
121     * Returns a set of status which holds the status code that assist in presenting notification messages.
122     *
123     * @return the set of status which holds the status code that assist in presenting notification messages.
124     */
125    public Set<Status> getStatus() {
126        return statusCodes;
127    }
128
129    /**
130     * Returns true if this MUCUser instance has also {@link Status} information.
131     * <p>
132     * If <code>true</code> is returned, then {@link #getStatus()} will return a non-empty set.
133     * </p>
134     *
135     * @return true if this MUCUser has status information.
136     * @since 4.1
137     */
138    public boolean hasStatus() {
139        return !statusCodes.isEmpty();
140    }
141
142    /**
143     * Returns the notification that the room has been destroyed. After a room has been destroyed,
144     * the room occupants will receive a Presence stanza of type 'unavailable' with the reason for
145     * the room destruction if provided by the room owner.
146     *
147     * @return a notification that the room has been destroyed.
148     */
149    public Destroy getDestroy() {
150        return destroy;
151    }
152
153    /**
154     * Sets the invitation for another user to a room. The sender of the invitation
155     * must be an occupant of the room. The invitation will be sent to the room which in turn
156     * will forward the invitation to the invitee.
157     *
158     * @param invite the invitation for another user to a room.
159     */
160    public void setInvite(Invite invite) {
161        this.invite = invite;
162    }
163
164    /**
165     * Sets the rejection to an invitation from another user to a room. The rejection will be
166     * sent to the room which in turn will forward the refusal to the inviting user.
167     *
168     * @param decline the rejection to an invitation from another user to a room.
169     */
170    public void setDecline(Decline decline) {
171        this.decline = decline;
172    }
173
174    /**
175     * Sets the item child that holds information about roles, affiliation, jids and nicks.
176     *
177     * @param item the item child that holds information about roles, affiliation, jids and nicks.
178     */
179    public void setItem(MUCItem item) {
180        this.item = item;
181    }
182
183    /**
184     * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is
185     * a room that a user cannot enter without first providing the correct password.
186     *
187     * @param string the password to use to enter Password-Protected Room.
188     */
189    public void setPassword(String string) {
190        password = string;
191    }
192
193    /**
194     * Add the status codes which holds the codes that assists in presenting notification messages.
195     *
196     * @param statusCodes the status codes which hold the codes that assists in presenting notification
197     * messages.
198     */
199    public void addStatusCodes(Set<Status> statusCodes) {
200        this.statusCodes.addAll(statusCodes);
201    }
202
203    /**
204     * Add a status code which hold a code that assists in presenting notification messages.
205     *
206     * @param status the status code which olds a code that assists in presenting notification messages.
207     */
208    public void addStatusCode(Status status) {
209        this.statusCodes.add(status);
210    }
211
212    /**
213     * Sets the notification that the room has been destroyed. After a room has been destroyed,
214     * the room occupants will receive a Presence stanza of type 'unavailable' with the reason for
215     * the room destruction if provided by the room owner.
216     *
217     * @param destroy the notification that the room has been destroyed.
218     */
219    public void setDestroy(Destroy destroy) {
220        this.destroy = destroy;
221    }
222
223    /**
224     * Retrieve the MUCUser PacketExtension from packet, if any.
225     *
226     * @param packet TODO javadoc me please
227     * @return the MUCUser PacketExtension or {@code null}
228     * @deprecated use {@link #from(Stanza)} instead
229     */
230    @Deprecated
231    public static MUCUser getFrom(Stanza packet) {
232        return from(packet);
233    }
234
235    /**
236     * Retrieve the MUCUser PacketExtension from packet, if any.
237     *
238     * @param packet TODO javadoc me please
239     * @return the MUCUser PacketExtension or {@code null}
240     */
241    public static MUCUser from(Stanza packet) {
242        return packet.getExtension(MUCUser.class);
243    }
244
245    /**
246     * Represents an invitation for another user to a room. The sender of the invitation
247     * must be an occupant of the room. The invitation will be sent to the room which in turn
248     * will forward the invitation to the invitee.
249     *
250     * @author Gaston Dombiak
251     */
252    public static class Invite implements NamedElement {
253        public static final String ELEMENT = "invite";
254
255        private final String reason;
256
257        /**
258         * From XEP-0045 § 7.8.2: "… whose value is the bare JID, full JID, or occupant JID of the inviting user …"
259         */
260        private final EntityJid from;
261
262        private final EntityBareJid to;
263
264        public Invite(String reason, EntityFullJid from) {
265            this(reason, from, null);
266        }
267
268        public Invite(String reason, EntityBareJid to) {
269            this(reason, null, to);
270        }
271
272        public Invite(String reason, EntityJid from, EntityBareJid to) {
273            this.reason = reason;
274            this.from = from;
275            this.to = to;
276        }
277
278        /**
279         * Returns the bare JID of the inviting user or, optionally, the room JID. (e.g.
280         * 'crone1@shakespeare.lit/desktop').
281         *
282         * @return the room's occupant that sent the invitation.
283         */
284        public EntityJid getFrom() {
285            return from;
286        }
287
288        /**
289         * Returns the message explaining the invitation.
290         *
291         * @return the message explaining the invitation.
292         */
293        public String getReason() {
294            return reason;
295        }
296
297        /**
298         * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit')
299         *
300         * @return the bare JID of the invitee.
301         */
302        public EntityBareJid getTo() {
303            return to;
304        }
305
306        @Override
307        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
308            XmlStringBuilder xml = new XmlStringBuilder(this);
309            xml.optAttribute("to", getTo());
310            xml.optAttribute("from", getFrom());
311            xml.rightAngleBracket();
312            xml.optElement("reason", getReason());
313            xml.closeElement(this);
314            return xml;
315        }
316
317        @Override
318        public String getElementName() {
319            return ELEMENT;
320        }
321    }
322
323    /**
324     * Represents a rejection to an invitation from another user to a room. The rejection will be
325     * sent to the room which in turn will forward the refusal to the inviting user.
326     *
327     * @author Gaston Dombiak
328     */
329    public static class Decline implements ExtensionElement {
330        public static final String ELEMENT = "decline";
331        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
332
333        private final String reason;
334        private final EntityBareJid from;
335        private final EntityBareJid to;
336
337        public Decline(String reason, EntityBareJid to) {
338            this(reason, null, to);
339        }
340
341        public Decline(String reason, EntityBareJid from, EntityBareJid to) {
342            this.reason = reason;
343            this.from = from;
344            this.to = to;
345        }
346
347        /**
348         * Returns the bare JID of the invitee that rejected the invitation. (e.g.
349         * 'crone1@shakespeare.lit').
350         *
351         * @return the bare JID of the invitee that rejected the invitation.
352         */
353        public EntityBareJid getFrom() {
354            return from;
355        }
356
357        /**
358         * Returns the message explaining why the invitation was rejected.
359         *
360         * @return the message explaining the reason for the rejection.
361         */
362        public String getReason() {
363            return reason;
364        }
365
366        /**
367         * Returns the bare JID of the inviting user. (e.g. 'hecate@shakespeare.lit')
368         *
369         * @return the bare JID of the inviting user.
370         */
371        public EntityBareJid getTo() {
372            return to;
373        }
374
375        @Override
376        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
377            XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
378            xml.optAttribute("to", getTo());
379            xml.optAttribute("from", getFrom());
380            xml.rightAngleBracket();
381            xml.optElement("reason", getReason());
382            xml.closeElement(this);
383            return xml;
384        }
385
386        @Override
387        public String getElementName() {
388            return QNAME.getLocalPart();
389        }
390
391        @Override
392        public String getNamespace() {
393            return QNAME.getNamespaceURI();
394        }
395    }
396
397    /**
398     * Status code assists in presenting notification messages. The following link provides the
399     * list of existing error codes <a href="http://xmpp.org/registrar/mucstatus.html">Multi-User Chat Status Codes</a>.
400     *
401     * @author Gaston Dombiak
402     */
403    public static final class Status implements NamedElement {
404        public static final String ELEMENT = "status";
405
406        private static final Map<Integer, Status> statusMap = new HashMap<>(8);
407
408        public static final Status PRESENCE_TO_SELF_110 = Status.create(110);
409        public static final Status ROOM_CREATED_201 = Status.create(201);
410        public static final Status BANNED_301 = Status.create(301);
411        public static final Status NEW_NICKNAME_303 = Status.create(303);
412        public static final Status KICKED_307 = Status.create(307);
413        public static final Status REMOVED_AFFIL_CHANGE_321 = Status.create(321);
414        public static final Status REMOVED_FOR_TECHNICAL_REASONS_333 = Status.create(333);
415
416        private final Integer code;
417
418        public static Status create(String string) {
419            Integer integer = Integer.valueOf(string);
420            return create(integer);
421        }
422
423        public static Status create(Integer i) {
424            Status status;
425            // TODO: Use computeIfAbsent once Smack's minimum required Android SDK level is 24 or higher.
426            synchronized (statusMap) {
427                status = statusMap.get(i);
428                if (status == null) {
429                    status = new Status(i);
430                    statusMap.put(i, status);
431                }
432            }
433            return status;
434        }
435
436        /**
437         * Creates a new instance of Status with the specified code.
438         *
439         * @param code the code that uniquely identifies the reason of the error.
440         */
441        private Status(int code) {
442            this.code = code;
443        }
444
445        /**
446         * Returns the code that uniquely identifies the reason of the error. The code
447         * assists in presenting notification messages.
448         *
449         * @return the code that uniquely identifies the reason of the error.
450         */
451        public int getCode() {
452            return code;
453        }
454
455        @Override
456        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
457            XmlStringBuilder xml = new XmlStringBuilder(this);
458            xml.attribute("code", getCode());
459            xml.closeEmptyElement();
460            return xml;
461        }
462
463        @Override
464        public String toString() {
465            return code.toString();
466        }
467
468        @Override
469        public boolean equals(Object other) {
470            if (other == null) {
471                return false;
472            }
473            if (other instanceof Status) {
474                Status otherStatus = (Status) other;
475                return code.equals(otherStatus.getCode());
476            }
477            return false;
478        }
479
480        @Override
481        public int hashCode() {
482            return code;
483        }
484
485        @Override
486        public String getElementName() {
487            return ELEMENT;
488        }
489    }
490}