Index: Chat.java
===================================================================
RCS file: /var/lib/cvs/smack/source/org/jivesoftware/smack/Chat.java,v
retrieving revision 1.2
diff -u -r1.2 Chat.java
--- Chat.java	3 Sep 2005 23:01:21 -0000	1.2
+++ Chat.java	25 Oct 2005 19:27:01 -0000
@@ -20,23 +20,24 @@
 
 package org.jivesoftware.smack;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 import org.jivesoftware.smack.packet.Message;
 import org.jivesoftware.smack.util.StringUtils;
 import org.jivesoftware.smack.filter.*;
 
 /**
- * A chat is a series of messages sent between two users. Each chat can have
- * a unique thread ID, which is used to track which messages are part of a particular
- * conversation.<p>
- *
- * In some situations, it is better to have all messages from the other user delivered
- * to a Chat rather than just the messages that have a particular thread ID. To
- * enable this behavior, call {@link #setFilteredOnThreadID(boolean)} with
- * <tt>false</tt> as the parameter.
+ * A chat is a series of messages sent between two users. Each chat has a unique 
+ * thread ID, which is used to track which messages are part of a particular 
+ * conversation. Some messages are sent without a thread ID, and some clients 
+ * don't send thread IDs at all. Therefore, if a message without a thread ID 
+ * arrives it is routed to the most recently created Chat with the message 
+ * sender.
  *
  * @see XMPPConnection#createChat(String)
  * @author Matt Tucker
@@ -49,12 +50,6 @@
     private static String prefix = StringUtils.randomString(5);
     
     /**
-     * True if only messages that have a matching threadID will be delivered to a Chat. When
-     * false, any message from the other participant will be delivered to a Chat.
-     */
-    private static boolean filteredOnThreadID = true;
-    
-    /**
      * Keeps track of the current increment, which is appended to the prefix to
      * forum a unique ID.
      */
@@ -74,6 +69,7 @@
     private String threadID;
     private String participant;
     private PacketFilter messageFilter;
+    private Set<WeakReference<PacketListener>> listeners = new CopyOnWriteArraySet<WeakReference<PacketListener>>();
 
     /**
      * Creates a new chat with the specified user.
@@ -81,13 +77,9 @@
      * @param connection the connection the chat will use.
      * @param participant the user to chat with.
      */
