/** * $RCSfile$ * $Revision: 2407 $ * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ * * Copyright 2003-2007 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.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArraySet; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.FromContainsFilter; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.ThreadFilter; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.collections.ReferenceMap; /** * The chat manager keeps track of references to all current chats. It will not * hold any references in memory on its own so it is neccesary to keep a * reference to the chat object itself. To be made aware of new chats, register * a listener by calling {@link #addChatListener(ChatManagerListener)}. * * @author Alexander Wenckus */ public class ChatManager { /** * A prefix helps to make sure that ID's are unique across mutliple * instances. */ private static String prefix = StringUtils.randomString(5); /** * Keeps track of the current increment, which is appended to the prefix to * forum a unique ID. */ private static long id = 0; /** * Returns the next unique id. Each id made up of a short alphanumeric * prefix along with a unique numeric value. * * @return the next id. */ private static synchronized String nextID() { return prefix + Long.toString(id++); } /** * Maps thread ID to chat. */ private final Map<String, Chat> threadChats = Collections .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, ReferenceMap.WEAK)); /** * Maps jids to chats */ private final Map<String, Chat> jidChats = Collections .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, ReferenceMap.WEAK)); /** * Maps base jids to chats */ private final Map<String, Chat> baseJidChats = Collections .synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD, ReferenceMap.WEAK)); private final Set<ChatManagerListener> chatManagerListeners = new CopyOnWriteArraySet<ChatManagerListener>(); private final Map<PacketInterceptor, PacketFilter> interceptors = new WeakHashMap<PacketInterceptor, PacketFilter>(); private Connection connection; ChatManager(Connection connection) { this.connection = connection; final PacketFilter filter = new PacketFilter() { @Override public boolean accept(Packet packet) { if (!(packet instanceof Message)) { return false; } final Message.Type messageType = ((Message) packet).getType(); return messageType != Message.Type.groupchat && messageType != Message.Type.headline; } }; // Add a listener for all message packets so that we can deliver errant // messages to the best Chat instance available. connection.addPacketListener(new PacketListener() { @Override public void processPacket(Packet packet) { final Message message = (Message) packet; Chat chat; if (message.getThread() == null) { chat = getUserChat(message.getFrom()); } else { chat = getThreadChat(message.getThread()); if (chat == null) { // Try to locate the chat based on the sender of the // message chat = getUserChat(message.getFrom()); } } if (chat == null) { chat = createChat(message); } deliverMessage(chat, message); } }, filter); } /** * Register a new listener with the ChatManager to recieve events related to * chats. * * @param listener * the listener. */ public void addChatListener(ChatManagerListener listener) { chatManagerListeners.add(listener); } /** * 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); } } private Chat createChat(Message message) { String threadID = message.getThread(); if (threadID == null) { threadID = nextID(); } final String userJID = message.getFrom(); return createChat(userJID, threadID, false); } /** * Creates a new chat and returns it. * * @param userJID * the user this chat is with. * @param listener * the listener which will listen for new messages from this * chat. * @return the created chat. */ public Chat createChat(String userJID, MessageListener listener) { String threadID; do { threadID = nextID(); } while (threadChats.get(threadID) != null); return createChat(userJID, threadID, listener); } private Chat createChat(String userJID, String threadID, boolean createdLocally) { final Chat chat = new Chat(this, userJID, threadID); threadChats.put(threadID, chat); jidChats.put(userJID, chat); baseJidChats.put(StringUtils.parseBareAddress(userJID), chat); for (final ChatManagerListener listener : chatManagerListeners) { listener.chatCreated(chat, createdLocally); } return chat; } /** * Creates a new chat using the specified thread ID, then returns it. * * @param userJID * the jid of the user this chat is with * @param thread * the thread of the created chat. * @param listener * the listener to add to the chat * @return the created chat. */ public Chat createChat(String userJID, String thread, MessageListener listener) { if (thread == null) { thread = nextID(); } Chat chat = threadChats.get(thread); if (chat != null) { throw new IllegalArgumentException("ThreadID is already used"); } chat = createChat(userJID, thread, true); chat.addMessageListener(listener); return chat; } PacketCollector createPacketCollector(Chat chat) { return connection.createPacketCollector(new AndFilter(new ThreadFilter( chat.getThreadID()), new FromContainsFilter(chat .getParticipant()))); } private void deliverMessage(Chat chat, Message message) { // Here we will run any interceptors chat.deliver(message); } /** * Returns an unmodifiable collection of all chat listeners currently * registered with this manager. * * @return an unmodifiable collection of all chat listeners currently * registered with this manager. */ public Collection<ChatManagerListener> getChatListeners() { return Collections.unmodifiableCollection(chatManagerListeners); } public Chat getThreadChat(String thread) { return threadChats.get(thread); } /** * Try to get a matching chat for the given user JID. Try the full JID map * first, the try to match on the base JID if no match is found. * * @param userJID * @return */ private Chat getUserChat(String userJID) { Chat match = jidChats.get(userJID); if (match == null) { match = baseJidChats.get(StringUtils.parseBareAddress(userJID)); } return match; } /** * Removes a listener, it will no longer be notified of new events related * to chats. * * @param listener * the listener that is being removed */ public void removeChatListener(ChatManagerListener listener) { chatManagerListeners.remove(listener); } void sendMessage(Chat chat, Message message) { for (final Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors .entrySet()) { final PacketFilter filter = interceptor.getValue(); if (filter != null && filter.accept(message)) { interceptor.getKey().interceptPacket(message); } } // Ensure that messages being sent have a proper FROM value if (message.getFrom() == null) { message.setFrom(connection.getUser()); } connection.sendPacket(message); } }