diff --git a/source/org/jivesoftware/smackx/MultiUserChat.java b/source/org/jivesoftware/smackx/MultiUserChat.java index 53bb5bf95..dffc39bc8 100644 --- a/source/org/jivesoftware/smackx/MultiUserChat.java +++ b/source/org/jivesoftware/smackx/MultiUserChat.java @@ -73,19 +73,23 @@ public class MultiUserChat { private final static String discoNamespace = "http://jabber.org/protocol/muc"; private final static String discoNode = "http://jabber.org/protocol/muc#rooms"; - + private static Map joinedRooms = new WeakHashMap(); private XMPPConnection connection; private String room; + private String subject; private String nickname = null; private boolean joined = false; private Map participantsMap = new HashMap(); private List invitationRejectionListeners = new ArrayList(); + private List subjectUpdatedListeners = new ArrayList(); private PacketFilter presenceFilter; private PacketListener presenceListener; + private PacketFilter subjectFilter; + private PacketListener subjectListener; private PacketFilter messageFilter; private PacketFilter declinesFilter; private PacketListener declinesListener; @@ -129,54 +133,7 @@ public class MultiUserChat { public MultiUserChat(XMPPConnection connection, String room) { this.connection = connection; this.room = room; - // Create a collector for all incoming messages. - messageFilter = - new AndFilter(new FromContainsFilter(room), new PacketTypeFilter(Message.class)); - messageFilter = new AndFilter(messageFilter, new PacketFilter() { - public boolean accept(Packet packet) { - Message msg = (Message) packet; - return msg.getType() == Message.Type.GROUP_CHAT; - } - }); - messageCollector = connection.createPacketCollector(messageFilter); - // Create a listener for all presence updates. - presenceFilter = - new AndFilter(new FromContainsFilter(room), new PacketTypeFilter(Presence.class)); - presenceListener = new PacketListener() { - public void processPacket(Packet packet) { - Presence presence = (Presence) packet; - String from = presence.getFrom(); - if (presence.getType() == Presence.Type.AVAILABLE) { - synchronized (participantsMap) { - participantsMap.put(from, presence); - } - } - else if (presence.getType() == Presence.Type.UNAVAILABLE) { - synchronized (participantsMap) { - participantsMap.remove(from); - } - } - } - }; - connection.addPacketListener(presenceListener, presenceFilter); - - // Listens for all messages that include a MUCUser extension and fire the invitation - // rejection listeners if the message includes an invitation rejection. - declinesFilter = new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); - declinesListener = new PacketListener() { - public void processPacket(Packet packet) { - // Get the MUC User extension - MUCUser mucUser = getMUCUserExtension(packet); - // Check if the MUCUser informs that the invitee has declined the invitation - if (mucUser.getDecline() != null) { - // Fire event for invitation rejection listeners - fireInvitationRejectionListeners( - mucUser.getDecline().getFrom(), - mucUser.getDecline().getReason()); - } - }; - }; - connection.addPacketListener(declinesListener, declinesFilter); + init(); } /** @@ -298,6 +255,9 @@ public class MultiUserChat { // Wait up to a certain number of seconds for a reply. Presence presence = (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + if (presence == null) { throw new XMPPException("No response from server."); } @@ -459,6 +419,9 @@ public class MultiUserChat { connection.sendPacket(joinPresence); // Wait up to a certain number of seconds for a reply. Presence presence = (Presence) response.nextResult(timeout); + // Stop queuing results + response.cancel(); + if (presence == null) { throw new XMPPException("No response from server."); } @@ -663,6 +626,11 @@ public class MultiUserChat { else if (answer.getError() != null) { throw new XMPPException(answer.getError()); } + // Reset participant information. + participantsMap = new HashMap(); + nickname = null; + joined = false; + userHasLeft(); } /** @@ -777,6 +745,63 @@ public class MultiUserChat { listeners[i].invitationDeclined(invitee, reason); } } + + /** + * Adds a listener to subject change notifications. The listener will be fired anytime + * the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void addSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + if (!subjectUpdatedListeners.contains(listener)) { + subjectUpdatedListeners.add(listener); + } + } + } + + /** + * Removes a listener from subject change notifications. The listener will be fired + * anytime the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + subjectUpdatedListeners.remove(listener); + } + } + + /** + * Fires subject updated listeners. + */ + private void fireSubjectUpdatedListeners(String subject, String from) { + SubjectUpdatedListener[] listeners = null; + synchronized (subjectUpdatedListeners) { + listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()]; + subjectUpdatedListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].subjectUpdated(subject, from); + } + } + + /** + * Returns the last known room's subject or null if the user hasn't joined the room + * or the room does not have a subject yet. In case the room has a subject, as soon as the + * user joins the room a message with the current room's subject will be received.

+ * + * To be notified every time the room's subject change you should add a listener + * to this room. (@see addSubjectUpdatedListener(SubjectUpdatedListener))

+ * + * To change the room's subject use changeSubject(String). + * + * @return the room's subject or null if the user hasn't joined the room or the + * room does not have a subject yet. + */ + public String getSubject() { + return subject; + } /** * Returns the reserved room nickname for the user in the room. A user may have a reserved @@ -852,6 +877,9 @@ public class MultiUserChat { // Wait up to a certain number of seconds for a reply. Presence presence = (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + if (presence == null) { throw new XMPPException("No response from server."); } @@ -1065,6 +1093,47 @@ public class MultiUserChat { connection.addPacketListener(listener, messageFilter); } + /** + * Changes the subject within the room. As a default, only users with a role of "moderator" + * are allowed to change the subject in a room. Although some rooms may be configured to + * allow a mere participant or even a visitor to change the subject. + * + * @param subject the new room's subject to set. + * @throws XMPPException if someone without appropriate privileges attempts to change the + * room subject will throw an error with code 403 (i.e. Forbidden) + */ + public void changeSubject(final String subject) throws XMPPException { + // TODO Implement way to listen to changes of subject made by other users + Message message = new Message(room, Message.Type.GROUP_CHAT); + message.setSubject(subject); + // Wait for an error or confirmation message back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromContainsFilter(room), + new PacketTypeFilter(Message.class)); + responseFilter = new AndFilter(responseFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return subject.equals(msg.getSubject()); + } + }); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send change subject packet. + connection.sendPacket(message); + // Wait up to a certain number of seconds for a reply. + Message answer = + (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + /** * Notification message that the user has joined the room. */ @@ -1104,8 +1173,88 @@ public class MultiUserChat { return null; } + private void init() { + // Create a collector for all incoming messages. + messageFilter = + new AndFilter( + new FromContainsFilter(room), + new MessageTypeFilter(Message.Type.GROUP_CHAT)); + messageFilter = new AndFilter(messageFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getBody() != null; + } + }); + messageCollector = connection.createPacketCollector(messageFilter); + // Create a listener for all subject updates. + subjectFilter = + new AndFilter( + new FromContainsFilter(room), + new MessageTypeFilter(Message.Type.GROUP_CHAT)); + subjectFilter = new AndFilter(subjectFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getSubject() != null; + } + }); + subjectListener = new PacketListener() { + public void processPacket(Packet packet) { + Message msg = (Message) packet; + // Update the room subject + subject = msg.getSubject(); + // Fire event for subject updated listeners + fireSubjectUpdatedListeners( + msg.getSubject(), + msg.getFrom()); + + } + }; + connection.addPacketListener(subjectListener, subjectFilter); + + // Create a listener for all presence updates. + presenceFilter = + new AndFilter(new FromContainsFilter(room), new PacketTypeFilter(Presence.class)); + presenceListener = new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence) packet; + String from = presence.getFrom(); + if (presence.getType() == Presence.Type.AVAILABLE) { + synchronized (participantsMap) { + participantsMap.put(from, presence); + } + } + else if (presence.getType() == Presence.Type.UNAVAILABLE) { + synchronized (participantsMap) { + participantsMap.remove(from); + } + } + } + }; + connection.addPacketListener(presenceListener, presenceFilter); + + // Listens for all messages that include a MUCUser extension and fire the invitation + // rejection listeners if the message includes an invitation rejection. + declinesFilter = new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); + declinesListener = new PacketListener() { + public void processPacket(Packet packet) { + // Get the MUC User extension + MUCUser mucUser = getMUCUserExtension(packet); + // Check if the MUCUser informs that the invitee has declined the invitation + if (mucUser.getDecline() != null) { + // Fire event for invitation rejection listeners + fireInvitationRejectionListeners( + mucUser.getDecline().getFrom(), + mucUser.getDecline().getReason()); + } + }; + }; + connection.addPacketListener(declinesListener, declinesFilter); + } + public void finalize() { if (connection != null) { + messageCollector.cancel(); + connection.removePacketListener(subjectListener); connection.removePacketListener(presenceListener); connection.removePacketListener(declinesListener); }