From 88ea6cf0378374ede120c4c1002bd3711f6c65fe Mon Sep 17 00:00:00 2001 From: Alex Wenckus Date: Thu, 23 Nov 2006 21:10:59 +0000 Subject: [PATCH] ChatState mostly code complete. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@6217 b35dd754-fafc-0310-a699-88a17e54d16e --- source/org/jivesoftware/smack/Chat.java | 13 +- .../org/jivesoftware/smack/ChatManager.java | 30 ++++- .../jivesoftware/smack/MessageListener.java | 30 +++++ .../smack/filter/PacketExtensionFilter.java | 10 ++ .../org/jivesoftware/smack/packet/Packet.java | 39 +++--- .../smackx/ChatStateListener.java | 15 ++- .../jivesoftware/smackx/ChatStateManager.java | 118 +++++++++++++++++- 7 files changed, 223 insertions(+), 32 deletions(-) create mode 100644 source/org/jivesoftware/smack/MessageListener.java diff --git a/source/org/jivesoftware/smack/Chat.java b/source/org/jivesoftware/smack/Chat.java index 9e11c69ba..14429e630 100644 --- a/source/org/jivesoftware/smack/Chat.java +++ b/source/org/jivesoftware/smack/Chat.java @@ -42,7 +42,7 @@ public class Chat { private ChatManager chatManager; private String threadID; private String participant; - private final Set listeners = new CopyOnWriteArraySet(); + private final Set listeners = new CopyOnWriteArraySet(); /** * Creates a new chat with the specified user and thread ID. @@ -119,14 +119,15 @@ public class Chat { * * @param listener a packet listener. */ - public void addMessageListener(PacketListener listener) { + public void addMessageListener(MessageListener listener) { if(listener == null) { return; } + // TODO these references should be weak. listeners.add(listener); } - public void removeMessageListener(PacketListener listener) { + public void removeMessageListener(MessageListener listener) { listeners.remove(listener); } @@ -135,7 +136,7 @@ public class Chat { * * @return an unmodifiable collection of all of the listeners registered with this chat. */ - public Collection getListeners() { + public Collection getListeners() { return Collections.unmodifiableCollection(listeners); } @@ -164,8 +165,8 @@ public class Chat { // probably never had one. message.setThread(threadID); - for (PacketListener listener : listeners) { - listener.processPacket(message); + for (MessageListener listener : listeners) { + listener.processMessage(this, message); } } } \ No newline at end of file diff --git a/source/org/jivesoftware/smack/ChatManager.java b/source/org/jivesoftware/smack/ChatManager.java index 69cfd4d09..5bcd6e2fd 100644 --- a/source/org/jivesoftware/smack/ChatManager.java +++ b/source/org/jivesoftware/smack/ChatManager.java @@ -37,6 +37,7 @@ import java.util.concurrent.CopyOnWriteArraySet; * @author Alexander Wenckus */ public class ChatManager { + /** * Returns the next unique id. Each id made up of a short alphanumeric * prefix along with a unique numeric value. @@ -70,7 +71,11 @@ public class ChatManager { private Map jidChats = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK); - private Set chatManagerListeners = new CopyOnWriteArraySet(); + private Set chatManagerListeners + = new CopyOnWriteArraySet(); + + private Map interceptors + = new WeakHashMap(); private XMPPConnection connection; @@ -115,7 +120,7 @@ public class ChatManager { * @param listener the listener which will listen for new messages from this chat. * @return the created chat. */ - public Chat createChat(String userJID, PacketListener listener) { + public Chat createChat(String userJID, MessageListener listener) { String threadID = nextID(); Chat chat = createChat(userJID, threadID, true); @@ -186,7 +191,12 @@ public class ChatManager { } void sendMessage(Chat chat, Message message) { - // Here we will run any interceptors + for(Map.Entry interceptor : interceptors.entrySet()) { + PacketFilter filter = interceptor.getValue(); + if(filter != null && filter.accept(message)) { + interceptor.getKey().interceptPacket(message); + } + } connection.sendPacket(message); } @@ -195,4 +205,18 @@ public class ChatManager { new FromContainsFilter(chat.getParticipant()))); } + /** + * Adds an interceptor which intercepts any messages sent through chats. + * + * @param packetInterceptor the interceptor. + */ + public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) { + addOutgoingMessageInterceptor(packetInterceptor, null); + } + + public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) { + if (packetInterceptor != null) { + interceptors.put(packetInterceptor, filter); + } + } } diff --git a/source/org/jivesoftware/smack/MessageListener.java b/source/org/jivesoftware/smack/MessageListener.java new file mode 100644 index 000000000..c25cf8b88 --- /dev/null +++ b/source/org/jivesoftware/smack/MessageListener.java @@ -0,0 +1,30 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * 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 org.jivesoftware.smack.packet.Message; + +/** + * + */ +public interface MessageListener { + void processMessage(Chat chat, Message message); +} diff --git a/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java b/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java index b46c118cc..a572c37aa 100644 --- a/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java +++ b/source/org/jivesoftware/smack/filter/PacketExtensionFilter.java @@ -45,6 +45,16 @@ public class PacketExtensionFilter implements PacketFilter { this.namespace = namespace; } + /** + * Creates a new packet extension filter. Packets will pass the filter if they have a packet + * extension that matches the specified namespace. + * + * @param namespace the XML namespace of the packet extension. + */ + public PacketExtensionFilter(String namespace) { + this(null, namespace); + } + public boolean accept(Packet packet) { return packet.getExtension(elementName, namespace) != null; } diff --git a/source/org/jivesoftware/smack/packet/Packet.java b/source/org/jivesoftware/smack/packet/Packet.java index 292332e77..c1cbf17ba 100644 --- a/source/org/jivesoftware/smack/packet/Packet.java +++ b/source/org/jivesoftware/smack/packet/Packet.java @@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; /** * Base class for XMPP packets. Every packet has a unique ID (which is automatically @@ -72,7 +73,9 @@ public abstract class Packet { private String packetID = null; private String to = null; private String from = null; - private List packetExtensions = null; + private final List packetExtensions + = new CopyOnWriteArrayList(); + private Map properties = null; private XMPPError error = null; @@ -179,9 +182,20 @@ public abstract class Packet { return Collections.unmodifiableList(new ArrayList(packetExtensions)); } + /** + * Returns the first extension of this packet that has the given namespace. + * + * @param namespace the namespace of the extension that is desired. + * @return the packet extension with the given namespace. + */ + public PacketExtension getExtension(String namespace) { + return getExtension(null, namespace); + } + /** * Returns the first packet extension that matches the specified element name and - * namespace, or null if it doesn't exist. Packet extensions are + * namespace, or null if it doesn't exist. If the provided elementName is null + * than only the provided namespace is attempted to be matched. Packet extensions are * are arbitrary XML sub-documents in standard XMPP packets. By default, a * DefaultPacketExtension instance will be returned for each extension. However, * PacketExtensionProvider instances can be registered with the @@ -189,16 +203,18 @@ public abstract class Packet { * class to handle custom parsing. In that case, the type of the Object * will be determined by the provider. * - * @param elementName the XML element name of the packet extension. + * @param elementName the XML element name of the packet extension. (May be null) * @param namespace the XML element namespace of the packet extension. * @return the extension, or null if it doesn't exist. */ - public synchronized PacketExtension getExtension(String elementName, String namespace) { - if (packetExtensions == null || elementName == null || namespace == null) { + public PacketExtension getExtension(String elementName, String namespace) { + if (namespace == null) { return null; } for (PacketExtension ext : packetExtensions) { - if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { + if ((elementName == null || elementName.equals(ext.getElementName())) + && namespace.equals(ext.getNamespace())) + { return ext; } } @@ -210,10 +226,7 @@ public abstract class Packet { * * @param extension a packet extension. */ - public synchronized void addExtension(PacketExtension extension) { - if (packetExtensions == null) { - packetExtensions = new ArrayList(); - } + public void addExtension(PacketExtension extension) { packetExtensions.add(extension); } @@ -222,10 +235,8 @@ public abstract class Packet { * * @param extension the packet extension to remove. */ - public synchronized void removeExtension(PacketExtension extension) { - if (packetExtensions != null) { - packetExtensions.remove(extension); - } + public void removeExtension(PacketExtension extension) { + packetExtensions.remove(extension); } /** diff --git a/source/org/jivesoftware/smackx/ChatStateListener.java b/source/org/jivesoftware/smackx/ChatStateListener.java index 3265c0d20..6401c279a 100644 --- a/source/org/jivesoftware/smackx/ChatStateListener.java +++ b/source/org/jivesoftware/smackx/ChatStateListener.java @@ -21,11 +21,20 @@ package org.jivesoftware.smackx; import org.jivesoftware.smack.Chat; -import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.MessageListener; /** + * Events for when the state of a user in a chat changes. * + * @author Alexander Wenckus */ -public interface ChatStateListener extends PacketListener { - void participantActive(Chat chat); +public interface ChatStateListener extends MessageListener { + + /** + * Fired when the state of a chat with another user changes. + * + * @param chat the chat in which the state has changed. + * @param state the new state of the participant. + */ + void stateChanged(Chat chat, ChatState state); } diff --git a/source/org/jivesoftware/smackx/ChatStateManager.java b/source/org/jivesoftware/smackx/ChatStateManager.java index 1a0208184..a8bd9d590 100644 --- a/source/org/jivesoftware/smackx/ChatStateManager.java +++ b/source/org/jivesoftware/smackx/ChatStateManager.java @@ -20,22 +20,128 @@ package org.jivesoftware.smackx; -import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.ChatStateExtension; + +import java.util.Map; +import java.util.WeakHashMap; +import java.util.Collection; /** + * Handles chat state for all chats on a particular XMPPConnection. This class manages both the + * packet extensions and the disco response neccesary for compliance with + * XEP-0085. * + * @see org.jivesoftware.smackx.ChatState + * @see org.jivesoftware.smackx.packet.ChatStateExtension + * @author Alexander Wenckus */ public class ChatStateManager { + private static Map managers = + new WeakHashMap(); + /** - * - * @param currentState - * @param chat + * Returns the ChatStateManager related to the XMPPConnection and it will create one if it does + * not yet exist. + * + * @param connection the connection to return the ChatStateManager/ + * @return the ChatStateManager related the the connection. */ - public void setCurrentState(ChatState currentState, Chat chat) { - Message message = new Message(); + public static ChatStateManager getInstance(XMPPConnection connection) { + ChatStateManager manager = managers.get(connection); + if(manager == null) { + manager = new ChatStateManager(connection); + manager.init(); + managers.put(connection, manager); + } + + return manager; } + private XMPPConnection connection; + private OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor(); + + private IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor(); + + private ChatStateManager(XMPPConnection connection) { + this.connection = connection; + } + + private void init() { + PacketFilter filter = new NotFilter( + new PacketExtensionFilter("http://jabber.org/protocol/chatstates")); + connection.getChatManager().addOutgoingMessageInterceptor(outgoingInterceptor, + filter); + connection.getChatManager().addChatListener(incomingInterceptor); + } + + /** + * Sets the current state of the provided chat. This method will send an empty bodied Message + * packet with the state attached as a {@link org.jivesoftware.smack.packet.PacketExtension}. + * + * @param newState the new state of the chat + * @param chat the chat. + * @throws org.jivesoftware.smack.XMPPException when there is an error sending the message + * packet. + */ + public void setCurrentState(ChatState newState, Chat chat) throws XMPPException { + Message message = new Message(); + ChatStateExtension extension = new ChatStateExtension(newState); + message.addExtension(extension); + + chat.sendMessage(message); + } + + private void fireNewChatState(Chat chat, ChatState state) { + Collection listeners = chat.getListeners(); + for(MessageListener listener : listeners) { + if(listener instanceof ChatStateListener) { + ((ChatStateListener)listener).stateChanged(chat, state); + } + } + } + + private class OutgoingMessageInterceptor implements PacketInterceptor { + + public void interceptPacket(Packet packet) { + if (!(packet instanceof Message)) { + return; + } + Message message = (Message)packet; + message.addExtension(new ChatStateExtension(ChatState.active)); + } + } + + private class IncomingMessageInterceptor implements ChatManagerListener, MessageListener { + + public void chatCreated(final Chat chat, boolean createdLocally) { + chat.addMessageListener(this); + } + + public void processMessage(Chat chat, Message message) { + PacketExtension extension + = message.getExtension("http://jabber.org/protocol/chatstates"); + if(extension == null) { + return; + } + + ChatState state; + try { + state = ChatState.valueOf(extension.getElementName()); + } + catch (Exception ex) { + return; + } + + fireNewChatState(chat, state); + } + } }