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}