diff --git a/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java b/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java
new file mode 100644
index 000000000..600149b0b
--- /dev/null
+++ b/source/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java
@@ -0,0 +1,135 @@
+/**
+ * $RCSfile$
+ * $Revision: 2779 $
+ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smack.packet.Packet;
+
+import java.util.LinkedList;
+
+/**
+ * A variant of the {@link org.jivesoftware.smack.PacketCollector} class
+ * that does not force attachment to an XMPPConnection
+ * on creation and no filter is required. Used to collect message
+ * packets targeted to a group chat room.
+ *
+ * @author Larry Kirschner
+ */
+class ConnectionDetachedPacketCollector {
+ /**
+ * Max number of packets that any one collector can hold. After the max is
+ * reached, older packets will be automatically dropped from the queue as
+ * new packets are added.
+ */
+ private static final int MAX_PACKETS = 65536;
+
+ private LinkedList resultQueue;
+
+ /**
+ * Creates a new packet collector. If the packet filter is null, then
+ * all packets will match this collector.
+ */
+ public ConnectionDetachedPacketCollector() {
+ this.resultQueue = new LinkedList();
+ }
+
+ /**
+ * Polls to see if a packet is currently available and returns it, or
+ * immediately returns null if no packets are currently in the
+ * result queue.
+ *
+ * @return the next packet result, or null if there are no more
+ * results.
+ */
+ public synchronized Packet pollResult() {
+ if (resultQueue.isEmpty()) {
+ return null;
+ }
+ else {
+ return resultQueue.removeLast();
+ }
+ }
+
+ /**
+ * Returns the next available packet. The method call will block (not return)
+ * until a packet is available.
+ *
+ * @return the next available packet.
+ */
+ public synchronized Packet nextResult() {
+ // Wait indefinitely until there is a result to return.
+ while (resultQueue.isEmpty()) {
+ try {
+ wait();
+ }
+ catch (InterruptedException ie) {
+ // Ignore.
+ }
+ }
+ return resultQueue.removeLast();
+ }
+
+ /**
+ * Returns the next available packet. The method call will block (not return)
+ * until a packet is available or the timeout has elapased. If the
+ * timeout elapses without a result, null will be returned.
+ *
+ * @param timeout the amount of time to wait for the next packet (in milleseconds).
+ * @return the next available packet.
+ */
+ public synchronized Packet nextResult(long timeout) {
+ // Wait up to the specified amount of time for a result.
+ if (resultQueue.isEmpty()) {
+ try {
+ wait(timeout);
+ }
+ catch (InterruptedException ie) {
+ // Ignore.
+ }
+ }
+ // If still no result, return null.
+ if (resultQueue.isEmpty()) {
+ return null;
+ }
+ else {
+ return resultQueue.removeLast();
+ }
+ }
+
+ /**
+ * Processes a packet to see if it meets the criteria for this packet collector.
+ * If so, the packet is added to the result queue.
+ *
+ * @param packet the packet to process.
+ */
+ protected synchronized void processPacket(Packet packet) {
+ if (packet == null) {
+ return;
+ }
+ // If the max number of packets has been reached, remove the oldest one.
+ if (resultQueue.size() == MAX_PACKETS) {
+ resultQueue.removeLast();
+ }
+ // Add the new packet.
+ resultQueue.addFirst(packet);
+ // Notify waiting threads a result is available.
+ notifyAll();
+ }
+}
diff --git a/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java b/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java
new file mode 100644
index 000000000..2ff7aa939
--- /dev/null
+++ b/source/org/jivesoftware/smackx/muc/PacketMultiplexListener.java
@@ -0,0 +1,96 @@
+/**
+ * $RCSfile$
+ * $Revision: 2779 $
+ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+import org.jivesoftware.smack.filter.PacketExtensionFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.Presence;
+
+/**
+ * The single PacketListener
used by each {@link MultiplexedMultiUserChat}
+ * for all basic processing of presence, and message packets targeted to that chat.
+ *
+ * @author Larry Kirschner
+ */
+class PacketMultiplexListener implements PacketListener {
+
+ private static final PacketFilter MESSAGE_FILTER =
+ new MessageTypeFilter(Message.Type.GROUP_CHAT);
+ private static final PacketFilter PRESENCE_FILTER = new PacketTypeFilter(Presence.class);
+ private static final PacketFilter SUBJECT_FILTER = new PacketFilter() {
+ public boolean accept(Packet packet) {
+ Message msg = (Message) packet;
+ return msg.getSubject() != null;
+ }
+ };
+ private static final PacketFilter DECLINES_FILTER =
+ new PacketExtensionFilter("x",
+ "http://jabber.org/protocol/muc#user");
+
+ private ConnectionDetachedPacketCollector messageCollector;
+ private PacketListener presenceListener;
+ private PacketListener subjectListener;
+ private PacketListener declinesListener;
+
+ public PacketMultiplexListener(
+ ConnectionDetachedPacketCollector messageCollector,
+ PacketListener presenceListener,
+ PacketListener subjectListener, PacketListener declinesListener) {
+ if (messageCollector == null) {
+ throw new IllegalArgumentException("MessageCollector is null");
+ }
+ if (presenceListener == null) {
+ throw new IllegalArgumentException("Presence listener is null");
+ }
+ if (subjectListener == null) {
+ throw new IllegalArgumentException("Subject listener is null");
+ }
+ if (declinesListener == null) {
+ throw new IllegalArgumentException("Declines listener is null");
+ }
+ this.messageCollector = messageCollector;
+ this.presenceListener = presenceListener;
+ this.subjectListener = subjectListener;
+ this.declinesListener = declinesListener;
+ }
+
+ public void processPacket(Packet p) {
+ if (PRESENCE_FILTER.accept(p)) {
+ presenceListener.processPacket(p);
+ }
+ else if (MESSAGE_FILTER.accept(p)) {
+ messageCollector.processPacket(p);
+
+ if (SUBJECT_FILTER.accept(p)) {
+ subjectListener.processPacket(p);
+ }
+ }
+ else if (DECLINES_FILTER.accept(p)) {
+ declinesListener.processPacket(p);
+ }
+ }
+
+}
diff --git a/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java b/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java
new file mode 100644
index 000000000..e756f92f5
--- /dev/null
+++ b/source/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java
@@ -0,0 +1,215 @@
+/**
+ * $RCSfile$
+ * $Revision: 2779 $
+ * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.muc;
+
+import org.jivesoftware.smack.ConnectionListener;
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.StringUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A RoomListenerMultiplexor
multiplexes incoming packets on
+ * an XMPPConnection
using a single listener/filter pair.
+ * A single RoomListenerMultiplexor
is created for each
+ * {@link org.jivesoftware.smack.XMPPConnection} that has joined MUC rooms
+ * within its session.
+ *
+ * @author Larry Kirschner
+ */
+class RoomListenerMultiplexor implements ConnectionListener {
+
+ // We use a WeakHashMap so that the GC can collect the monitor when the
+ // connection is no longer referenced by any object.
+ private static final Map> monitors =
+ new WeakHashMap>();
+
+ private XMPPConnection connection;
+ private RoomMultiplexFilter filter;
+ private RoomMultiplexListener listener;
+
+ /**
+ * Returns a new or existing RoomListenerMultiplexor for a given connection.
+ *
+ * @param conn the connection to monitor for room invitations.
+ * @return a new or existing RoomListenerMultiplexor for a given connection.
+ */
+ public static RoomListenerMultiplexor getRoomMultiplexor(XMPPConnection conn) {
+ synchronized (monitors) {
+ if (!monitors.containsKey(conn)) {
+ RoomListenerMultiplexor rm = new RoomListenerMultiplexor(conn, new RoomMultiplexFilter(),
+ new RoomMultiplexListener());
+
+ rm.init();
+
+ // We need to use a WeakReference because the monitor references the
+ // connection and this could prevent the GC from collecting the monitor
+ // when no other object references the monitor
+ monitors.put(conn, new WeakReference(rm));
+ }
+ // Return the InvitationsMonitor that monitors the connection
+ return monitors.get(conn).get();
+ }
+ }
+
+ /**
+ * All access should be through
+ * the static method {@link #getRoomMultiplexor(XMPPConnection)}.
+ */
+ private RoomListenerMultiplexor(XMPPConnection connection, RoomMultiplexFilter filter,
+ RoomMultiplexListener listener) {
+ if (connection == null) {
+ throw new IllegalArgumentException("Connection is null");
+ }
+ if (filter == null) {
+ throw new IllegalArgumentException("Filter is null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("Listener is null");
+ }
+ this.connection = connection;
+ this.filter = filter;
+ this.listener = listener;
+ }
+
+ public void addRoom(String address, PacketMultiplexListener roomListener) {
+ filter.addRoom(address);
+ listener.addRoom(address, roomListener);
+ }
+
+ public void connectionClosed() {
+ cancel();
+ }
+
+ public void connectionClosedOnError(Exception e) {
+ cancel();
+ }
+
+ /**
+ * Initializes the listeners to detect received room invitations and to detect when the
+ * connection gets closed. As soon as a room invitation is received the invitations
+ * listeners will be fired. When the connection gets closed the monitor will remove
+ * his listeners on the connection.
+ */
+ public void init() {
+ connection.addConnectionListener(this);
+ connection.addPacketListener(listener, filter);
+ }
+
+ public void removeRoom(String address) {
+ filter.removeRoom(address);
+ listener.removeRoom(address);
+ }
+
+ /**
+ * Cancels all the listeners that this InvitationsMonitor has added to the connection.
+ */
+ private void cancel() {
+ connection.removeConnectionListener(this);
+ connection.removePacketListener(listener);
+ }
+
+ /**
+ * The single XMPPConnection
-level PacketFilter
used by a {@link RoomListenerMultiplexor}
+ * for all muc chat rooms on an XMPPConnection
.
+ * Each time a muc chat room is added to/removed from an
+ * XMPPConnection
the address for that chat room
+ * is added to/removed from that XMPPConnection
's
+ * RoomMultiplexFilter
.
+ */
+ private static class RoomMultiplexFilter implements PacketFilter {
+
+ private Map roomAddressTable = new ConcurrentHashMap();
+
+ public boolean accept(Packet p) {
+ String from = p.getFrom();
+ if (from == null) {
+ return false;
+ }
+ return roomAddressTable.containsKey(StringUtils.parseBareAddress(from).toLowerCase());
+ }
+
+ public void addRoom(String address) {
+ if (address == null) {
+ return;
+ }
+ roomAddressTable.put(address.toLowerCase(), address);
+ }
+
+ public void removeRoom(String address) {
+ if (address == null) {
+ return;
+ }
+ roomAddressTable.remove(address.toLowerCase());
+ }
+ }
+
+ /**
+ * The single XMPPConnection
-level PacketListener
+ * used by a {@link RoomListenerMultiplexor}
+ * for all muc chat rooms on an XMPPConnection
.
+ * Each time a muc chat room is added to/removed from an
+ * XMPPConnection
the address and listener for that chat room
+ * are added to/removed from that XMPPConnection
's
+ * RoomMultiplexListener
.
+ *
+ * @author Larry Kirschner
+ */
+ private static class RoomMultiplexListener implements PacketListener {
+
+ private Map roomListenersByAddress =
+ new ConcurrentHashMap();
+
+ public void processPacket(Packet p) {
+ String from = p.getFrom();
+ if (from == null) {
+ return;
+ }
+
+ PacketMultiplexListener listener =
+ roomListenersByAddress.get(StringUtils.parseBareAddress(from).toLowerCase());
+
+ if (listener != null) {
+ listener.processPacket(p);
+ }
+ }
+
+ public void addRoom(String address, PacketMultiplexListener listener) {
+ if (address == null) {
+ return;
+ }
+ roomListenersByAddress.put(address.toLowerCase(), listener);
+ }
+
+ public void removeRoom(String address) {
+ if (address == null) {
+ return;
+ }
+ roomListenersByAddress.remove(address.toLowerCase());
+ }
+ }
+}