001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.chat; 019 020import org.jivesoftware.smack.PacketCollector; 021import org.jivesoftware.smack.SmackException.NotConnectedException; 022import org.jivesoftware.smack.packet.Message; 023import org.jivesoftware.smack.util.StringUtils; 024 025import java.util.Set; 026import java.util.Collections; 027import java.util.concurrent.CopyOnWriteArraySet; 028 029/** 030 * A chat is a series of messages sent between two users. Each chat has a unique 031 * thread ID, which is used to track which messages are part of a particular 032 * conversation. Some messages are sent without a thread ID, and some clients 033 * don't send thread IDs at all. Therefore, if a message without a thread ID 034 * arrives it is routed to the most recently created Chat with the message 035 * sender. 036 * 037 * @author Matt Tucker 038 */ 039public class Chat { 040 041 private ChatManager chatManager; 042 private String threadID; 043 private String participant; 044 private final Set<ChatMessageListener> listeners = new CopyOnWriteArraySet<ChatMessageListener>(); 045 046 /** 047 * Creates a new chat with the specified user and thread ID. 048 * 049 * @param chatManager the chatManager the chat will use. 050 * @param participant the user to chat with. 051 * @param threadID the thread ID to use. 052 */ 053 Chat(ChatManager chatManager, String participant, String threadID) { 054 if (StringUtils.isEmpty(threadID)) { 055 throw new IllegalArgumentException("Thread ID must not be null"); 056 } 057 this.chatManager = chatManager; 058 this.participant = participant; 059 this.threadID = threadID; 060 } 061 062 /** 063 * Returns the thread id associated with this chat, which corresponds to the 064 * <tt>thread</tt> field of XMPP messages. This method may return <tt>null</tt> 065 * if there is no thread ID is associated with this Chat. 066 * 067 * @return the thread ID of this chat. 068 */ 069 public String getThreadID() { 070 return threadID; 071 } 072 073 /** 074 * Returns the name of the user the chat is with. 075 * 076 * @return the name of the user the chat is occuring with. 077 */ 078 public String getParticipant() { 079 return participant; 080 } 081 082 /** 083 * Sends the specified text as a message to the other chat participant. 084 * This is a convenience method for: 085 * 086 * <pre> 087 * Message message = chat.createMessage(); 088 * message.setBody(messageText); 089 * chat.sendMessage(message); 090 * </pre> 091 * 092 * @param text the text to send. 093 * @throws NotConnectedException 094 */ 095 public void sendMessage(String text) throws NotConnectedException { 096 Message message = new Message(); 097 message.setBody(text); 098 sendMessage(message); 099 } 100 101 /** 102 * Sends a message to the other chat participant. The thread ID, recipient, 103 * and message type of the message will automatically set to those of this chat. 104 * 105 * @param message the message to send. 106 * @throws NotConnectedException 107 */ 108 public void sendMessage(Message message) throws NotConnectedException { 109 // Force the recipient, message type, and thread ID since the user elected 110 // to send the message through this chat object. 111 message.setTo(participant); 112 message.setType(Message.Type.chat); 113 message.setThread(threadID); 114 chatManager.sendMessage(this, message); 115 } 116 117 /** 118 * Adds a stanza(/packet) listener that will be notified of any new messages in the 119 * chat. 120 * 121 * @param listener a stanza(/packet) listener. 122 */ 123 public void addMessageListener(ChatMessageListener listener) { 124 if(listener == null) { 125 return; 126 } 127 // TODO these references should be weak. 128 listeners.add(listener); 129 } 130 131 public void removeMessageListener(ChatMessageListener listener) { 132 listeners.remove(listener); 133 } 134 135 /** 136 * Closes the Chat and removes all references to it from the {@link ChatManager}. The chat will 137 * be unusable when this method returns, so it's recommend to drop all references to the 138 * instance right after calling {@link #close()}. 139 */ 140 public void close() { 141 chatManager.closeChat(this); 142 listeners.clear(); 143 } 144 145 /** 146 * Returns an unmodifiable set of all of the listeners registered with this chat. 147 * 148 * @return an unmodifiable set of all of the listeners registered with this chat. 149 */ 150 public Set<ChatMessageListener> getListeners() { 151 return Collections.unmodifiableSet(listeners); 152 } 153 154 /** 155 * Creates a {@link org.jivesoftware.smack.PacketCollector} which will accumulate the Messages 156 * for this chat. Always cancel PacketCollectors when finished with them as they will accumulate 157 * messages indefinitely. 158 * 159 * @return the PacketCollector which returns Messages for this chat. 160 */ 161 public PacketCollector createCollector() { 162 return chatManager.createPacketCollector(this); 163 } 164 165 /** 166 * Delivers a message directly to this chat, which will add the message 167 * to the collector and deliver it to all listeners registered with the 168 * Chat. This is used by the XMPPConnection class to deliver messages 169 * without a thread ID. 170 * 171 * @param message the message. 172 */ 173 void deliver(Message message) { 174 // Because the collector and listeners are expecting a thread ID with 175 // a specific value, set the thread ID on the message even though it 176 // probably never had one. 177 message.setThread(threadID); 178 179 for (ChatMessageListener listener : listeners) { 180 listener.processMessage(this, message); 181 } 182 } 183 184 @Override 185 public String toString() { 186 return "Chat [(participant=" + participant + "), (thread=" + threadID + ")]"; 187 } 188 189 @Override 190 public int hashCode() { 191 int hash = 1; 192 hash = hash * 31 + threadID.hashCode(); 193 hash = hash * 31 + participant.hashCode(); 194 return hash; 195 } 196 197 @Override 198 public boolean equals(Object obj) { 199 return obj instanceof Chat 200 && threadID.equals(((Chat)obj).getThreadID()) 201 && participant.equals(((Chat)obj).getParticipant()); 202 } 203}