-    public Chat(XMPPConnection connection, String participant) {
+    Chat(XMPPConnection connection, String participant) {
         // Automatically assign the next chat ID.
         this(connection, participant, nextID());
-        // If not filtering on thread ID, force the thread ID for this Chat to be null.
-        if (!filteredOnThreadID) {
-            this.threadID = null;
-        }
     }
 
     /**
@@ -97,44 +89,13 @@
      * @param participant the user to chat with.
      * @param threadID the thread ID to use.
      */
-    public Chat(XMPPConnection connection, String participant, String threadID) {
+    Chat(XMPPConnection connection, String participant, String threadID) {
         this.connection = connection;
         this.participant = participant;
         this.threadID = threadID;
 
-        if (filteredOnThreadID) {
-            // Filter the messages whose thread equals Chat's id
-            messageFilter = new ThreadFilter(threadID);
-        }
-        else {
-            // Filter the messages of type "chat" and sender equals Chat's participant
-            messageFilter =
-                new OrFilter(
-                    new AndFilter(
-                        new MessageTypeFilter(Message.Type.CHAT),
-                        new FromContainsFilter(participant)),
-                    new ThreadFilter(threadID));
-        }
-    }
-
-    /**
-     * Returns true if only messages that have a matching threadID will be delivered to Chat
-     * instances. When false, any message from the other participant will be delivered to Chat instances.
-     *
-     * @return true if messages delivered to Chat instances are filtered on thread ID.
-     */
-    public static boolean isFilteredOnThreadID() {
-        return filteredOnThreadID;
-    }
-
-    /**
-     * Sets whether only messages that have a matching threadID will be delivered to Chat instances.
-     * When false, any message from the other participant will be delivered to a Chat instances.
-     *
-     * @param value true if messages delivered to Chat instances are filtered on thread ID.
-     */
-    public static void setFilteredOnThreadID(boolean value) {
-        filteredOnThreadID = value;
+        // Filter the messages whose thread equals Chat's id
+        messageFilter = new ThreadFilter(threadID);
     }
 
     /**
@@ -237,5 +198,37 @@
      */
     public void addMessageListener(PacketListener listener) {
         connection.addPacketListener(listener, messageFilter);
+        // Keep track of the listener so that we can manually deliver extra
+        // messages to it later if needed.
+        listeners.add(new WeakReference<PacketListener>(listener)); 
+    }
+
+    /**
+     * Delivers a message directly to this chat, which will add the message
+     * to the collector and deliver it to all listeners registered with the
+     * Chat. This is used by the XMPPConnection class to deliver messages
+     * without a thread ID.
+     *
+     * @param message the message.
+     */
+    void deliver(Message message) {
+    	// Save the old thread ID, so it can be restored before returning to the caller.
+    	String oldThreadID = message.getThread();
+
+    	// Because the collector and listeners are expecting a thread ID with
+        // a specific value, set the thread ID on the message even though it
+        // probably never had one.
+        message.setThread(threadID);
+
+        for (WeakReference<PacketListener> listenerRef : listeners) {
+        	PacketListener listener = listenerRef.get();
+			if (listener != null)
+        		listener.processPacket(message);
+        	else
+        		// If the reference was cleared, remove it from the set.
+        		listeners.remove(listenerRef);
+        }
+
+        message.setThread(oldThreadID);
     }
 }
Index: XMPPConnection.java
===================================================================
RCS file: /var/lib/cvs/smack/source/org/jivesoftware/smack/XMPPConnection.java,v
retrieving revision 1.4
diff -u -r1.4 XMPPConnection.java
--- XMPPConnection.java	14 Sep 2005 00:29:09 -0000	1.4
+++ XMPPConnection.java	25 Oct 2005 19:27:01 -0000
@@ -21,11 +21,14 @@
 package org.jivesoftware.smack;
 
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.security.GeneralSecurityException;
 import java.security.SecureRandom;
+import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -43,9 +46,11 @@
 import org.jivesoftware.smack.debugger.SmackDebugger;
 import org.jivesoftware.smack.filter.PacketFilter;
 import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.filter.PacketTypeFilter;
 import org.jivesoftware.smack.net.SocketConnector;
 import org.jivesoftware.smack.packet.Authentication;
 import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Message;
 import org.jivesoftware.smack.packet.Packet;
 import org.jivesoftware.smack.packet.Presence;
 import org.jivesoftware.smack.util.StringUtils;
@@ -109,6 +114,7 @@
 
     private final List<AuthenticationListener> authListeners =
     	new CopyOnWriteArrayList<AuthenticationListener>();
+    private final List<ChatListener> chatListeners = new CopyOnWriteArrayList<ChatListener>();
 
     InetAddress address;
     /**
@@ -142,6 +148,14 @@
     private AccountManager accountManager = null;
     private final SASLAuthentication saslAuthentication = new SASLAuthentication(this);
 
+    /**
+     * A map between JIDs and the most recently created Chat object with that JID.
+     * Reference to the Chat is stored via a WeakReference so that the map
+     * does not interfere with garbage collection. The map of chats must be stored
+     * with each connection.
+     */
+    private final Map<String,WeakReference<Chat>> chats = new Hashtable<String,WeakReference<Chat>>();
+
     protected NioWritableFilter createSocket(InetAddress address, int port) throws IOException {
 		return new NioSocket(new InetSocketAddress(address, port));
 	}
@@ -408,10 +422,38 @@
         if (!isConnected()) {
             throw new IllegalStateException("Not connected to server.");
         }
-        return new Chat(this, participant);
+        Chat chat = new Chat(this, participant);
+        registerChat(participant, chat);
+        return chat;
     }
 
     /**
+     * Creates a new chat with the specified participant and the given thread ID.
+     * The participant should
+     * be a valid XMPP user such as <tt>jdoe@jivesoftware.com</tt> or
+     * <tt>jdoe@jivesoftware.com/work</tt>.
+     *
+     * @param participant the person to start the conversation with.
+     * @param threadID the thread ID to use.
+     * @return a new Chat object.
+     */
+    public Chat createChat(String participant, String threadID) {
+        if (!isConnected()) {
+            throw new IllegalStateException("Not connected to server.");
+        }
+        Chat chat = new Chat(this, participant, threadID);
+        registerChat(participant, chat);
+        return chat;
+    }
+
+	private void registerChat(String participant, Chat chat) {
+		// Register with the map of chats so that messages with no thread ID
+        // set will be delivered to this Chat.
+        chats.put(StringUtils.parseBareAddress(participant),
+        		new WeakReference<Chat>(chat));
+	}
+
+    /**
      * Creates a new group chat connected to the specified room. The room name
      * should be full address, such as <tt>room@chat.example.com</tt>.
      * <p>
@@ -565,6 +607,27 @@
     }
 
     /**
+     * Registers a chat listener with this connection. The chat listener
+     * will be informed of new chat sessions corresponding to message
+     * packets that bear no thread ID.
+     *
+     * @param listener a chat listener.
+     */
+    public void addChatListener(ChatListener listener) {
+    	if (!chatListeners.contains(listener))
+    		chatListeners.add(listener);
+    }
+
+    /**
+     * Removes a chat listener from this connection.
+     * 
+     * @param listener a chat listener.
+     */
+    public void removeChatListener(ChatListener listener) {
+    	chatListeners.remove(listener);
+    }
+
+    /**
      * Adds a connection listener to this connection that will be notified when
      * the connection closes or fails.
      *
@@ -692,6 +755,35 @@
 		// Make note of the fact that we're now connected.
 		connected = true;
 
+		// Add a listener for all message packets so that we can deliver errant
+        // messages to the best Chat instance available.
+        addPacketListener(new PacketListener() {
+            public void processPacket(Packet packet) {
+                Message message = (Message)packet;
+                // Ignore any messages with a thread ID, as they will likely
+                // already be associated with a Chat. This will miss messages
+                // with new thread ID values, but we can only assume that a
+                // listener is registered to deal with this case.
+                if (message.getThread() == null) {
+                    String from = message.getFrom();
+					WeakReference<Chat> chatRef = chats.get(
+                            StringUtils.parseBareAddress(from));
+					Chat chat = null;
+                    if (chatRef != null)
+						chat = chatRef.get();
+                    // Re-create the chat instance if the reference was cleared,
+                    // or create a new one if there was no session established with this peer.
+                    if (chat == null) {
+                    	chat = createChat(from);
+                    	// Create a new session and Inform the interested listeners.
+                    	for (ChatListener listener : chatListeners)
+                    		listener.incomingChat(from, chat);
+                    }
+                    chat.deliver(message);
+                }
+            }
+        }, new PacketTypeFilter(Message.class));
+
 		// Notify that a new connection has been established
 		connectionEstablished(this);
 	}
@@ -794,7 +886,7 @@
      * to be sent by the server.
      */
     void resetStream() {
-    	// TODO discard all knoweldge obtained so far from the peer.
+    	// TODO discard all knowledge obtained so far from the peer.
     	connector.reset();
     }
 
Index: ChatListener.java
===================================================================
RCS file: ChatListener.java
diff -N ChatListener.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ChatListener.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,46 @@
+/**
+ * $RCSfile: Chat.java,v $
+ * $Revision: 1.2 $
+ * $Date: 2005/09/03 23:01:21 $
+ *
+ * Copyright 2003-2004 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.smack;
+
+import java.util.EventListener;
+
+/**
+ * It is possible to receive notification of new chat instances by
+ * implementing this class and registering with the connection.
+ * 
+ * @author jkohen
+ * @see org.jivesoftware.smack.XMPPConnection#addChatListener(ChatListener)
+ */
+public interface ChatListener extends EventListener {
+	/**
+	 * Invoked when a chat message without a thread ID is received and
+	 * there is no record of a previous chat session with this user, or the
+	 * previous session was lost.
+	 * 
+	 * <p>The handler might want to add a message listener to the new
+	 * chat session.</p>
+	 * 
+	 * @param from the sender of the message.
+	 * @param chat the new chat session.
+	 * @see Chat#addMessageListener(PacketListener)
+	 */
+	void incomingChat(String from, Chat chat);
+}
