blob: 22dc3f9d5c8e15b4217b2c81e5aa332c13e39c7d [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
2 * $RCSfile$
3 * $Revision: 2407 $
4 * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $
5 *
6 * Copyright 2003-2007 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smack;
22
23import java.util.Collection;
24import java.util.Collections;
25import java.util.Map;
26import java.util.Set;
27import java.util.WeakHashMap;
28import java.util.concurrent.CopyOnWriteArraySet;
29
30import org.jivesoftware.smack.filter.AndFilter;
31import org.jivesoftware.smack.filter.FromContainsFilter;
32import org.jivesoftware.smack.filter.PacketFilter;
33import org.jivesoftware.smack.filter.ThreadFilter;
34import org.jivesoftware.smack.packet.Message;
35import org.jivesoftware.smack.packet.Packet;
36import org.jivesoftware.smack.util.StringUtils;
37import org.jivesoftware.smack.util.collections.ReferenceMap;
38
39import java.util.*;
40import java.util.concurrent.CopyOnWriteArraySet;
41
42/**
43 * The chat manager keeps track of references to all current chats. It will not hold any references
44 * in memory on its own so it is neccesary to keep a reference to the chat object itself. To be
45 * made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}.
46 *
47 * @author Alexander Wenckus
48 */
49public class ChatManager {
50
51 /**
52 * Returns the next unique id. Each id made up of a short alphanumeric
53 * prefix along with a unique numeric value.
54 *
55 * @return the next id.
56 */
57 private static synchronized String nextID() {
58 return prefix + Long.toString(id++);
59 }
60
61 /**
62 * A prefix helps to make sure that ID's are unique across mutliple instances.
63 */
64 private static String prefix = StringUtils.randomString(5);
65
66 /**
67 * Keeps track of the current increment, which is appended to the prefix to
68 * forum a unique ID.
69 */
70 private static long id = 0;
71
72 /**
73 * Maps thread ID to chat.
74 */
75 private Map<String, Chat> threadChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
76 ReferenceMap.WEAK));
77
78 /**
79 * Maps jids to chats
80 */
81 private Map<String, Chat> jidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
82 ReferenceMap.WEAK));
83
84 /**
85 * Maps base jids to chats
86 */
87 private Map<String, Chat> baseJidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
88 ReferenceMap.WEAK));
89
90 private Set<ChatManagerListener> chatManagerListeners
91 = new CopyOnWriteArraySet<ChatManagerListener>();
92
93 private Map<PacketInterceptor, PacketFilter> interceptors
94 = new WeakHashMap<PacketInterceptor, PacketFilter>();
95
96 private Connection connection;
97
98 ChatManager(Connection connection) {
99 this.connection = connection;
100
101 PacketFilter filter = new PacketFilter() {
102 public boolean accept(Packet packet) {
103 if (!(packet instanceof Message)) {
104 return false;
105 }
106 Message.Type messageType = ((Message) packet).getType();
107 return messageType != Message.Type.groupchat &&
108 messageType != Message.Type.headline;
109 }
110 };
111 // Add a listener for all message packets so that we can deliver errant
112 // messages to the best Chat instance available.
113 connection.addPacketListener(new PacketListener() {
114 public void processPacket(Packet packet) {
115 Message message = (Message) packet;
116 Chat chat;
117 if (message.getThread() == null) {
118 chat = getUserChat(message.getFrom());
119 }
120 else {
121 chat = getThreadChat(message.getThread());
122 if (chat == null) {
123 // Try to locate the chat based on the sender of the message
124 chat = getUserChat(message.getFrom());
125 }
126 }
127
128 if(chat == null) {
129 chat = createChat(message);
130 }
131 deliverMessage(chat, message);
132 }
133 }, filter);
134 }
135
136 /**
137 * Creates a new chat and returns it.
138 *
139 * @param userJID the user this chat is with.
140 * @param listener the listener which will listen for new messages from this chat.
141 * @return the created chat.
142 */
143 public Chat createChat(String userJID, MessageListener listener) {
144 String threadID;
145 do {
146 threadID = nextID();
147 } while (threadChats.get(threadID) != null);
148
149 return createChat(userJID, threadID, listener);
150 }
151
152 /**
153 * Creates a new chat using the specified thread ID, then returns it.
154 *
155 * @param userJID the jid of the user this chat is with
156 * @param thread the thread of the created chat.
157 * @param listener the listener to add to the chat
158 * @return the created chat.
159 */
160 public Chat createChat(String userJID, String thread, MessageListener listener) {
161 if(thread == null) {
162 thread = nextID();
163 }
164 Chat chat = threadChats.get(thread);
165 if(chat != null) {
166 throw new IllegalArgumentException("ThreadID is already used");
167 }
168 chat = createChat(userJID, thread, true);
169 chat.addMessageListener(listener);
170 return chat;
171 }
172
173 private Chat createChat(String userJID, String threadID, boolean createdLocally) {
174 Chat chat = new Chat(this, userJID, threadID);
175 threadChats.put(threadID, chat);
176 jidChats.put(userJID, chat);
177 baseJidChats.put(StringUtils.parseBareAddress(userJID), chat);
178
179 for(ChatManagerListener listener : chatManagerListeners) {
180 listener.chatCreated(chat, createdLocally);
181 }
182
183 return chat;
184 }
185
186 private Chat createChat(Message message) {
187 String threadID = message.getThread();
188 if(threadID == null) {
189 threadID = nextID();
190 }
191 String userJID = message.getFrom();
192
193 return createChat(userJID, threadID, false);
194 }
195
196 /**
197 * Try to get a matching chat for the given user JID. Try the full
198 * JID map first, the try to match on the base JID if no match is
199 * found.
200 *
201 * @param userJID
202 * @return
203 */
204 private Chat getUserChat(String userJID) {
205 Chat match = jidChats.get(userJID);
206
207 if (match == null) {
208 match = baseJidChats.get(StringUtils.parseBareAddress(userJID));
209 }
210 return match;
211 }
212
213 public Chat getThreadChat(String thread) {
214 return threadChats.get(thread);
215 }
216
217 /**
218 * Register a new listener with the ChatManager to recieve events related to chats.
219 *
220 * @param listener the listener.
221 */
222 public void addChatListener(ChatManagerListener listener) {
223 chatManagerListeners.add(listener);
224 }
225
226 /**
227 * Removes a listener, it will no longer be notified of new events related to chats.
228 *
229 * @param listener the listener that is being removed
230 */
231 public void removeChatListener(ChatManagerListener listener) {
232 chatManagerListeners.remove(listener);
233 }
234
235 /**
236 * Returns an unmodifiable collection of all chat listeners currently registered with this
237 * manager.
238 *
239 * @return an unmodifiable collection of all chat listeners currently registered with this
240 * manager.
241 */
242 public Collection<ChatManagerListener> getChatListeners() {
243 return Collections.unmodifiableCollection(chatManagerListeners);
244 }
245
246 private void deliverMessage(Chat chat, Message message) {
247 // Here we will run any interceptors
248 chat.deliver(message);
249 }
250
251 void sendMessage(Chat chat, Message message) {
252 for(Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors.entrySet()) {
253 PacketFilter filter = interceptor.getValue();
254 if(filter != null && filter.accept(message)) {
255 interceptor.getKey().interceptPacket(message);
256 }
257 }
258 // Ensure that messages being sent have a proper FROM value
259 if (message.getFrom() == null) {
260 message.setFrom(connection.getUser());
261 }
262 connection.sendPacket(message);
263 }
264
265 PacketCollector createPacketCollector(Chat chat) {
266 return connection.createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()),
267 new FromContainsFilter(chat.getParticipant())));
268 }
269
270 /**
271 * Adds an interceptor which intercepts any messages sent through chats.
272 *
273 * @param packetInterceptor the interceptor.
274 */
275 public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) {
276 addOutgoingMessageInterceptor(packetInterceptor, null);
277 }
278
279 public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) {
280 if (packetInterceptor != null) {
281 interceptors.put(packetInterceptor, filter);
282 }
283 }
284}