MultiUserChat.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.muc;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Set;
  23. import java.util.concurrent.ConcurrentHashMap;
  24. import java.util.concurrent.CopyOnWriteArraySet;
  25. import java.util.logging.Level;
  26. import java.util.logging.Logger;

  27. import org.jivesoftware.smack.MessageListener;
  28. import org.jivesoftware.smack.PacketCollector;
  29. import org.jivesoftware.smack.StanzaListener;
  30. import org.jivesoftware.smack.PresenceListener;
  31. import org.jivesoftware.smack.SmackException;
  32. import org.jivesoftware.smack.SmackException.NoResponseException;
  33. import org.jivesoftware.smack.SmackException.NotConnectedException;
  34. import org.jivesoftware.smack.XMPPConnection;
  35. import org.jivesoftware.smack.XMPPException;
  36. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  37. import org.jivesoftware.smack.chat.Chat;
  38. import org.jivesoftware.smack.chat.ChatManager;
  39. import org.jivesoftware.smack.chat.ChatMessageListener;
  40. import org.jivesoftware.smack.filter.AndFilter;
  41. import org.jivesoftware.smack.filter.FromMatchesFilter;
  42. import org.jivesoftware.smack.filter.MessageTypeFilter;
  43. import org.jivesoftware.smack.filter.MessageWithSubjectFilter;
  44. import org.jivesoftware.smack.filter.NotFilter;
  45. import org.jivesoftware.smack.filter.StanzaFilter;
  46. import org.jivesoftware.smack.filter.StanzaExtensionFilter;
  47. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  48. import org.jivesoftware.smack.filter.ToFilter;
  49. import org.jivesoftware.smack.packet.IQ;
  50. import org.jivesoftware.smack.packet.Message;
  51. import org.jivesoftware.smack.packet.Stanza;
  52. import org.jivesoftware.smack.packet.Presence;
  53. import org.jivesoftware.smack.util.StringUtils;
  54. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  55. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  56. import org.jivesoftware.smackx.iqregister.packet.Registration;
  57. import org.jivesoftware.smackx.muc.packet.Destroy;
  58. import org.jivesoftware.smackx.muc.packet.MUCAdmin;
  59. import org.jivesoftware.smackx.muc.packet.MUCInitialPresence;
  60. import org.jivesoftware.smackx.muc.packet.MUCItem;
  61. import org.jivesoftware.smackx.muc.packet.MUCOwner;
  62. import org.jivesoftware.smackx.muc.packet.MUCUser;
  63. import org.jivesoftware.smackx.muc.packet.MUCUser.Status;
  64. import org.jivesoftware.smackx.xdata.Form;
  65. import org.jivesoftware.smackx.xdata.FormField;
  66. import org.jivesoftware.smackx.xdata.packet.DataForm;
  67. import org.jxmpp.jid.BareJid;
  68. import org.jxmpp.jid.FullJid;
  69. import org.jxmpp.jid.Jid;
  70. import org.jxmpp.jid.JidWithLocalpart;
  71. import org.jxmpp.jid.impl.JidCreate;
  72. import org.jxmpp.jid.parts.Resourcepart;

  73. /**
  74.  * A MultiUserChat room (XEP-45), created with {@link MultiUserChatManager#getMultiUserChat(BareJid)}.
  75.  * <p>
  76.  * A MultiUserChat is a conversation that takes place among many users in a virtual
  77.  * room. A room could have many occupants with different affiliation and roles.
  78.  * Possible affiliations are "owner", "admin", "member", and "outcast". Possible roles
  79.  * are "moderator", "participant", and "visitor". Each role and affiliation guarantees
  80.  * different privileges (e.g. Send messages to all occupants, Kick participants and visitors,
  81.  * Grant voice, Edit member list, etc.).
  82.  * </p>
  83.  * <p>
  84.  * <b>Note:</b> Make sure to leave the MUC ({@link #leave()}) when you don't need it anymore or
  85.  * otherwise you may leak the instance.
  86.  * </p>
  87.  *
  88.  * @author Gaston Dombiak, Larry Kirschner
  89.  */
  90. public class MultiUserChat {
  91.     private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName());

  92.     private final XMPPConnection connection;
  93.     private final BareJid room;
  94.     private final MultiUserChatManager multiUserChatManager;
  95.     private final Map<FullJid, Presence> occupantsMap = new ConcurrentHashMap<>();

  96.     private final Set<InvitationRejectionListener> invitationRejectionListeners = new CopyOnWriteArraySet<InvitationRejectionListener>();
  97.     private final Set<SubjectUpdatedListener> subjectUpdatedListeners = new CopyOnWriteArraySet<SubjectUpdatedListener>();
  98.     private final Set<UserStatusListener> userStatusListeners = new CopyOnWriteArraySet<UserStatusListener>();
  99.     private final Set<ParticipantStatusListener> participantStatusListeners = new CopyOnWriteArraySet<ParticipantStatusListener>();
  100.     private final Set<MessageListener> messageListeners = new CopyOnWriteArraySet<MessageListener>();
  101.     private final Set<PresenceListener> presenceListeners = new CopyOnWriteArraySet<PresenceListener>();
  102.     private final Set<PresenceListener> presenceInterceptors = new CopyOnWriteArraySet<PresenceListener>();

  103.     /**
  104.      * This filter will match all stanzas send from the groupchat or from one if
  105.      * the groupchat participants, i.e. it filters only the bare JID of the from
  106.      * attribute against the JID of the MUC.
  107.      */
  108.     private final StanzaFilter fromRoomFilter;

  109.     /**
  110.      * Same as {@link #fromRoomFilter} together with {@link MessageTypeFilter#GROUPCHAT}.
  111.      */
  112.     private final StanzaFilter fromRoomGroupchatFilter;

  113.     private final StanzaListener presenceInterceptor;
  114.     private final StanzaListener messageListener;
  115.     private final StanzaListener presenceListener;
  116.     private final StanzaListener subjectListener;
  117.     private final StanzaListener declinesListener;

  118.     private String subject;
  119.     private Resourcepart nickname;
  120.     private boolean joined = false;
  121.     private PacketCollector messageCollector;

  122.     MultiUserChat(XMPPConnection connection, BareJid room, MultiUserChatManager multiUserChatManager) {
  123.         this.connection = connection;
  124.         this.room = room;
  125.         this.multiUserChatManager = multiUserChatManager;

  126.         fromRoomFilter = FromMatchesFilter.create(room);
  127.         fromRoomGroupchatFilter = new AndFilter(fromRoomFilter, MessageTypeFilter.GROUPCHAT);

  128.         messageListener = new StanzaListener() {
  129.             @Override
  130.             public void processPacket(Stanza packet) throws NotConnectedException {
  131.                 Message message = (Message) packet;
  132.                 for (MessageListener listener : messageListeners) {
  133.                     listener.processMessage(message);
  134.                 }
  135.             }
  136.         };

  137.         // Create a listener for subject updates.
  138.         subjectListener = new StanzaListener() {
  139.             public void processPacket(Stanza packet) {
  140.                 Message msg = (Message) packet;
  141.                 FullJid from = msg.getFrom().asFullJidIfPossible();
  142.                 if (from == null) {
  143.                     LOGGER.warning("Message subject not changed by a full JID: " + msg.getFrom());
  144.                     return;
  145.                 }
  146.                 // Update the room subject
  147.                 subject = msg.getSubject();
  148.                 // Fire event for subject updated listeners
  149.                 for (SubjectUpdatedListener listener : subjectUpdatedListeners) {
  150.                     listener.subjectUpdated(subject, from);
  151.                 }
  152.             }
  153.         };

  154.         // Create a listener for all presence updates.
  155.         presenceListener = new StanzaListener() {
  156.             public void processPacket(Stanza packet) {
  157.                 Presence presence = (Presence) packet;
  158.                 final FullJid from = presence.getFrom().asFullJidIfPossible();
  159.                 if (from == null) {
  160.                     LOGGER.warning("Presence not from a full JID: " + presence.getFrom());
  161.                     return;
  162.                 }
  163.                 String myRoomJID = MultiUserChat.this.room + "/" + nickname;
  164.                 boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
  165.                 switch (presence.getType()) {
  166.                 case available:
  167.                     Presence oldPresence = occupantsMap.put(from, presence);
  168.                     if (oldPresence != null) {
  169.                         // Get the previous occupant's affiliation & role
  170.                         MUCUser mucExtension = MUCUser.from(packet);
  171.                         MUCAffiliation oldAffiliation = mucExtension.getItem().getAffiliation();
  172.                         MUCRole oldRole = mucExtension.getItem().getRole();
  173.                         // Get the new occupant's affiliation & role
  174.                         mucExtension = MUCUser.from(packet);
  175.                         MUCAffiliation newAffiliation = mucExtension.getItem().getAffiliation();
  176.                         MUCRole newRole = mucExtension.getItem().getRole();
  177.                         // Fire role modification events
  178.                         checkRoleModifications(oldRole, newRole, isUserStatusModification, from);
  179.                         // Fire affiliation modification events
  180.                         checkAffiliationModifications(
  181.                             oldAffiliation,
  182.                             newAffiliation,
  183.                             isUserStatusModification,
  184.                             from);
  185.                     }
  186.                     else {
  187.                         // A new occupant has joined the room
  188.                         if (!isUserStatusModification) {
  189.                             for (ParticipantStatusListener listener : participantStatusListeners) {
  190.                                 listener.joined(from);
  191.                             }
  192.                         }
  193.                     }
  194.                     break;
  195.                 case unavailable:
  196.                     occupantsMap.remove(from);
  197.                     MUCUser mucUser = MUCUser.from(packet);
  198.                     if (mucUser != null && mucUser.hasStatus()) {
  199.                         // Fire events according to the received presence code
  200.                         checkPresenceCode(
  201.                             mucUser.getStatus(),
  202.                             presence.getFrom().equals(myRoomJID),
  203.                             mucUser,
  204.                             from);
  205.                     } else {
  206.                         // An occupant has left the room
  207.                         if (!isUserStatusModification) {
  208.                             for (ParticipantStatusListener listener : participantStatusListeners) {
  209.                                 listener.left(from);
  210.                             }
  211.                         }
  212.                     }
  213.                     break;
  214.                 default:
  215.                     break;
  216.                 }
  217.                 for (PresenceListener listener : presenceListeners) {
  218.                     listener.processPresence(presence);
  219.                 }
  220.             }
  221.         };

  222.         // Listens for all messages that include a MUCUser extension and fire the invitation
  223.         // rejection listeners if the message includes an invitation rejection.
  224.         declinesListener = new StanzaListener() {
  225.             public void processPacket(Stanza packet) {
  226.                 // Get the MUC User extension
  227.                 MUCUser mucUser = MUCUser.from(packet);
  228.                 // Check if the MUCUser informs that the invitee has declined the invitation
  229.                 if (mucUser.getDecline() == null) {
  230.                     return;
  231.                 }
  232.                 // Fire event for invitation rejection listeners
  233.                 fireInvitationRejectionListeners(mucUser.getDecline().getFrom(), mucUser.getDecline().getReason());
  234.             }
  235.         };

  236.         presenceInterceptor = new StanzaListener() {
  237.             @Override
  238.             public void processPacket(Stanza packet) {
  239.                 Presence presence = (Presence) packet;
  240.                 for (PresenceListener interceptor : presenceInterceptors) {
  241.                     interceptor.processPresence(presence);
  242.                 }
  243.             }
  244.         };
  245.     }


  246.     /**
  247.      * Returns the name of the room this MultiUserChat object represents.
  248.      *
  249.      * @return the multi user chat room name.
  250.      */
  251.     public BareJid getRoom() {
  252.         return room;
  253.     }

  254.     /**
  255.      * Enter a room, as described in XEP-45 7.2.
  256.      *
  257.      * @param nickname
  258.      * @param password
  259.      * @param history
  260.      * @param timeout
  261.      * @return the returned presence by the service after the client send the initial presence in order to enter the room.
  262.      * @throws NotConnectedException
  263.      * @throws NoResponseException
  264.      * @throws XMPPErrorException
  265.      * @throws InterruptedException
  266.      * @see <a href="http://xmpp.org/extensions/xep-0045.html#enter">XEP-45 7.2 Entering a Room</a>
  267.      */
  268.     private Presence enter(Resourcepart nickname, String password, DiscussionHistory history,
  269.                     long timeout) throws NotConnectedException, NoResponseException,
  270.                     XMPPErrorException, InterruptedException {
  271.         StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
  272.         // We enter a room by sending a presence packet where the "to"
  273.         // field is in the form "roomName@service/nickname"
  274.         Presence joinPresence = new Presence(Presence.Type.available);
  275.         final FullJid jid = JidCreate.fullFrom(room, nickname);
  276.         joinPresence.setTo(jid);

  277.         // Indicate the the client supports MUC
  278.         MUCInitialPresence mucInitialPresence = new MUCInitialPresence();
  279.         if (password != null) {
  280.             mucInitialPresence.setPassword(password);
  281.         }
  282.         if (history != null) {
  283.             mucInitialPresence.setHistory(history.getMUCHistory());
  284.         }
  285.         joinPresence.addExtension(mucInitialPresence);

  286.         // Wait for a presence packet back from the server.
  287.         StanzaFilter responseFilter = new AndFilter(FromMatchesFilter.createFull(jid), new StanzaTypeFilter(Presence.class));

  288.         // Setup the messageListeners and presenceListeners *before* the join presence is send.
  289.         connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
  290.         connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter,
  291.                         StanzaTypeFilter.PRESENCE));
  292.         connection.addSyncStanzaListener(subjectListener, new AndFilter(fromRoomFilter,
  293.                         MessageWithSubjectFilter.INSTANCE));
  294.         connection.addSyncStanzaListener(declinesListener, new AndFilter(new StanzaExtensionFilter(MUCUser.ELEMENT,
  295.                         MUCUser.NAMESPACE), new NotFilter(MessageTypeFilter.ERROR)));
  296.         connection.addPacketInterceptor(presenceInterceptor, new AndFilter(new ToFilter(room),
  297.                         StanzaTypeFilter.PRESENCE));
  298.         messageCollector = connection.createPacketCollector(fromRoomGroupchatFilter);

  299.         Presence presence;
  300.         try {
  301.             presence = connection.createPacketCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(timeout);
  302.         }
  303.         catch (InterruptedException | NoResponseException | XMPPErrorException e) {
  304.             // Ensure that all callbacks are removed if there is an exception
  305.             removeConnectionCallbacks();
  306.             throw e;
  307.         }

  308.         this.nickname = nickname;
  309.         joined = true;

  310.         // Update the list of joined rooms
  311.         multiUserChatManager.addJoinedRoom(room);
  312.         return presence;
  313.     }

  314.     /**
  315.      * Creates the room according to some default configuration, assign the requesting user as the
  316.      * room owner, and add the owner to the room but not allow anyone else to enter the room
  317.      * (effectively "locking" the room). The requesting user will join the room under the specified
  318.      * nickname as soon as the room has been created.
  319.      * <p>
  320.      * To create an "Instant Room", that means a room with some default configuration that is
  321.      * available for immediate access, the room's owner should send an empty form after creating the
  322.      * room. {@link #sendConfigurationForm(Form)}
  323.      * <p>
  324.      * To create a "Reserved Room", that means a room manually configured by the room creator before
  325.      * anyone is allowed to enter, the room's owner should complete and send a form after creating
  326.      * the room. Once the completed configuration form is sent to the server, the server will unlock
  327.      * the room. {@link #sendConfigurationForm(Form)}
  328.      *
  329.      * @param nickname the nickname to use.
  330.      * @throws XMPPErrorException if the room couldn't be created for some reason (e.g. 405 error if
  331.      *         the user is not allowed to create the room)
  332.      * @throws NoResponseException if there was no response from the server.
  333.      * @throws SmackException If the creation failed because of a missing acknowledge from the
  334.      *         server, e.g. because the room already existed.
  335.      * @throws InterruptedException
  336.      */
  337.     public synchronized void create(Resourcepart nickname) throws NoResponseException, XMPPErrorException, SmackException, InterruptedException {
  338.         if (joined) {
  339.             throw new IllegalStateException("Creation failed - User already joined the room.");
  340.         }

  341.         if (createOrJoin(nickname)) {
  342.             // We successfully created a new room
  343.             return;
  344.         }
  345.         // We need to leave the room since it seems that the room already existed
  346.         leave();
  347.         throw new SmackException("Creation failed - Missing acknowledge of room creation.");
  348.     }

  349.     /**
  350.      * Same as {@link #createOrJoin(Resourcepart, String, DiscussionHistory, long)}, but without a password, specifying a
  351.      * discussion history and using the connections default reply timeout.
  352.      *
  353.      * @param nickname
  354.      * @return true if the room creation was acknowledged by the service, false otherwise.
  355.      * @throws NoResponseException
  356.      * @throws XMPPErrorException
  357.      * @throws SmackException
  358.      * @throws InterruptedException
  359.      * @see #createOrJoin(Resourcepart, String, DiscussionHistory, long)
  360.      */
  361.     public synchronized boolean createOrJoin(Resourcepart nickname) throws NoResponseException, XMPPErrorException,
  362.                     SmackException, InterruptedException {
  363.         return createOrJoin(nickname, null, null, connection.getPacketReplyTimeout());
  364.     }

  365.     /**
  366.      * Like {@link #create(Resourcepart)}, but will return true if the room creation was acknowledged by
  367.      * the service (with an 201 status code). It's up to the caller to decide, based on the return
  368.      * value, if he needs to continue sending the room configuration. If false is returned, the room
  369.      * already existed and the user is able to join right away, without sending a form.
  370.      *
  371.      * @param nickname the nickname to use.
  372.      * @param password the password to use.
  373.      * @param history the amount of discussion history to receive while joining a room.
  374.      * @param timeout the amount of time to wait for a reply from the MUC service(in milliseconds).
  375.      * @return true if the room creation was acknowledged by the service, false otherwise.
  376.      * @throws XMPPErrorException if the room couldn't be created for some reason (e.g. 405 error if
  377.      *         the user is not allowed to create the room)
  378.      * @throws NoResponseException if there was no response from the server.
  379.      * @throws InterruptedException
  380.      */
  381.     public synchronized boolean createOrJoin(Resourcepart nickname, String password, DiscussionHistory history, long timeout)
  382.                     throws NoResponseException, XMPPErrorException, SmackException, InterruptedException {
  383.         if (joined) {
  384.             throw new IllegalStateException("Creation failed - User already joined the room.");
  385.         }

  386.         Presence presence = enter(nickname, password, history, timeout);

  387.         // Look for confirmation of room creation from the server
  388.         MUCUser mucUser = MUCUser.from(presence);
  389.         if (mucUser != null && mucUser.getStatus().contains(Status.ROOM_CREATED_201)) {
  390.             // Room was created and the user has joined the room
  391.             return true;
  392.         }
  393.         return false;
  394.     }

  395.     /**
  396.      * Joins the chat room using the specified nickname. If already joined
  397.      * using another nickname, this method will first leave the room and then
  398.      * re-join using the new nickname. The default connection timeout for a reply
  399.      * from the group chat server that the join succeeded will be used. After
  400.      * joining the room, the room will decide the amount of history to send.
  401.      *
  402.      * @param nickname the nickname to use.
  403.      * @throws NoResponseException
  404.      * @throws XMPPErrorException if an error occurs joining the room. In particular, a
  405.      *      401 error can occur if no password was provided and one is required; or a
  406.      *      403 error can occur if the user is banned; or a
  407.      *      404 error can occur if the room does not exist or is locked; or a
  408.      *      407 error can occur if user is not on the member list; or a
  409.      *      409 error can occur if someone is already in the group chat with the same nickname.
  410.      * @throws NoResponseException if there was no response from the server.
  411.      * @throws NotConnectedException
  412.      * @throws InterruptedException
  413.      */
  414.     public void join(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  415.         join(nickname, null, null, connection.getPacketReplyTimeout());
  416.     }

  417.     /**
  418.      * Joins the chat room using the specified nickname and password. If already joined
  419.      * using another nickname, this method will first leave the room and then
  420.      * re-join using the new nickname. The default connection timeout for a reply
  421.      * from the group chat server that the join succeeded will be used. After
  422.      * joining the room, the room will decide the amount of history to send.<p>
  423.      *
  424.      * A password is required when joining password protected rooms. If the room does
  425.      * not require a password there is no need to provide one.
  426.      *
  427.      * @param nickname the nickname to use.
  428.      * @param password the password to use.
  429.      * @throws XMPPErrorException if an error occurs joining the room. In particular, a
  430.      *      401 error can occur if no password was provided and one is required; or a
  431.      *      403 error can occur if the user is banned; or a
  432.      *      404 error can occur if the room does not exist or is locked; or a
  433.      *      407 error can occur if user is not on the member list; or a
  434.      *      409 error can occur if someone is already in the group chat with the same nickname.
  435.      * @throws SmackException if there was no response from the server.
  436.      * @throws InterruptedException
  437.      */
  438.     public void join(Resourcepart nickname, String password) throws XMPPErrorException, SmackException, InterruptedException {
  439.         join(nickname, password, null, connection.getPacketReplyTimeout());
  440.     }

  441.     /**
  442.      * Joins the chat room using the specified nickname and password. If already joined
  443.      * using another nickname, this method will first leave the room and then
  444.      * re-join using the new nickname.<p>
  445.      *
  446.      * To control the amount of history to receive while joining a room you will need to provide
  447.      * a configured DiscussionHistory object.<p>
  448.      *
  449.      * A password is required when joining password protected rooms. If the room does
  450.      * not require a password there is no need to provide one.<p>
  451.      *
  452.      * If the room does not already exist when the user seeks to enter it, the server will
  453.      * decide to create a new room or not.
  454.      *
  455.      * @param nickname the nickname to use.
  456.      * @param password the password to use.
  457.      * @param history the amount of discussion history to receive while joining a room.
  458.      * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds).
  459.      * @throws XMPPErrorException if an error occurs joining the room. In particular, a
  460.      *      401 error can occur if no password was provided and one is required; or a
  461.      *      403 error can occur if the user is banned; or a
  462.      *      404 error can occur if the room does not exist or is locked; or a
  463.      *      407 error can occur if user is not on the member list; or a
  464.      *      409 error can occur if someone is already in the group chat with the same nickname.
  465.      * @throws NoResponseException if there was no response from the server.
  466.      * @throws NotConnectedException
  467.      * @throws InterruptedException
  468.      */
  469.     public synchronized void join(
  470.         Resourcepart nickname,
  471.         String password,
  472.         DiscussionHistory history,
  473.         long timeout)
  474.         throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  475.         // If we've already joined the room, leave it before joining under a new
  476.         // nickname.
  477.         if (joined) {
  478.             leave();
  479.         }
  480.         enter(nickname, password, history, timeout);
  481.     }

  482.     /**
  483.      * Returns true if currently in the multi user chat (after calling the {@link
  484.      * #join(Resourcepart)} method).
  485.      *
  486.      * @return true if currently in the multi user chat room.
  487.      */
  488.     public boolean isJoined() {
  489.         return joined;
  490.     }

  491.     /**
  492.      * Leave the chat room.
  493.      * @throws NotConnectedException
  494.      * @throws InterruptedException
  495.      */
  496.     public synchronized void leave() throws NotConnectedException, InterruptedException {
  497.         // If not joined already, do nothing.
  498.         if (!joined) {
  499.             return;
  500.         }
  501.         // We leave a room by sending a presence packet where the "to"
  502.         // field is in the form "roomName@service/nickname"
  503.         Presence leavePresence = new Presence(Presence.Type.unavailable);
  504.         leavePresence.setTo(JidCreate.fullFrom(room, nickname));
  505.         connection.sendStanza(leavePresence);
  506.         // Reset occupant information.
  507.         occupantsMap.clear();
  508.         nickname = null;
  509.         joined = false;
  510.         userHasLeft();
  511.     }

  512.     /**
  513.      * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if
  514.      * no configuration is possible. The configuration form allows to set the room's language,
  515.      * enable logging, specify room's type, etc..
  516.      *
  517.      * @return the Form that contains the fields to complete together with the instrucions or
  518.      * <tt>null</tt> if no configuration is possible.
  519.      * @throws XMPPErrorException if an error occurs asking the configuration form for the room.
  520.      * @throws NoResponseException if there was no response from the server.
  521.      * @throws NotConnectedException
  522.      * @throws InterruptedException
  523.      */
  524.     public Form getConfigurationForm() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  525.         MUCOwner iq = new MUCOwner();
  526.         iq.setTo(room);
  527.         iq.setType(IQ.Type.get);

  528.         IQ answer = connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  529.         return Form.getFormFrom(answer);
  530.     }

  531.     /**
  532.      * Sends the completed configuration form to the server. The room will be configured
  533.      * with the new settings defined in the form. If the form is empty then the server
  534.      * will create an instant room (will use default configuration).
  535.      *
  536.      * @param form the form with the new settings.
  537.      * @throws XMPPErrorException if an error occurs setting the new rooms' configuration.
  538.      * @throws NoResponseException if there was no response from the server.
  539.      * @throws NotConnectedException
  540.      * @throws InterruptedException
  541.      */
  542.     public void sendConfigurationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  543.         MUCOwner iq = new MUCOwner();
  544.         iq.setTo(room);
  545.         iq.setType(IQ.Type.set);
  546.         iq.addExtension(form.getDataFormToSend());

  547.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  548.     }

  549.     /**
  550.      * Returns the room's registration form that an unaffiliated user, can use to become a member
  551.      * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the
  552.      * privilege to register members and allow only room admins to add new members.<p>
  553.      *
  554.      * If the user requesting registration requirements is not allowed to register with the room
  555.      * (e.g. because that privilege has been restricted), the room will return a "Not Allowed"
  556.      * error to the user (error code 405).
  557.      *
  558.      * @return the registration Form that contains the fields to complete together with the
  559.      * instrucions or <tt>null</tt> if no registration is possible.
  560.      * @throws XMPPErrorException if an error occurs asking the registration form for the room or a
  561.      * 405 error if the user is not allowed to register with the room.
  562.      * @throws NoResponseException if there was no response from the server.
  563.      * @throws NotConnectedException
  564.      * @throws InterruptedException
  565.      */
  566.     public Form getRegistrationForm() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  567.         Registration reg = new Registration();
  568.         reg.setType(IQ.Type.get);
  569.         reg.setTo(room);

  570.         IQ result = connection.createPacketCollectorAndSend(reg).nextResultOrThrow();
  571.         return Form.getFormFrom(result);
  572.     }

  573.     /**
  574.      * Sends the completed registration form to the server. After the user successfully submits
  575.      * the form, the room may queue the request for review by the room admins or may immediately
  576.      * add the user to the member list by changing the user's affiliation from "none" to "member.<p>
  577.      *
  578.      * If the desired room nickname is already reserved for that room, the room will return a
  579.      * "Conflict" error to the user (error code 409). If the room does not support registration,
  580.      * it will return a "Service Unavailable" error to the user (error code 503).
  581.      *
  582.      * @param form the completed registration form.
  583.      * @throws XMPPErrorException if an error occurs submitting the registration form. In particular, a
  584.      *      409 error can occur if the desired room nickname is already reserved for that room;
  585.      *      or a 503 error can occur if the room does not support registration.
  586.      * @throws NoResponseException if there was no response from the server.
  587.      * @throws NotConnectedException
  588.      * @throws InterruptedException
  589.      */
  590.     public void sendRegistrationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  591.         Registration reg = new Registration();
  592.         reg.setType(IQ.Type.set);
  593.         reg.setTo(room);
  594.         reg.addExtension(form.getDataFormToSend());

  595.         connection.createPacketCollectorAndSend(reg).nextResultOrThrow();
  596.     }

  597.     /**
  598.      * Sends a request to the server to destroy the room. The sender of the request
  599.      * should be the room's owner. If the sender of the destroy request is not the room's owner
  600.      * then the server will answer a "Forbidden" error (403).
  601.      *
  602.      * @param reason the reason for the room destruction.
  603.      * @param alternateJID the JID of an alternate location.
  604.      * @throws XMPPErrorException if an error occurs while trying to destroy the room.
  605.      *      An error can occur which will be wrapped by an XMPPException --
  606.      *      XMPP error code 403. The error code can be used to present more
  607.      *      appropiate error messages to end-users.
  608.      * @throws NoResponseException if there was no response from the server.
  609.      * @throws NotConnectedException
  610.      * @throws InterruptedException
  611.      */
  612.     public void destroy(String reason, BareJid alternateJID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  613.         MUCOwner iq = new MUCOwner();
  614.         iq.setTo(room);
  615.         iq.setType(IQ.Type.set);

  616.         // Create the reason for the room destruction
  617.         Destroy destroy = new Destroy(alternateJID, reason);
  618.         iq.setDestroy(destroy);

  619.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();

  620.         // Reset occupant information.
  621.         occupantsMap.clear();
  622.         nickname = null;
  623.         joined = false;
  624.         userHasLeft();
  625.     }

  626.     /**
  627.      * Invites another user to the room in which one is an occupant. The invitation
  628.      * will be sent to the room which in turn will forward the invitation to the invitee.<p>
  629.      *
  630.      * If the room is password-protected, the invitee will receive a password to use to join
  631.      * the room. If the room is members-only, the the invitee may be added to the member list.
  632.      *
  633.      * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit)
  634.      * @param reason the reason why the user is being invited.
  635.      * @throws NotConnectedException
  636.      * @throws InterruptedException
  637.      */
  638.     public void invite(String user, String reason) throws NotConnectedException, InterruptedException {
  639.         invite(new Message(), user, reason);
  640.     }

  641.     /**
  642.      * Invites another user to the room in which one is an occupant using a given Message. The invitation
  643.      * will be sent to the room which in turn will forward the invitation to the invitee.<p>
  644.      *
  645.      * If the room is password-protected, the invitee will receive a password to use to join
  646.      * the room. If the room is members-only, the the invitee may be added to the member list.
  647.      *
  648.      * @param message the message to use for sending the invitation.
  649.      * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit)
  650.      * @param reason the reason why the user is being invited.
  651.      * @throws NotConnectedException
  652.      * @throws InterruptedException
  653.      */
  654.     public void invite(Message message, String user, String reason) throws NotConnectedException, InterruptedException {
  655.         // TODO listen for 404 error code when inviter supplies a non-existent JID
  656.         message.setTo(room);

  657.         // Create the MUCUser packet that will include the invitation
  658.         MUCUser mucUser = new MUCUser();
  659.         MUCUser.Invite invite = new MUCUser.Invite();
  660.         invite.setTo(user);
  661.         invite.setReason(reason);
  662.         mucUser.setInvite(invite);
  663.         // Add the MUCUser packet that includes the invitation to the message
  664.         message.addExtension(mucUser);

  665.         connection.sendStanza(message);
  666.     }

  667.     /**
  668.      * Adds a listener to invitation rejections notifications. The listener will be fired anytime
  669.      * an invitation is declined.
  670.      *
  671.      * @param listener an invitation rejection listener.
  672.      * @return true if the listener was not already added.
  673.      */
  674.     public boolean addInvitationRejectionListener(InvitationRejectionListener listener) {
  675.          return invitationRejectionListeners.add(listener);
  676.     }

  677.     /**
  678.      * Removes a listener from invitation rejections notifications. The listener will be fired
  679.      * anytime an invitation is declined.
  680.      *
  681.      * @param listener an invitation rejection listener.
  682.      * @return true if the listener was registered and is now removed.
  683.      */
  684.     public boolean removeInvitationRejectionListener(InvitationRejectionListener listener) {
  685.         return invitationRejectionListeners.remove(listener);
  686.     }

  687.     /**
  688.      * Fires invitation rejection listeners.
  689.      *
  690.      * @param invitee the user being invited.
  691.      * @param reason the reason for the rejection
  692.      */
  693.     private void fireInvitationRejectionListeners(String invitee, String reason) {
  694.         InvitationRejectionListener[] listeners;
  695.         synchronized (invitationRejectionListeners) {
  696.             listeners = new InvitationRejectionListener[invitationRejectionListeners.size()];
  697.             invitationRejectionListeners.toArray(listeners);
  698.         }
  699.         for (InvitationRejectionListener listener : listeners) {
  700.             listener.invitationDeclined(invitee, reason);
  701.         }
  702.     }

  703.     /**
  704.      * Adds a listener to subject change notifications. The listener will be fired anytime
  705.      * the room's subject changes.
  706.      *
  707.      * @param listener a subject updated listener.
  708.      * @return true if the listener was not already added.
  709.      */
  710.     public boolean addSubjectUpdatedListener(SubjectUpdatedListener listener) {
  711.         return subjectUpdatedListeners.add(listener);
  712.     }

  713.     /**
  714.      * Removes a listener from subject change notifications. The listener will be fired
  715.      * anytime the room's subject changes.
  716.      *
  717.      * @param listener a subject updated listener.
  718.      * @return true if the listener was registered and is now removed.
  719.      */
  720.     public boolean removeSubjectUpdatedListener(SubjectUpdatedListener listener) {
  721.         return subjectUpdatedListeners.remove(listener);
  722.     }

  723.     /**
  724.      * Adds a new {@link StanzaListener} that will be invoked every time a new presence
  725.      * is going to be sent by this MultiUserChat to the server. Packet interceptors may
  726.      * add new extensions to the presence that is going to be sent to the MUC service.
  727.      *
  728.      * @param presenceInterceptor the new packet interceptor that will intercept presence packets.
  729.      */
  730.     public void addPresenceInterceptor(PresenceListener presenceInterceptor) {
  731.         presenceInterceptors.add(presenceInterceptor);
  732.     }

  733.     /**
  734.      * Removes a {@link StanzaListener} that was being invoked every time a new presence
  735.      * was being sent by this MultiUserChat to the server. Packet interceptors may
  736.      * add new extensions to the presence that is going to be sent to the MUC service.
  737.      *
  738.      * @param presenceInterceptor the packet interceptor to remove.
  739.      */
  740.     public void removePresenceInterceptor(StanzaListener presenceInterceptor) {
  741.         presenceInterceptors.remove(presenceInterceptor);
  742.     }

  743.     /**
  744.      * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room
  745.      * or the room does not have a subject yet. In case the room has a subject, as soon as the
  746.      * user joins the room a message with the current room's subject will be received.<p>
  747.      *
  748.      * To be notified every time the room's subject change you should add a listener
  749.      * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p>
  750.      *
  751.      * To change the room's subject use {@link #changeSubject(String)}.
  752.      *
  753.      * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the
  754.      * room does not have a subject yet.
  755.      */
  756.     public String getSubject() {
  757.         return subject;
  758.     }

  759.     /**
  760.      * Returns the reserved room nickname for the user in the room. A user may have a reserved
  761.      * nickname, for example through explicit room registration or database integration. In such
  762.      * cases it may be desirable for the user to discover the reserved nickname before attempting
  763.      * to enter the room.
  764.      *
  765.      * @return the reserved room nickname or <tt>null</tt> if none.
  766.      * @throws SmackException if there was no response from the server.
  767.      * @throws InterruptedException
  768.      */
  769.     public String getReservedNickname() throws SmackException, InterruptedException {
  770.         try {
  771.             DiscoverInfo result =
  772.                 ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(
  773.                     room,
  774.                     "x-roomuser-item");
  775.             // Look for an Identity that holds the reserved nickname and return its name
  776.             for (DiscoverInfo.Identity identity : result.getIdentities()) {
  777.                 return identity.getName();
  778.             }
  779.         }
  780.         catch (XMPPException e) {
  781.             LOGGER.log(Level.SEVERE, "Error retrieving room nickname", e);
  782.         }
  783.         // If no Identity was found then the user does not have a reserved room nickname
  784.         return null;
  785.     }

  786.     /**
  787.      * Returns the nickname that was used to join the room, or <tt>null</tt> if not
  788.      * currently joined.
  789.      *
  790.      * @return the nickname currently being used.
  791.      */
  792.     public Resourcepart getNickname() {
  793.         return nickname;
  794.     }

  795.     /**
  796.      * Changes the occupant's nickname to a new nickname within the room. Each room occupant
  797.      * will receive two presence packets. One of type "unavailable" for the old nickname and one
  798.      * indicating availability for the new nickname. The unavailable presence will contain the new
  799.      * nickname and an appropriate status code (namely 303) as extended presence information. The
  800.      * status code 303 indicates that the occupant is changing his/her nickname.
  801.      *
  802.      * @param nickname the new nickname within the room.
  803.      * @throws XMPPErrorException if the new nickname is already in use by another occupant.
  804.      * @throws NoResponseException if there was no response from the server.
  805.      * @throws NotConnectedException
  806.      * @throws InterruptedException
  807.      */
  808.     public void changeNickname(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  809.         StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
  810.         // Check that we already have joined the room before attempting to change the
  811.         // nickname.
  812.         if (!joined) {
  813.             throw new IllegalStateException("Must be logged into the room to change nickname.");
  814.         }
  815.         final FullJid jid = JidCreate.fullFrom(room, nickname);
  816.         // We change the nickname by sending a presence packet where the "to"
  817.         // field is in the form "roomName@service/nickname"
  818.         // We don't have to signal the MUC support again
  819.         Presence joinPresence = new Presence(Presence.Type.available);
  820.         joinPresence.setTo(jid);

  821.         // Wait for a presence packet back from the server.
  822.         StanzaFilter responseFilter =
  823.             new AndFilter(
  824.                 FromMatchesFilter.createFull(jid),
  825.                 new StanzaTypeFilter(Presence.class));
  826.         PacketCollector response = connection.createPacketCollectorAndSend(responseFilter, joinPresence);
  827.         // Wait up to a certain number of seconds for a reply. If there is a negative reply, an
  828.         // exception will be thrown
  829.         response.nextResultOrThrow();

  830.         this.nickname = nickname;
  831.     }

  832.     /**
  833.      * Changes the occupant's availability status within the room. The presence type
  834.      * will remain available but with a new status that describes the presence update and
  835.      * a new presence mode (e.g. Extended away).
  836.      *
  837.      * @param status a text message describing the presence update.
  838.      * @param mode the mode type for the presence update.
  839.      * @throws NotConnectedException
  840.      * @throws InterruptedException
  841.      */
  842.     public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException {
  843.         StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank.");
  844.         // Check that we already have joined the room before attempting to change the
  845.         // availability status.
  846.         if (!joined) {
  847.             throw new IllegalStateException(
  848.                 "Must be logged into the room to change the " + "availability status.");
  849.         }
  850.         // We change the availability status by sending a presence packet to the room with the
  851.         // new presence status and mode
  852.         Presence joinPresence = new Presence(Presence.Type.available);
  853.         joinPresence.setStatus(status);
  854.         joinPresence.setMode(mode);
  855.         joinPresence.setTo(JidCreate.fullFrom(room, nickname));

  856.         // Send join packet.
  857.         connection.sendStanza(joinPresence);
  858.     }

  859.     /**
  860.      * Kicks a visitor or participant from the room. The kicked occupant will receive a presence
  861.      * of type "unavailable" including a status code 307 and optionally along with the reason
  862.      * (if provided) and the bare JID of the user who initiated the kick. After the occupant
  863.      * was kicked from the room, the rest of the occupants will receive a presence of type
  864.      * "unavailable". The presence will include a status code 307 which means that the occupant
  865.      * was kicked from the room.
  866.      *
  867.      * @param nickname the nickname of the participant or visitor to kick from the room
  868.      * (e.g. "john").
  869.      * @param reason the reason why the participant or visitor is being kicked from the room.
  870.      * @throws XMPPErrorException if an error occurs kicking the occupant. In particular, a
  871.      *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
  872.      *      was intended to be kicked (i.e. Not Allowed error); or a
  873.      *      403 error can occur if the occupant that intended to kick another occupant does
  874.      *      not have kicking privileges (i.e. Forbidden error); or a
  875.      *      400 error can occur if the provided nickname is not present in the room.
  876.      * @throws NoResponseException if there was no response from the server.
  877.      * @throws NotConnectedException
  878.      * @throws InterruptedException
  879.      */
  880.     public void kickParticipant(Resourcepart nickname, String reason) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  881.         changeRole(nickname, MUCRole.none, reason);
  882.     }

  883.     /**
  884.      * Sends a voice request to the MUC. The room moderators usually need to approve this request.
  885.      *
  886.      * @throws NotConnectedException
  887.      * @throws InterruptedException
  888.      * @see <a href="http://xmpp.org/extensions/xep-0045.html#requestvoice">XEP-45 § 7.13 Requesting
  889.      *      Voice</a>
  890.      * @since 4.1
  891.      */
  892.     public void requestVoice() throws NotConnectedException, InterruptedException {
  893.         DataForm form = new DataForm(DataForm.Type.submit);
  894.         FormField formTypeField = new FormField(FormField.FORM_TYPE);
  895.         formTypeField.addValue(MUCInitialPresence.NAMESPACE + "#request");
  896.         form.addField(formTypeField);
  897.         FormField requestVoiceField = new FormField("muc#role");
  898.         requestVoiceField.setType(FormField.Type.text_single);
  899.         requestVoiceField.setLabel("Requested role");
  900.         requestVoiceField.addValue("participant");
  901.         form.addField(requestVoiceField);
  902.         Message message = new Message(room);
  903.         message.addExtension(form);
  904.         connection.sendStanza(message);
  905.     }

  906.     /**
  907.      * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage
  908.      * who does and does not have "voice" in the room. To have voice means that a room occupant
  909.      * is able to send messages to the room occupants.
  910.      *
  911.      * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john").
  912.      * @throws XMPPErrorException if an error occurs granting voice to a visitor. In particular, a
  913.      *      403 error can occur if the occupant that intended to grant voice is not
  914.      *      a moderator in this room (i.e. Forbidden error); or a
  915.      *      400 error can occur if the provided nickname is not present in the room.
  916.      * @throws NoResponseException if there was no response from the server.
  917.      * @throws NotConnectedException
  918.      * @throws InterruptedException
  919.      */
  920.     public void grantVoice(Collection<Resourcepart> nicknames) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  921.         changeRole(nicknames, MUCRole.participant);
  922.     }

  923.     /**
  924.      * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage
  925.      * who does and does not have "voice" in the room. To have voice means that a room occupant
  926.      * is able to send messages to the room occupants.
  927.      *
  928.      * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john").
  929.      * @throws XMPPErrorException if an error occurs granting voice to a visitor. In particular, a
  930.      *      403 error can occur if the occupant that intended to grant voice is not
  931.      *      a moderator in this room (i.e. Forbidden error); or a
  932.      *      400 error can occur if the provided nickname is not present in the room.
  933.      * @throws NoResponseException if there was no response from the server.
  934.      * @throws NotConnectedException
  935.      * @throws InterruptedException
  936.      */
  937.     public void grantVoice(Resourcepart nickname) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  938.         changeRole(nickname, MUCRole.participant, null);
  939.     }

  940.     /**
  941.      * Revokes voice from participants in the room. In a moderated room, a moderator may want to
  942.      * revoke an occupant's privileges to speak. To have voice means that a room occupant
  943.      * is able to send messages to the room occupants.
  944.      *
  945.      * @param nicknames the nicknames of the participants to revoke voice (e.g. "john").
  946.      * @throws XMPPErrorException if an error occurs revoking voice from a participant. In particular, a
  947.      *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
  948.      *      was tried to revoke his voice (i.e. Not Allowed error); or a
  949.      *      400 error can occur if the provided nickname is not present in the room.
  950.      * @throws NoResponseException if there was no response from the server.
  951.      * @throws NotConnectedException
  952.      * @throws InterruptedException
  953.      */
  954.     public void revokeVoice(Collection<Resourcepart> nicknames) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  955.         changeRole(nicknames, MUCRole.visitor);
  956.     }

  957.     /**
  958.      * Revokes voice from a participant in the room. In a moderated room, a moderator may want to
  959.      * revoke an occupant's privileges to speak. To have voice means that a room occupant
  960.      * is able to send messages to the room occupants.
  961.      *
  962.      * @param nickname the nickname of the participant to revoke voice (e.g. "john").
  963.      * @throws XMPPErrorException if an error occurs revoking voice from a participant. In particular, a
  964.      *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
  965.      *      was tried to revoke his voice (i.e. Not Allowed error); or a
  966.      *      400 error can occur if the provided nickname is not present in the room.
  967.      * @throws NoResponseException if there was no response from the server.
  968.      * @throws NotConnectedException
  969.      * @throws InterruptedException
  970.      */
  971.     public void revokeVoice(Resourcepart nickname) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  972.         changeRole(nickname, MUCRole.visitor, null);
  973.     }

  974.     /**
  975.      * Bans users from the room. An admin or owner of the room can ban users from a room. This
  976.      * means that the banned user will no longer be able to join the room unless the ban has been
  977.      * removed. If the banned user was present in the room then he/she will be removed from the
  978.      * room and notified that he/she was banned along with the reason (if provided) and the bare
  979.      * XMPP user ID of the user who initiated the ban.
  980.      *
  981.      * @param jids the bare XMPP user IDs of the users to ban.
  982.      * @throws XMPPErrorException if an error occurs banning a user. In particular, a
  983.      *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
  984.      *      was tried to be banned (i.e. Not Allowed error).
  985.      * @throws NoResponseException if there was no response from the server.
  986.      * @throws NotConnectedException
  987.      * @throws InterruptedException
  988.      */
  989.     public void banUsers(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  990.         changeAffiliationByAdmin(jids, MUCAffiliation.outcast);
  991.     }

  992.     /**
  993.      * Bans a user from the room. An admin or owner of the room can ban users from a room. This
  994.      * means that the banned user will no longer be able to join the room unless the ban has been
  995.      * removed. If the banned user was present in the room then he/she will be removed from the
  996.      * room and notified that he/she was banned along with the reason (if provided) and the bare
  997.      * XMPP user ID of the user who initiated the ban.
  998.      *
  999.      * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org").
  1000.      * @param reason the optional reason why the user was banned.
  1001.      * @throws XMPPErrorException if an error occurs banning a user. In particular, a
  1002.      *      405 error can occur if a moderator or a user with an affiliation of "owner" or "admin"
  1003.      *      was tried to be banned (i.e. Not Allowed error).
  1004.      * @throws NoResponseException if there was no response from the server.
  1005.      * @throws NotConnectedException
  1006.      * @throws InterruptedException
  1007.      */
  1008.     public void banUser(Jid jid, String reason) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1009.         changeAffiliationByAdmin(jid, MUCAffiliation.outcast, reason);
  1010.     }

  1011.     /**
  1012.      * Grants membership to other users. Only administrators are able to grant membership. A user
  1013.      * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
  1014.      * that a user cannot enter without being on the member list).
  1015.      *
  1016.      * @param jids the XMPP user IDs of the users to grant membership.
  1017.      * @throws XMPPErrorException if an error occurs granting membership to a user.
  1018.      * @throws NoResponseException if there was no response from the server.
  1019.      * @throws NotConnectedException
  1020.      * @throws InterruptedException
  1021.      */
  1022.     public void grantMembership(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1023.         changeAffiliationByAdmin(jids, MUCAffiliation.member);
  1024.     }

  1025.     /**
  1026.      * Grants membership to a user. Only administrators are able to grant membership. A user
  1027.      * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
  1028.      * that a user cannot enter without being on the member list).
  1029.      *
  1030.      * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org").
  1031.      * @throws XMPPErrorException if an error occurs granting membership to a user.
  1032.      * @throws NoResponseException if there was no response from the server.
  1033.      * @throws NotConnectedException
  1034.      * @throws InterruptedException
  1035.      */
  1036.     public void grantMembership(Jid jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1037.         changeAffiliationByAdmin(jid, MUCAffiliation.member, null);
  1038.     }

  1039.     /**
  1040.      * Revokes users' membership. Only administrators are able to revoke membership. A user
  1041.      * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
  1042.      * that a user cannot enter without being on the member list). If the user is in the room and
  1043.      * the room is of type members-only then the user will be removed from the room.
  1044.      *
  1045.      * @param jids the bare XMPP user IDs of the users to revoke membership.
  1046.      * @throws XMPPErrorException if an error occurs revoking membership to a user.
  1047.      * @throws NoResponseException if there was no response from the server.
  1048.      * @throws NotConnectedException
  1049.      * @throws InterruptedException
  1050.      */
  1051.     public void revokeMembership(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1052.         changeAffiliationByAdmin(jids, MUCAffiliation.none);
  1053.     }

  1054.     /**
  1055.      * Revokes a user's membership. Only administrators are able to revoke membership. A user
  1056.      * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room
  1057.      * that a user cannot enter without being on the member list). If the user is in the room and
  1058.      * the room is of type members-only then the user will be removed from the room.
  1059.      *
  1060.      * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org").
  1061.      * @throws XMPPErrorException if an error occurs revoking membership to a user.
  1062.      * @throws NoResponseException if there was no response from the server.
  1063.      * @throws NotConnectedException
  1064.      * @throws InterruptedException
  1065.      */
  1066.     public void revokeMembership(Jid jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1067.         changeAffiliationByAdmin(jid, MUCAffiliation.none, null);
  1068.     }

  1069.     /**
  1070.      * Grants moderator privileges to participants or visitors. Room administrators may grant
  1071.      * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite
  1072.      * other users, modify room's subject plus all the partcipants privileges.
  1073.      *
  1074.      * @param nicknames the nicknames of the occupants to grant moderator privileges.
  1075.      * @throws XMPPErrorException if an error occurs granting moderator privileges to a user.
  1076.      * @throws NoResponseException if there was no response from the server.
  1077.      * @throws NotConnectedException
  1078.      * @throws InterruptedException
  1079.      */
  1080.     public void grantModerator(Collection<Resourcepart> nicknames) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1081.         changeRole(nicknames, MUCRole.moderator);
  1082.     }

  1083.     /**
  1084.      * Grants moderator privileges to a participant or visitor. Room administrators may grant
  1085.      * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite
  1086.      * other users, modify room's subject plus all the partcipants privileges.
  1087.      *
  1088.      * @param nickname the nickname of the occupant to grant moderator privileges.
  1089.      * @throws XMPPErrorException if an error occurs granting moderator privileges to a user.
  1090.      * @throws NoResponseException if there was no response from the server.
  1091.      * @throws NotConnectedException
  1092.      * @throws InterruptedException
  1093.      */
  1094.     public void grantModerator(Resourcepart nickname) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1095.         changeRole(nickname, MUCRole.moderator, null);
  1096.     }

  1097.     /**
  1098.      * Revokes moderator privileges from other users. The occupant that loses moderator
  1099.      * privileges will become a participant. Room administrators may revoke moderator privileges
  1100.      * only to occupants whose affiliation is member or none. This means that an administrator is
  1101.      * not allowed to revoke moderator privileges from other room administrators or owners.
  1102.      *
  1103.      * @param nicknames the nicknames of the occupants to revoke moderator privileges.
  1104.      * @throws XMPPErrorException if an error occurs revoking moderator privileges from a user.
  1105.      * @throws NoResponseException if there was no response from the server.
  1106.      * @throws NotConnectedException
  1107.      * @throws InterruptedException
  1108.      */
  1109.     public void revokeModerator(Collection<Resourcepart> nicknames) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1110.         changeRole(nicknames, MUCRole.participant);
  1111.     }

  1112.     /**
  1113.      * Revokes moderator privileges from another user. The occupant that loses moderator
  1114.      * privileges will become a participant. Room administrators may revoke moderator privileges
  1115.      * only to occupants whose affiliation is member or none. This means that an administrator is
  1116.      * not allowed to revoke moderator privileges from other room administrators or owners.
  1117.      *
  1118.      * @param nickname the nickname of the occupant to revoke moderator privileges.
  1119.      * @throws XMPPErrorException if an error occurs revoking moderator privileges from a user.
  1120.      * @throws NoResponseException if there was no response from the server.
  1121.      * @throws NotConnectedException
  1122.      * @throws InterruptedException
  1123.      */
  1124.     public void revokeModerator(Resourcepart nickname) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1125.         changeRole(nickname, MUCRole.participant, null);
  1126.     }

  1127.     /**
  1128.      * Grants ownership privileges to other users. Room owners may grant ownership privileges.
  1129.      * Some room implementations will not allow to grant ownership privileges to other users.
  1130.      * An owner is allowed to change defining room features as well as perform all administrative
  1131.      * functions.
  1132.      *
  1133.      * @param jids the collection of bare XMPP user IDs of the users to grant ownership.
  1134.      * @throws XMPPErrorException if an error occurs granting ownership privileges to a user.
  1135.      * @throws NoResponseException if there was no response from the server.
  1136.      * @throws NotConnectedException
  1137.      * @throws InterruptedException
  1138.      */
  1139.     public void grantOwnership(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1140.         changeAffiliationByAdmin(jids, MUCAffiliation.owner);
  1141.     }

  1142.     /**
  1143.      * Grants ownership privileges to another user. Room owners may grant ownership privileges.
  1144.      * Some room implementations will not allow to grant ownership privileges to other users.
  1145.      * An owner is allowed to change defining room features as well as perform all administrative
  1146.      * functions.
  1147.      *
  1148.      * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org").
  1149.      * @throws XMPPErrorException if an error occurs granting ownership privileges to a user.
  1150.      * @throws NoResponseException if there was no response from the server.
  1151.      * @throws NotConnectedException
  1152.      * @throws InterruptedException
  1153.      */
  1154.     public void grantOwnership(Jid jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1155.         changeAffiliationByAdmin(jid, MUCAffiliation.owner, null);
  1156.     }

  1157.     /**
  1158.      * Revokes ownership privileges from other users. The occupant that loses ownership
  1159.      * privileges will become an administrator. Room owners may revoke ownership privileges.
  1160.      * Some room implementations will not allow to grant ownership privileges to other users.
  1161.      *
  1162.      * @param jids the bare XMPP user IDs of the users to revoke ownership.
  1163.      * @throws XMPPErrorException if an error occurs revoking ownership privileges from a user.
  1164.      * @throws NoResponseException if there was no response from the server.
  1165.      * @throws NotConnectedException
  1166.      * @throws InterruptedException
  1167.      */
  1168.     public void revokeOwnership(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1169.         changeAffiliationByAdmin(jids, MUCAffiliation.admin);
  1170.     }

  1171.     /**
  1172.      * Revokes ownership privileges from another user. The occupant that loses ownership
  1173.      * privileges will become an administrator. Room owners may revoke ownership privileges.
  1174.      * Some room implementations will not allow to grant ownership privileges to other users.
  1175.      *
  1176.      * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org").
  1177.      * @throws XMPPErrorException if an error occurs revoking ownership privileges from a user.
  1178.      * @throws NoResponseException if there was no response from the server.
  1179.      * @throws NotConnectedException
  1180.      * @throws InterruptedException
  1181.      */
  1182.     public void revokeOwnership(Jid jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1183.         changeAffiliationByAdmin(jid, MUCAffiliation.admin, null);
  1184.     }

  1185.     /**
  1186.      * Grants administrator privileges to other users. Room owners may grant administrator
  1187.      * privileges to a member or unaffiliated user. An administrator is allowed to perform
  1188.      * administrative functions such as banning users and edit moderator list.
  1189.      *
  1190.      * @param jids the bare XMPP user IDs of the users to grant administrator privileges.
  1191.      * @throws XMPPErrorException if an error occurs granting administrator privileges to a user.
  1192.      * @throws NoResponseException if there was no response from the server.
  1193.      * @throws NotConnectedException
  1194.      * @throws InterruptedException
  1195.      */
  1196.     public void grantAdmin(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1197.         changeAffiliationByAdmin(jids, MUCAffiliation.admin);
  1198.     }

  1199.     /**
  1200.      * Grants administrator privileges to another user. Room owners may grant administrator
  1201.      * privileges to a member or unaffiliated user. An administrator is allowed to perform
  1202.      * administrative functions such as banning users and edit moderator list.
  1203.      *
  1204.      * @param jid the bare XMPP user ID of the user to grant administrator privileges
  1205.      * (e.g. "user@host.org").
  1206.      * @throws XMPPErrorException if an error occurs granting administrator privileges to a user.
  1207.      * @throws NoResponseException if there was no response from the server.
  1208.      * @throws NotConnectedException
  1209.      * @throws InterruptedException
  1210.      */
  1211.     public void grantAdmin(Jid jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1212.         changeAffiliationByAdmin(jid, MUCAffiliation.admin);
  1213.     }

  1214.     /**
  1215.      * Revokes administrator privileges from users. The occupant that loses administrator
  1216.      * privileges will become a member. Room owners may revoke administrator privileges from
  1217.      * a member or unaffiliated user.
  1218.      *
  1219.      * @param jids the bare XMPP user IDs of the user to revoke administrator privileges.
  1220.      * @throws XMPPErrorException if an error occurs revoking administrator privileges from a user.
  1221.      * @throws NoResponseException if there was no response from the server.
  1222.      * @throws NotConnectedException
  1223.      * @throws InterruptedException
  1224.      */
  1225.     public void revokeAdmin(Collection<? extends Jid> jids) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1226.         changeAffiliationByAdmin(jids, MUCAffiliation.admin);
  1227.     }

  1228.     /**
  1229.      * Revokes administrator privileges from a user. The occupant that loses administrator
  1230.      * privileges will become a member. Room owners may revoke administrator privileges from
  1231.      * a member or unaffiliated user.
  1232.      *
  1233.      * @param jid the bare XMPP user ID of the user to revoke administrator privileges
  1234.      * (e.g. "user@host.org").
  1235.      * @throws XMPPErrorException if an error occurs revoking administrator privileges from a user.
  1236.      * @throws NoResponseException if there was no response from the server.
  1237.      * @throws NotConnectedException
  1238.      * @throws InterruptedException
  1239.      */
  1240.     public void revokeAdmin(JidWithLocalpart jid) throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException {
  1241.         changeAffiliationByAdmin(jid, MUCAffiliation.member);
  1242.     }

  1243.     /**
  1244.      * Tries to change the affiliation with an 'muc#admin' namespace
  1245.      *
  1246.      * @param jid
  1247.      * @param affiliation
  1248.      * @throws XMPPErrorException
  1249.      * @throws NoResponseException
  1250.      * @throws NotConnectedException
  1251.      * @throws InterruptedException
  1252.      */
  1253.     private void changeAffiliationByAdmin(Jid jid, MUCAffiliation affiliation)
  1254.                     throws NoResponseException, XMPPErrorException,
  1255.                     NotConnectedException, InterruptedException {
  1256.         changeAffiliationByAdmin(jid, affiliation, null);
  1257.     }

  1258.     /**
  1259.      * Tries to change the affiliation with an 'muc#admin' namespace
  1260.      *
  1261.      * @param jid
  1262.      * @param affiliation
  1263.      * @param reason the reason for the affiliation change (optional)
  1264.      * @throws XMPPErrorException
  1265.      * @throws NoResponseException
  1266.      * @throws NotConnectedException
  1267.      * @throws InterruptedException
  1268.      */
  1269.     private void changeAffiliationByAdmin(Jid jid, MUCAffiliation affiliation, String reason) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  1270.             {
  1271.         MUCAdmin iq = new MUCAdmin();
  1272.         iq.setTo(room);
  1273.         iq.setType(IQ.Type.set);
  1274.         // Set the new affiliation.
  1275.         MUCItem item = new MUCItem(affiliation, jid, reason);
  1276.         iq.addItem(item);

  1277.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  1278.     }

  1279.     private void changeAffiliationByAdmin(Collection<? extends Jid> jids, MUCAffiliation affiliation)
  1280.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1281.         MUCAdmin iq = new MUCAdmin();
  1282.         iq.setTo(room);
  1283.         iq.setType(IQ.Type.set);
  1284.         for (Jid jid : jids) {
  1285.             // Set the new affiliation.
  1286.             MUCItem item = new MUCItem(affiliation, jid);
  1287.             iq.addItem(item);
  1288.         }

  1289.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  1290.     }

  1291.     private void changeRole(Resourcepart nickname, MUCRole role, String reason) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1292.         MUCAdmin iq = new MUCAdmin();
  1293.         iq.setTo(room);
  1294.         iq.setType(IQ.Type.set);
  1295.         // Set the new role.
  1296.         MUCItem item = new MUCItem(role, nickname, reason);
  1297.         iq.addItem(item);

  1298.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  1299.     }

  1300.     private void changeRole(Collection<Resourcepart> nicknames, MUCRole role) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  1301.         MUCAdmin iq = new MUCAdmin();
  1302.         iq.setTo(room);
  1303.         iq.setType(IQ.Type.set);
  1304.         for (Resourcepart nickname : nicknames) {
  1305.             // Set the new role.
  1306.             MUCItem item = new MUCItem(role, nickname);
  1307.             iq.addItem(item);
  1308.         }

  1309.         connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  1310.     }

  1311.     /**
  1312.      * Returns the number of occupants in the group chat.<p>
  1313.      *
  1314.      * Note: this value will only be accurate after joining the group chat, and
  1315.      * may fluctuate over time. If you query this value directly after joining the
  1316.      * group chat it may not be accurate, as it takes a certain amount of time for
  1317.      * the server to send all presence packets to this client.
  1318.      *
  1319.      * @return the number of occupants in the group chat.
  1320.      */
  1321.     public int getOccupantsCount() {
  1322.         return occupantsMap.size();
  1323.     }

  1324.     /**
  1325.      * Returns an List  for the list of fully qualified occupants
  1326.      * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser".
  1327.      * Typically, a client would only display the nickname of the occupant. To
  1328.      * get the nickname from the fully qualified name, use the
  1329.      * {@link org.jxmpp.util.XmppStringUtils#parseResource(String)} method.
  1330.      * Note: this value will only be accurate after joining the group chat, and may
  1331.      * fluctuate over time.
  1332.      *
  1333.      * @return a List of the occupants in the group chat.
  1334.      */
  1335.     public List<FullJid> getOccupants() {
  1336.         return new ArrayList<>(occupantsMap.keySet());
  1337.     }

  1338.     /**
  1339.      * Returns the presence info for a particular user, or <tt>null</tt> if the user
  1340.      * is not in the room.<p>
  1341.      *
  1342.      * @param user the room occupant to search for his presence. The format of user must
  1343.      * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch).
  1344.      * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable
  1345.      *      or if no presence information is available.
  1346.      */
  1347.     public Presence getOccupantPresence(String user) {
  1348.         return occupantsMap.get(user);
  1349.     }

  1350.     /**
  1351.      * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the
  1352.      * user is not in the room. The Occupant object may include information such as full
  1353.      * JID of the user as well as the role and affiliation of the user in the room.<p>
  1354.      *
  1355.      * @param user the room occupant to search for his presence. The format of user must
  1356.      * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch).
  1357.      * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room).
  1358.      */
  1359.     public Occupant getOccupant(String user) {
  1360.         Presence presence = occupantsMap.get(user);
  1361.         if (presence != null) {
  1362.             return new Occupant(presence);
  1363.         }
  1364.         return null;
  1365.     }

  1366.     /**
  1367.      * Adds a packet listener that will be notified of any new Presence packets
  1368.      * sent to the group chat. Using a listener is a suitable way to know when the list
  1369.      * of occupants should be re-loaded due to any changes.
  1370.      *
  1371.      * @param listener a packet listener that will be notified of any presence packets
  1372.      *      sent to the group chat.
  1373.      * @return true if the listener was not already added.
  1374.      */
  1375.     public boolean addParticipantListener(PresenceListener listener) {
  1376.         return presenceListeners.add(listener);
  1377.     }

  1378.     /**
  1379.      * Removes a packet listener that was being notified of any new Presence packets
  1380.      * sent to the group chat.
  1381.      *
  1382.      * @param listener a packet listener that was being notified of any presence packets
  1383.      *      sent to the group chat.
  1384.      * @return true if the listener was removed, otherwise the listener was not added previously.
  1385.      */
  1386.     public boolean removeParticipantListener(PresenceListener listener) {
  1387.         return presenceListeners.remove(listener);
  1388.     }

  1389.     /**
  1390.      * Returns a list of <code>Affiliate</code> with the room owners.
  1391.      *
  1392.      * @return a list of <code>Affiliate</code> with the room owners.
  1393.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1394.      * @throws NoResponseException if there was no response from the server.
  1395.      * @throws NotConnectedException
  1396.      * @throws InterruptedException
  1397.      */
  1398.     public List<Affiliate> getOwners() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1399.         return getAffiliatesByAdmin(MUCAffiliation.owner);
  1400.     }

  1401.     /**
  1402.      * Returns a list of <code>Affiliate</code> with the room administrators.
  1403.      *
  1404.      * @return a list of <code>Affiliate</code> with the room administrators.
  1405.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1406.      * @throws NoResponseException if there was no response from the server.
  1407.      * @throws NotConnectedException
  1408.      * @throws InterruptedException
  1409.      */
  1410.     public List<Affiliate> getAdmins() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1411.         return getAffiliatesByAdmin(MUCAffiliation.admin);
  1412.     }

  1413.     /**
  1414.      * Returns a list of <code>Affiliate</code> with the room members.
  1415.      *
  1416.      * @return a list of <code>Affiliate</code> with the room members.
  1417.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1418.      * @throws NoResponseException if there was no response from the server.
  1419.      * @throws NotConnectedException
  1420.      * @throws InterruptedException
  1421.      */
  1422.     public List<Affiliate> getMembers() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  1423.         return getAffiliatesByAdmin(MUCAffiliation.member);
  1424.     }

  1425.     /**
  1426.      * Returns a list of <code>Affiliate</code> with the room outcasts.
  1427.      *
  1428.      * @return a list of <code>Affiliate</code> with the room outcasts.
  1429.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1430.      * @throws NoResponseException if there was no response from the server.
  1431.      * @throws NotConnectedException
  1432.      * @throws InterruptedException
  1433.      */
  1434.     public List<Affiliate> getOutcasts() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1435.         return getAffiliatesByAdmin(MUCAffiliation.outcast);
  1436.     }

  1437.     /**
  1438.      * Returns a collection of <code>Affiliate</code> that have the specified room affiliation
  1439.      * sending a request in the admin namespace.
  1440.      *
  1441.      * @param affiliation the affiliation of the users in the room.
  1442.      * @return a collection of <code>Affiliate</code> that have the specified room affiliation.
  1443.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1444.      * @throws NoResponseException if there was no response from the server.
  1445.      * @throws NotConnectedException
  1446.      * @throws InterruptedException
  1447.      */
  1448.     private List<Affiliate> getAffiliatesByAdmin(MUCAffiliation affiliation) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1449.         MUCAdmin iq = new MUCAdmin();
  1450.         iq.setTo(room);
  1451.         iq.setType(IQ.Type.get);
  1452.         // Set the specified affiliation. This may request the list of owners/admins/members/outcasts.
  1453.         MUCItem item = new MUCItem(affiliation);
  1454.         iq.addItem(item);

  1455.         MUCAdmin answer = (MUCAdmin) connection.createPacketCollectorAndSend(iq).nextResultOrThrow();

  1456.         // Get the list of affiliates from the server's answer
  1457.         List<Affiliate> affiliates = new ArrayList<Affiliate>();
  1458.         for (MUCItem mucadminItem : answer.getItems()) {
  1459.             affiliates.add(new Affiliate(mucadminItem));
  1460.         }
  1461.         return affiliates;
  1462.     }

  1463.     /**
  1464.      * Returns a list of <code>Occupant</code> with the room moderators.
  1465.      *
  1466.      * @return a list of <code>Occupant</code> with the room moderators.
  1467.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1468.      * @throws NoResponseException if there was no response from the server.
  1469.      * @throws NotConnectedException
  1470.      * @throws InterruptedException
  1471.      */
  1472.     public List<Occupant> getModerators() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1473.         return getOccupants(MUCRole.moderator);
  1474.     }

  1475.     /**
  1476.      * Returns a list of <code>Occupant</code> with the room participants.
  1477.      *
  1478.      * @return a list of <code>Occupant</code> with the room participants.
  1479.      * @throws XMPPErrorException if you don't have enough privileges to get this information.
  1480.      * @throws NoResponseException if there was no response from the server.
  1481.      * @throws NotConnectedException
  1482.      * @throws InterruptedException
  1483.      */
  1484.     public List<Occupant> getParticipants() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1485.         return getOccupants(MUCRole.participant);
  1486.     }

  1487.     /**
  1488.      * Returns a list of <code>Occupant</code> that have the specified room role.
  1489.      *
  1490.      * @param role the role of the occupant in the room.
  1491.      * @return a list of <code>Occupant</code> that have the specified room role.
  1492.      * @throws XMPPErrorException if an error occured while performing the request to the server or you
  1493.      *         don't have enough privileges to get this information.
  1494.      * @throws NoResponseException if there was no response from the server.
  1495.      * @throws NotConnectedException
  1496.      * @throws InterruptedException
  1497.      */
  1498.     private List<Occupant> getOccupants(MUCRole role) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1499.         MUCAdmin iq = new MUCAdmin();
  1500.         iq.setTo(room);
  1501.         iq.setType(IQ.Type.get);
  1502.         // Set the specified role. This may request the list of moderators/participants.
  1503.         MUCItem item = new MUCItem(role);
  1504.         iq.addItem(item);

  1505.         MUCAdmin answer = (MUCAdmin) connection.createPacketCollectorAndSend(iq).nextResultOrThrow();
  1506.         // Get the list of participants from the server's answer
  1507.         List<Occupant> participants = new ArrayList<Occupant>();
  1508.         for (MUCItem mucadminItem : answer.getItems()) {
  1509.             participants.add(new Occupant(mucadminItem));
  1510.         }
  1511.         return participants;
  1512.     }

  1513.     /**
  1514.      * Sends a message to the chat room.
  1515.      *
  1516.      * @param text the text of the message to send.
  1517.      * @throws NotConnectedException
  1518.      * @throws InterruptedException
  1519.      */
  1520.     public void sendMessage(String text) throws NotConnectedException, InterruptedException {
  1521.         Message message = createMessage();
  1522.         message.setBody(text);
  1523.         connection.sendStanza(message);
  1524.     }

  1525.     /**
  1526.      * Returns a new Chat for sending private messages to a given room occupant.
  1527.      * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server
  1528.      * service will change the 'from' address to the sender's room JID and delivering the message
  1529.      * to the intended recipient's full JID.
  1530.      *
  1531.      * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul').
  1532.      * @param listener the listener is a message listener that will handle messages for the newly
  1533.      * created chat.
  1534.      * @return new Chat for sending private messages to a given room occupant.
  1535.      */
  1536.     public Chat createPrivateChat(FullJid occupant, ChatMessageListener listener) {
  1537.         return ChatManager.getInstanceFor(connection).createChat(occupant, listener);
  1538.     }

  1539.     /**
  1540.      * Creates a new Message to send to the chat room.
  1541.      *
  1542.      * @return a new Message addressed to the chat room.
  1543.      */
  1544.     public Message createMessage() {
  1545.         return new Message(room, Message.Type.groupchat);
  1546.     }

  1547.     /**
  1548.      * Sends a Message to the chat room.
  1549.      *
  1550.      * @param message the message.
  1551.      * @throws NotConnectedException
  1552.      * @throws InterruptedException
  1553.      */
  1554.     public void sendMessage(Message message) throws NotConnectedException, InterruptedException {
  1555.         message.setTo(room);
  1556.         message.setType(Message.Type.groupchat);
  1557.         connection.sendStanza(message);
  1558.     }

  1559.     /**
  1560.     * Polls for and returns the next message, or <tt>null</tt> if there isn't
  1561.     * a message immediately available. This method provides significantly different
  1562.     * functionalty than the {@link #nextMessage()} method since it's non-blocking.
  1563.     * In other words, the method call will always return immediately, whereas the
  1564.     * nextMessage method will return only when a message is available (or after
  1565.     * a specific timeout).
  1566.     *
  1567.     * @return the next message if one is immediately available and
  1568.     *      <tt>null</tt> otherwise.
  1569.      * @throws MUCNotJoinedException
  1570.     */
  1571.     public Message pollMessage() throws MUCNotJoinedException {
  1572.         if (messageCollector == null) {
  1573.             throw new MUCNotJoinedException(this);
  1574.         }
  1575.         return messageCollector.pollResult();
  1576.     }

  1577.     /**
  1578.      * Returns the next available message in the chat. The method call will block
  1579.      * (not return) until a message is available.
  1580.      *
  1581.      * @return the next message.
  1582.      * @throws MUCNotJoinedException
  1583.      * @throws InterruptedException
  1584.      */
  1585.     public Message nextMessage() throws MUCNotJoinedException, InterruptedException {
  1586.         if (messageCollector == null) {
  1587.             throw new MUCNotJoinedException(this);
  1588.         }
  1589.         return  messageCollector.nextResult();
  1590.     }

  1591.     /**
  1592.      * Returns the next available message in the chat. The method call will block
  1593.      * (not return) until a packet is available or the <tt>timeout</tt> has elapased.
  1594.      * If the timeout elapses without a result, <tt>null</tt> will be returned.
  1595.      *
  1596.      * @param timeout the maximum amount of time to wait for the next message.
  1597.      * @return the next message, or <tt>null</tt> if the timeout elapses without a
  1598.      *      message becoming available.
  1599.      * @throws MUCNotJoinedException
  1600.      * @throws InterruptedException
  1601.      */
  1602.     public Message nextMessage(long timeout) throws MUCNotJoinedException, InterruptedException {
  1603.         if (messageCollector == null) {
  1604.             throw new MUCNotJoinedException(this);
  1605.         }
  1606.         return messageCollector.nextResult(timeout);
  1607.     }

  1608.     /**
  1609.      * Adds a packet listener that will be notified of any new messages in the
  1610.      * group chat. Only "group chat" messages addressed to this group chat will
  1611.      * be delivered to the listener. If you wish to listen for other packets
  1612.      * that may be associated with this group chat, you should register a
  1613.      * PacketListener directly with the XMPPConnection with the appropriate
  1614.      * PacketListener.
  1615.      *
  1616.      * @param listener a packet listener.
  1617.      * @return true if the listener was not already added.
  1618.      */
  1619.     public boolean addMessageListener(MessageListener listener) {
  1620.         return messageListeners.add(listener);
  1621.     }

  1622.     /**
  1623.      * Removes a packet listener that was being notified of any new messages in the
  1624.      * multi user chat. Only "group chat" messages addressed to this multi user chat were
  1625.      * being delivered to the listener.
  1626.      *
  1627.      * @param listener a packet listener.
  1628.      * @return true if the listener was removed, otherwise the listener was not added previously.
  1629.      */
  1630.     public boolean removeMessageListener(MessageListener listener) {
  1631.         return messageListeners.remove(listener);
  1632.     }

  1633.     /**
  1634.      * Changes the subject within the room. As a default, only users with a role of "moderator"
  1635.      * are allowed to change the subject in a room. Although some rooms may be configured to
  1636.      * allow a mere participant or even a visitor to change the subject.
  1637.      *
  1638.      * @param subject the new room's subject to set.
  1639.      * @throws XMPPErrorException if someone without appropriate privileges attempts to change the
  1640.      *          room subject will throw an error with code 403 (i.e. Forbidden)
  1641.      * @throws NoResponseException if there was no response from the server.
  1642.      * @throws NotConnectedException
  1643.      * @throws InterruptedException
  1644.      */
  1645.     public void changeSubject(final String subject) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  1646.         Message message = createMessage();
  1647.         message.setSubject(subject);
  1648.         // Wait for an error or confirmation message back from the server.
  1649.         StanzaFilter responseFilter = new AndFilter(fromRoomGroupchatFilter, new StanzaFilter() {
  1650.             @Override
  1651.             public boolean accept(Stanza packet) {
  1652.                 Message msg = (Message) packet;
  1653.                 return subject.equals(msg.getSubject());
  1654.             }
  1655.         });
  1656.         PacketCollector response = connection.createPacketCollectorAndSend(responseFilter, message);
  1657.         // Wait up to a certain number of seconds for a reply.
  1658.         response.nextResultOrThrow();
  1659.     }

  1660.     /**
  1661.      * Remove the connection callbacks (PacketListener, PacketInterceptor, PacketCollector) used by this MUC from the
  1662.      * connection.
  1663.      */
  1664.     private void removeConnectionCallbacks() {
  1665.         connection.removeSyncStanzaListener(messageListener);
  1666.         connection.removeSyncStanzaListener(presenceListener);
  1667.         connection.removeSyncStanzaListener(declinesListener);
  1668.         connection.removePacketInterceptor(presenceInterceptor);
  1669.         if (messageCollector != null) {
  1670.             messageCollector.cancel();
  1671.             messageCollector = null;
  1672.         }
  1673.     }

  1674.     /**
  1675.      * Remove all callbacks and resources necessary when the user has left the room for some reason.
  1676.      */
  1677.     private synchronized void userHasLeft() {
  1678.         // Update the list of joined rooms
  1679.         multiUserChatManager.removeJoinedRoom(room);
  1680.         removeConnectionCallbacks();
  1681.     }

  1682.     /**
  1683.      * Adds a listener that will be notified of changes in your status in the room
  1684.      * such as the user being kicked, banned, or granted admin permissions.
  1685.      *
  1686.      * @param listener a user status listener.
  1687.      * @return true if the user status listener was not already added.
  1688.      */
  1689.     public boolean addUserStatusListener(UserStatusListener listener) {
  1690.         return userStatusListeners.add(listener);
  1691.     }

  1692.     /**
  1693.      * Removes a listener that was being notified of changes in your status in the room
  1694.      * such as the user being kicked, banned, or granted admin permissions.
  1695.      *
  1696.      * @param listener a user status listener.
  1697.      * @return true if the listener was registered and is now removed.
  1698.      */
  1699.     public boolean removeUserStatusListener(UserStatusListener listener) {
  1700.         return userStatusListeners.remove(listener);
  1701.     }

  1702.     /**
  1703.      * Adds a listener that will be notified of changes in occupants status in the room
  1704.      * such as the user being kicked, banned, or granted admin permissions.
  1705.      *
  1706.      * @param listener a participant status listener.
  1707.      * @return true if the listener was not already added.
  1708.      */
  1709.     public boolean addParticipantStatusListener(ParticipantStatusListener listener) {
  1710.         return participantStatusListeners.add(listener);
  1711.     }

  1712.     /**
  1713.      * Removes a listener that was being notified of changes in occupants status in the room
  1714.      * such as the user being kicked, banned, or granted admin permissions.
  1715.      *
  1716.      * @param listener a participant status listener.
  1717.      * @return true if the listener was registered and is now removed.
  1718.      */
  1719.     public boolean removeParticipantStatusListener(ParticipantStatusListener listener) {
  1720.         return participantStatusListeners.remove(listener);
  1721.     }

  1722.     /**
  1723.      * Fires notification events if the role of a room occupant has changed. If the occupant that
  1724.      * changed his role is your occupant then the <code>UserStatusListeners</code> added to this
  1725.      * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed
  1726.      * his role is not yours then the <code>ParticipantStatusListeners</code> added to this
  1727.      * <code>MultiUserChat</code> will be fired. The following table shows the events that will
  1728.      * be fired depending on the previous and new role of the occupant.
  1729.      *
  1730.      * <pre>
  1731.      * <table border="1">
  1732.      * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
  1733.      *
  1734.      * <tr><td>None</td><td>Visitor</td><td>--</td></tr>
  1735.      * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr>
  1736.      * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr>
  1737.      *
  1738.      * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr>
  1739.      * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
  1740.      * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr>
  1741.      *
  1742.      * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr>
  1743.      * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr>
  1744.      * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr>
  1745.      *
  1746.      * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr>
  1747.      * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr>
  1748.      * <tr><td>Participant</td><td>None</td><td>kicked</td></tr>
  1749.      * </table>
  1750.      * </pre>
  1751.      *
  1752.      * @param oldRole the previous role of the user in the room before receiving the new presence
  1753.      * @param newRole the new role of the user in the room after receiving the new presence
  1754.      * @param isUserModification whether the received presence is about your user in the room or not
  1755.      * @param from the occupant whose role in the room has changed
  1756.      * (e.g. room@conference.jabber.org/nick).
  1757.      */
  1758.     private void checkRoleModifications(
  1759.         MUCRole oldRole,
  1760.         MUCRole newRole,
  1761.         boolean isUserModification,
  1762.         FullJid from) {
  1763.         // Voice was granted to a visitor
  1764.         if (("visitor".equals(oldRole) || "none".equals(oldRole))
  1765.             && "participant".equals(newRole)) {
  1766.             if (isUserModification) {
  1767.                 for (UserStatusListener listener : userStatusListeners) {
  1768.                     listener.voiceGranted();
  1769.                 }
  1770.             }
  1771.             else {
  1772.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1773.                     listener.voiceGranted(from);
  1774.                 }
  1775.             }
  1776.         }
  1777.         // The participant's voice was revoked from the room
  1778.         else if (
  1779.             "participant".equals(oldRole)
  1780.                 && ("visitor".equals(newRole) || "none".equals(newRole))) {
  1781.             if (isUserModification) {
  1782.                 for (UserStatusListener listener : userStatusListeners) {
  1783.                     listener.voiceRevoked();
  1784.                 }
  1785.             }
  1786.             else {
  1787.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1788.                     listener.voiceRevoked(from);
  1789.                 }
  1790.             }
  1791.         }
  1792.         // Moderator privileges were granted to a participant
  1793.         if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) {
  1794.             if ("visitor".equals(oldRole) || "none".equals(oldRole)) {
  1795.                 if (isUserModification) {
  1796.                     for (UserStatusListener listener : userStatusListeners) {
  1797.                         listener.voiceGranted();
  1798.                     }
  1799.                 }
  1800.                 else {
  1801.                     for (ParticipantStatusListener listener : participantStatusListeners) {
  1802.                         listener.voiceGranted(from);
  1803.                     }
  1804.                 }
  1805.             }
  1806.             if (isUserModification) {
  1807.                 for (UserStatusListener listener : userStatusListeners) {
  1808.                     listener.moderatorGranted();
  1809.                 }
  1810.             }
  1811.             else {
  1812.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1813.                     listener.moderatorGranted(from);
  1814.                 }
  1815.             }
  1816.         }
  1817.         // Moderator privileges were revoked from a participant
  1818.         else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) {
  1819.             if ("visitor".equals(newRole) || "none".equals(newRole)) {
  1820.                 if (isUserModification) {
  1821.                     for (UserStatusListener listener : userStatusListeners) {
  1822.                         listener.voiceRevoked();
  1823.                     }
  1824.                 }
  1825.                 else {
  1826.                     for (ParticipantStatusListener listener : participantStatusListeners) {
  1827.                         listener.voiceRevoked(from);
  1828.                     }
  1829.                 }
  1830.             }
  1831.             if (isUserModification) {
  1832.                 for (UserStatusListener listener : userStatusListeners) {
  1833.                     listener.moderatorRevoked();
  1834.                 }
  1835.             }
  1836.             else {
  1837.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1838.                     listener.moderatorRevoked(from);
  1839.                 }
  1840.             }
  1841.         }
  1842.     }

  1843.     /**
  1844.      * Fires notification events if the affiliation of a room occupant has changed. If the
  1845.      * occupant that changed his affiliation is your occupant then the
  1846.      * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired.
  1847.      * On the other hand, if the occupant that changed his affiliation is not yours then the
  1848.      * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be
  1849.      * fired. The following table shows the events that will be fired depending on the previous
  1850.      * and new affiliation of the occupant.
  1851.      *
  1852.      * <pre>
  1853.      * <table border="1">
  1854.      * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr>
  1855.      *
  1856.      * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr>
  1857.      * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr>
  1858.      * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr>
  1859.      *
  1860.      * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr>
  1861.      * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr>
  1862.      * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr>
  1863.      *
  1864.      * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr>
  1865.      * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr>
  1866.      * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr>
  1867.      *
  1868.      * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr>
  1869.      * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr>
  1870.      * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr>
  1871.      * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr>
  1872.      * </table>
  1873.      * </pre>
  1874.      *
  1875.      * @param oldAffiliation the previous affiliation of the user in the room before receiving the
  1876.      * new presence
  1877.      * @param newAffiliation the new affiliation of the user in the room after receiving the new
  1878.      * presence
  1879.      * @param isUserModification whether the received presence is about your user in the room or not
  1880.      * @param from the occupant whose role in the room has changed
  1881.      * (e.g. room@conference.jabber.org/nick).
  1882.      */
  1883.     private void checkAffiliationModifications(
  1884.         MUCAffiliation oldAffiliation,
  1885.         MUCAffiliation newAffiliation,
  1886.         boolean isUserModification,
  1887.         FullJid from) {
  1888.         // First check for revoked affiliation and then for granted affiliations. The idea is to
  1889.         // first fire the "revoke" events and then fire the "grant" events.

  1890.         // The user's ownership to the room was revoked
  1891.         if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) {
  1892.             if (isUserModification) {
  1893.                 for (UserStatusListener listener : userStatusListeners) {
  1894.                     listener.ownershipRevoked();
  1895.                 }
  1896.             }
  1897.             else {
  1898.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1899.                     listener.ownershipRevoked(from);
  1900.                 }
  1901.             }
  1902.         }
  1903.         // The user's administrative privileges to the room were revoked
  1904.         else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) {
  1905.             if (isUserModification) {
  1906.                 for (UserStatusListener listener : userStatusListeners) {
  1907.                     listener.adminRevoked();
  1908.                 }
  1909.             }
  1910.             else {
  1911.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1912.                     listener.adminRevoked(from);
  1913.                 }
  1914.             }
  1915.         }
  1916.         // The user's membership to the room was revoked
  1917.         else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) {
  1918.             if (isUserModification) {
  1919.                 for (UserStatusListener listener : userStatusListeners) {
  1920.                     listener.membershipRevoked();
  1921.                 }
  1922.             }
  1923.             else {
  1924.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1925.                     listener.membershipRevoked(from);
  1926.                 }
  1927.             }
  1928.         }

  1929.         // The user was granted ownership to the room
  1930.         if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) {
  1931.             if (isUserModification) {
  1932.                 for (UserStatusListener listener : userStatusListeners) {
  1933.                     listener.ownershipGranted();
  1934.                 }
  1935.             }
  1936.             else {
  1937.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1938.                     listener.ownershipGranted(from);
  1939.                 }
  1940.             }
  1941.         }
  1942.         // The user was granted administrative privileges to the room
  1943.         else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) {
  1944.             if (isUserModification) {
  1945.                 for (UserStatusListener listener : userStatusListeners) {
  1946.                     listener.adminGranted();
  1947.                 }
  1948.             }
  1949.             else {
  1950.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1951.                     listener.adminGranted(from);
  1952.                 }
  1953.             }
  1954.         }
  1955.         // The user was granted membership to the room
  1956.         else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) {
  1957.             if (isUserModification) {
  1958.                 for (UserStatusListener listener : userStatusListeners) {
  1959.                     listener.membershipGranted();
  1960.                 }
  1961.             }
  1962.             else {
  1963.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1964.                     listener.membershipGranted(from);
  1965.                 }
  1966.             }
  1967.         }
  1968.     }

  1969.     /**
  1970.      * Fires events according to the received presence code.
  1971.      *
  1972.      * @param statusCodes
  1973.      * @param isUserModification
  1974.      * @param mucUser
  1975.      * @param from
  1976.      */
  1977.     private void checkPresenceCode(
  1978.         Set<Status> statusCodes,
  1979.         boolean isUserModification,
  1980.         MUCUser mucUser,
  1981.         FullJid from) {
  1982.         // Check if an occupant was kicked from the room
  1983.         if (statusCodes.contains(Status.KICKED_307)) {
  1984.             // Check if this occupant was kicked
  1985.             if (isUserModification) {
  1986.                 joined = false;
  1987.                 for (UserStatusListener listener : userStatusListeners) {
  1988.                     listener.kicked(mucUser.getItem().getActor(), mucUser.getItem().getReason());
  1989.                 }

  1990.                 // Reset occupant information.
  1991.                 occupantsMap.clear();
  1992.                 nickname = null;
  1993.                 userHasLeft();
  1994.             }
  1995.             else {
  1996.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  1997.                     listener.kicked(from, mucUser.getItem().getActor(), mucUser.getItem().getReason());
  1998.                 }
  1999.             }
  2000.         }
  2001.         // A user was banned from the room
  2002.         if (statusCodes.contains(Status.BANNED_301)) {
  2003.             // Check if this occupant was banned
  2004.             if (isUserModification) {
  2005.                 joined = false;
  2006.                 for (UserStatusListener listener : userStatusListeners) {
  2007.                     listener.banned(mucUser.getItem().getActor(), mucUser.getItem().getReason());
  2008.                 }

  2009.                 // Reset occupant information.
  2010.                 occupantsMap.clear();
  2011.                 nickname = null;
  2012.                 userHasLeft();
  2013.             }
  2014.             else {
  2015.                 for (ParticipantStatusListener listener : participantStatusListeners) {
  2016.                     listener.banned(from, mucUser.getItem().getActor(), mucUser.getItem().getReason());
  2017.                 }
  2018.             }
  2019.         }
  2020.         // A user's membership was revoked from the room
  2021.         if (statusCodes.contains(Status.REMOVED_AFFIL_CHANGE_321)) {
  2022.             // Check if this occupant's membership was revoked
  2023.             if (isUserModification) {
  2024.                 joined = false;
  2025.                 for (UserStatusListener listener : userStatusListeners) {
  2026.                     listener.membershipRevoked();
  2027.                 }

  2028.                 // Reset occupant information.
  2029.                 occupantsMap.clear();
  2030.                 nickname = null;
  2031.                 userHasLeft();
  2032.             }
  2033.         }
  2034.         // A occupant has changed his nickname in the room
  2035.         if (statusCodes.contains(Status.NEW_NICKNAME_303)) {
  2036.             for (ParticipantStatusListener listener : participantStatusListeners) {
  2037.                 listener.nicknameChanged(from, mucUser.getItem().getNick());
  2038.             }
  2039.         }
  2040.     }

  2041.     @Override
  2042.     public String toString() {
  2043.         return "MUC: " + room + "(" + connection.getUser() + ")";
  2044.     }
  2045. }