| /******************************************************************************* |
| * Copyright (c) 2005, 2008 Remy Suen |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Remy Suen <remy.suen@gmail.com> - initial API and implementation |
| * Stoyan Boshev <s.boshev@prosyst.com> - [MSN] Session and subclasses needs to handle whitespace and exceptions better |
| ******************************************************************************/ |
| package org.eclipse.ecf.protocol.msn; |
| |
| import java.io.IOException; |
| import java.net.ConnectException; |
| import java.util.ArrayList; |
| import org.eclipse.ecf.protocol.msn.events.IChatSessionListener; |
| import org.eclipse.ecf.protocol.msn.internal.encode.StringUtils; |
| |
| /** |
| * <p> |
| * A ChatSession is a conversation that's held between two or more participants. |
| * </p> |
| * |
| * <p> |
| * As specified by {@link MsnClient}'s |
| * {@link MsnClient#disconnect() disconnect()} method, ChatSessions are not |
| * automatically disconnected when the client itself disconnects. However, |
| * clean-up will be performed automatically when a |
| * {@link IChatSessionListener#sessionTimedOut() sessionTimedOut()} event occurs |
| * or when the last user has left (as checked by monitoring the |
| * {@link IChatSessionListener#contactLeft(Contact) contactLeft(Contact)} |
| * event). |
| * </p> |
| * |
| * <p> |
| * <b>Note:</b> This class/interface is part of an interim API that is still |
| * under development and expected to change significantly before reaching |
| * stability. It is being made available at this early stage to solicit feedback |
| * from pioneering adopters on the understanding that any code that uses this |
| * API will almost certainly be broken (repeatedly) as the API evolves. |
| * </p> |
| */ |
| public final class ChatSession extends Session { |
| |
| /** |
| * The list of contacts that are currently a part of this session. Note that |
| * this does not include the client user. |
| */ |
| private final ArrayList contacts; |
| |
| private final ContactList contactList; |
| |
| private final String email = client.getUserEmail(); |
| |
| /** |
| * Create a new ChatSession that connects to the given host. |
| * |
| * @param host |
| * the host to be connected to |
| * @param client |
| * the MsnClient to hook onto |
| * @throws IOException |
| * If an I/O error occurs while attempting to connect to the |
| * host |
| */ |
| ChatSession(String host, MsnClient client) throws IOException { |
| super(host, client); |
| listeners = new ArrayList(); |
| contacts = new ArrayList(); |
| contactList = client.getContactList(); |
| } |
| |
| /** |
| * Create a new ChatSession that will connect to the given server response |
| * using the specified username. |
| * |
| * @param host |
| * the host to be connected to |
| * @param client |
| * the MsnClient to hook onto |
| * @param username |
| * the username to authenticate with |
| * @param info |
| * the authentication info |
| * @throws IOException |
| * If an I/O error occurs while attempting to connect to the |
| * specified host |
| */ |
| ChatSession(String host, MsnClient client, String username, String info) throws IOException { |
| this(host, client); |
| authenticate(username, info); |
| } |
| |
| /** |
| * This method attempts to authenticate the user with the switchboard server |
| * that it was instantiated to. |
| * |
| * @param username |
| * the user's MSN email address |
| * @param info |
| * the authentication information |
| * @throws IOException |
| */ |
| private void authenticate(String username, String info) throws IOException { |
| write("USR", username + ' ' + info); //$NON-NLS-1$ |
| final String input = super.read(); |
| // FIXME: check if this indexOf(String) call can be replaced with |
| // startsWith(String) |
| if (input == null || input.indexOf("USR") == -1) { //$NON-NLS-1$ |
| throw new ConnectException("Authentication has failed with the switchboard server."); //$NON-NLS-1$ |
| } |
| idle(); |
| } |
| |
| public void close() { |
| try { |
| write("OUT"); //$NON-NLS-1$ |
| } catch (final Exception e) { |
| // ignored |
| } |
| super.close(); |
| } |
| |
| /** |
| * Invites the user with the specified email to this chat session. |
| * |
| * @param userEmail |
| * the user's email address |
| * @throws IOException |
| * If an I/O error occurs while attempting to send the |
| * invitation to the user |
| */ |
| public void invite(String userEmail) throws IOException { |
| synchronized (contacts) { |
| for (int i = 0; i < contacts.size(); i++) { |
| if (((Contact) contacts.get(i)).getEmail().equals(userEmail)) { |
| return; |
| } |
| } |
| } |
| write("CAL", userEmail); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Sends a notifying event to the listeners connected to this that the |
| * specified contact has joined this switchboard. |
| * |
| * @param contact |
| * the contact that has joined this session |
| */ |
| private void fireContactJoinedEvent(Contact contact) { |
| synchronized (listeners) { |
| for (int i = 0; i < listeners.size(); i++) { |
| ((IChatSessionListener) listeners.get(i)).contactJoined(contact); |
| } |
| } |
| } |
| |
| /** |
| * Informs the {@link IChatSessionListener}s attached to this that the |
| * given contact has left this session. |
| * |
| * @param contact |
| * the contact that left this session |
| */ |
| private void fireContactLeftEvent(Contact contact) { |
| synchronized (listeners) { |
| for (int i = 0; i < listeners.size(); i++) { |
| ((IChatSessionListener) listeners.get(i)).contactLeft(contact); |
| } |
| } |
| } |
| |
| /** |
| * This event is fired when the specified contact has started typing. |
| * |
| * @param contact |
| * the contact who is typing |
| */ |
| private void fireContactIsTypingEvent(Contact contact) { |
| synchronized (listeners) { |
| for (int i = 0; i < listeners.size(); i++) { |
| ((IChatSessionListener) listeners.get(i)).contactIsTyping(contact); |
| } |
| } |
| } |
| |
| /** |
| * This event is fired when a message has been received from the given user. |
| * |
| * @param contact |
| * the user that sent the message |
| * @param message |
| * the message that has been received |
| */ |
| private void fireMessageReceivedEvent(Contact contact, String message) { |
| synchronized (listeners) { |
| for (int i = 0; i < listeners.size(); i++) { |
| ((IChatSessionListener) listeners.get(i)).messageReceived(contact, message); |
| } |
| } |
| } |
| |
| /** |
| * Notifies attached {@link IChatSessionListener}s that this session has now |
| * timed out. |
| */ |
| private void fireSessionTimedOutEvent() { |
| synchronized (listeners) { |
| for (int i = 0; i < listeners.size(); i++) { |
| ((IChatSessionListener) listeners.get(i)).sessionTimedOut(); |
| } |
| } |
| } |
| |
| /** |
| * Look for a contact that is connected to this switchboard connected to the |
| * given email. Comparison is done with the String class's equals(String) |
| * method, so case sensitivity is an issue. |
| * |
| * @param userEmail |
| * the email of the contact being sought after |
| * @return the contact that uses the specified email |
| * @throws IllegalArgumentException |
| * If the contact could not be found |
| */ |
| private Contact findContact(String userEmail) throws IllegalArgumentException { |
| for (int i = 0; i < contacts.size(); i++) { |
| final Contact contact = (Contact) contacts.get(i); |
| if (contact.getEmail().equals(userEmail)) { |
| return contact; |
| } |
| } |
| throw new IllegalArgumentException("A contact with the email " + userEmail + " could not be found in this ChatSession."); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Read the contents of the packet being sent from the server and handle any |
| * events accordingly. |
| * |
| * @return the String returned from {@link Session#read()} |
| */ |
| String read() throws IOException { |
| final String input = super.read(); |
| if (input == null) { |
| return null; |
| } |
| |
| final String[] lines = StringUtils.split(input, "\r\n"); //$NON-NLS-1$ |
| for (int i = 0; i < lines.length; i++) { |
| if (lines[i].startsWith("IRO")) { //$NON-NLS-1$ |
| final String[] split = StringUtils.splitOnSpace(lines[i]); |
| Contact contact = contactList.getContact(split[4]); |
| if (contact == null) { |
| contact = new Contact(split[4], split[5]); |
| } |
| contacts.add(contact); |
| fireContactJoinedEvent(contact); |
| } else if (lines[i].startsWith("JOI")) { //$NON-NLS-1$ |
| final String[] split = StringUtils.splitOnSpace(lines[i]); |
| Contact contact = contactList.getContact(split[2]); |
| if (contact == null) { |
| contact = new Contact(split[1], split[2]); |
| } |
| contacts.add(contact); |
| fireContactJoinedEvent(contact); |
| } else if (lines[i].startsWith("BYE")) { //$NON-NLS-1$ |
| final String[] split = StringUtils.splitOnSpace(lines[i]); |
| if (split.length == 2) { |
| final Contact contact = findContact(split[1]); |
| contacts.remove(contact); |
| fireContactLeftEvent(contact); |
| if (contacts.isEmpty()) { |
| close(); |
| } |
| } else { |
| fireSessionTimedOutEvent(); |
| close(); |
| } |
| } else if (lines[i].startsWith("MSG")) { //$NON-NLS-1$ |
| if (input.indexOf("TypingUser:") != -1) { //$NON-NLS-1$ |
| final String trim = input.substring(input.indexOf("MSG")); //$NON-NLS-1$ |
| final String content = StringUtils.splitSubstring(trim, "\r\n", 3); //$NON-NLS-1$ |
| fireContactIsTypingEvent(findContact(StringUtils.splitOnSpace(content)[1])); |
| } else if (input.indexOf("text/plain") != -1) { //$NON-NLS-1$ |
| final int index = input.indexOf("ANS") == -1 ? 2 : 3; //$NON-NLS-1$ |
| final String[] contents = StringUtils.split(input, "\r\n", index); //$NON-NLS-1$ |
| String[] split = StringUtils.splitOnSpace(contents[index - 2]); |
| final Contact contact = findContact(split[1]); |
| |
| final int count = Integer.parseInt(split[3]); |
| split = StringUtils.split(contents[index - 1], "\r\n\r\n", 2); //$NON-NLS-1$ |
| |
| final int text = count - (split[0].getBytes("UTF-8").length + 4); //$NON-NLS-1$ |
| fireMessageReceivedEvent(contact, new String(split[1].getBytes("UTF-8"), 0, text, "UTF-8")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| } |
| |
| return input; |
| } |
| |
| /** |
| * <p> |
| * <b>Note:</b> This method will likely be modified in the future (renamed, |
| * change in return type, <tt>Contact[]</tt> <-> <tt>java.util.List</tt>, |
| * inclusion/exclusion of the current user, complete removal, etc.). Please |
| * use it at your own risk. |
| * </p> |
| * |
| * <p> |
| * This method returns the Contacts that are currently participating in this |
| * ChatSession. Note that this does not include the current user. |
| * </p> |
| * @return array of contacts that are the participants. |
| */ |
| public Contact[] getParticipants() { |
| return (Contact[]) contacts.toArray(new Contact[contacts.size()]); |
| } |
| |
| /** |
| * Sends a message to the users connected to this chat session. |
| * |
| * @param message |
| * the message to be sent |
| * @throws IOException |
| * If an I/O occurs when sending the message to the server |
| */ |
| public void sendMessage(String message) throws IOException { |
| message = "MIME-Version: 1.0\r\n" //$NON-NLS-1$ |
| + "Content-Type: text/plain; charset=UTF-8\r\n" //$NON-NLS-1$ |
| + "X-MMS-IM-Format: FN=MS%20Sans%20Serif; EF=; CO=0; PF=0\r\n\r\n" //$NON-NLS-1$ |
| + message; |
| write("MSG", "N " + message.getBytes("UTF-8").length + "\r\n" + message, false); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| |
| /** |
| * Notifies the participants of this chat session that the current user is |
| * typing a message. |
| * |
| * @throws IOException |
| * If an I/O occurs when sending the message to the server |
| */ |
| public void sendTypingNotification() throws IOException { |
| final String message = "MIME-Version: 1.0\r\n" //$NON-NLS-1$ |
| + "Content-Type: text/x-msmsgscontrol\r\nTypingUser: " + email //$NON-NLS-1$ |
| + "\r\n\r\n\r\n"; //$NON-NLS-1$ |
| write("MSG", "U " + message.length() + "\r\n" + message, false); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| } |
| |
| /** |
| * Adds a IChatSessionListener to this session. |
| * |
| * @param listener |
| * the listener to be added |
| */ |
| public void addChatSessionListener(IChatSessionListener listener) { |
| if (listener != null) { |
| synchronized (listeners) { |
| if (!listeners.contains(listener)) { |
| listeners.add(listener); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes a IChatSessionListener from this session. |
| * |
| * @param listener |
| * the listener to be removed |
| */ |
| public void removeChatSessionListener(IChatSessionListener listener) { |
| if (listener != null) { |
| synchronized (listeners) { |
| listeners.remove(listener); |
| } |
| } |
| } |
| } |