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
332        private final String reason;
333        private final EntityBareJid from;
334        private final EntityBareJid to;
335
336        public Decline(String reason, EntityBareJid to) {
337            this(reason, null, to);
338        }
339
340        public Decline(String reason, EntityBareJid from, EntityBareJid to) {
341            this.reason = reason;
342            this.from = from;
343            this.to = to;
344        }
345
346        /**
347         * Returns the bare JID of the invitee that rejected the invitation. (e.g.
348         * 'crone1@shakespeare.lit').
349         *
350         * @return the bare JID of the invitee that rejected the invitation.
351         */
352        public EntityBareJid getFrom() {
353            return from;
354        }
355
356        /**
357         * Returns the message explaining why the invitation was rejected.
358         *
359         * @return the message explaining the reason for the rejection.
360         */
361        public String getReason() {
362            return reason;
363        }
364
365        /**
366         * Returns the bare JID of the inviting user. (e.g. 'hecate@shakespeare.lit')
367         *
368         * @return the bare JID of the inviting user.
369         */
370        public EntityBareJid getTo() {
371            return to;
372        }
373
374        @Override
375        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
376            XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
377            xml.optAttribute("to", getTo());
378            xml.optAttribute("from", getFrom());
379            xml.rightAngleBracket();
380            xml.optElement("reason", getReason());
381            xml.closeElement(this);
382            return xml;
383        }
384
385        @Override
386        public String getElementName() {
387            return ELEMENT;
388        }
389
390        @Override
391        public String getNamespace() {
392            return NAMESPACE;
393        }
394    }
395
396    /**
397     * Status code assists in presenting notification messages. The following link provides the
398     * list of existing error codes <a href="http://xmpp.org/registrar/mucstatus.html">Multi-User Chat Status Codes</a>.
399     *
400     * @author Gaston Dombiak
401     */
402    public static final class Status implements NamedElement {
403        public static final String ELEMENT = "status";
404
405        private static final Map<Integer, Status> statusMap = new HashMap<>(8);
406
407        public static final Status PRESENCE_TO_SELF_110 = Status.create(110);
408        public static final Status ROOM_CREATED_201 = Status.create(201);
409        public static final Status BANNED_301 = Status.create(301);
410        public static final Status NEW_NICKNAME_303 = Status.create(303);
411        public static final Status KICKED_307 = Status.create(307);
412        public static final Status REMOVED_AFFIL_CHANGE_321 = Status.create(321);
413        public static final Status REMOVED_FOR_TECHNICAL_REASONS_333 = Status.create(333);
414
415        private final Integer code;
416
417        public static Status create(String string) {
418            Integer integer = Integer.valueOf(string);
419            return create(integer);
420        }
421
422        public static Status create(Integer i) {
423            Status status;
424            // TODO: Use computeIfAbsent once Smack's minimum required Android SDK level is 24 or higher.
425            synchronized (statusMap) {
426                status = statusMap.get(i);
427                if (status == null) {
428                    status = new Status(i);
429                    statusMap.put(i, status);
430                }
431            }
432            return status;
433        }
434
435        /**
436         * Creates a new instance of Status with the specified code.
437         *
438         * @param code the code that uniquely identifies the reason of the error.
439         */
440        private Status(int code) {
441            this.code = code;
442        }
443
444        /**
445         * Returns the code that uniquely identifies the reason of the error. The code
446         * assists in presenting notification messages.
447         *
448         * @return the code that uniquely identifies the reason of the error.
449         */
450        public int getCode() {
451            return code;
452        }
453
454        @Override
455        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
456            XmlStringBuilder xml = new XmlStringBuilder(this);
457            xml.attribute("code", getCode());
458            xml.closeEmptyElement();
459            return xml;
460        }
461
462        @Override
463        public String toString() {
464            return code.toString();
465        }
466
467        @Override
468        public boolean equals(Object other) {
469            if (other == null) {
470                return false;
471            }
472            if (other instanceof Status) {
473                Status otherStatus = (Status) other;
474                return code.equals(otherStatus.getCode());
475            }
476            return false;
477        }
478
479        @Override
480        public int hashCode() {
481            return code;
482        }
483
484        @Override
485        public String getElementName() {
486            return ELEMENT;
487        }
488    }
489}