/*
* Hamsam - Instant Messaging API
* Copyright (C) 2003 Raghu K
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package hamsam.protocol.msn;
import hamsam.net.Connection;
import hamsam.util.log.LogManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This thread reads incoming commands from the server.
*/
class ReaderThread extends Thread
{
/**
* The MSN server associated with this reader thread.
*/
private MsnServer server;
/**
* Connection to read the commands from.
*/
private Connection conn;
/**
* Buffer to put the incoming commands.
*/
private Vector buff;
/**
* Flag used to stop this thread.
*/
private volatile boolean stop;
/**
* Constructs a reader thread that uses a specified connection.
* <code>buff</code> is a buffer that has to be filled with
* the commands received from the server.
*
* @param conn the connection to read the commands from.
* @param buff the buffer to hold the commands received.
*/
ReaderThread(MsnServer server, Connection conn, Vector buff)
{
this.server = server;
this.conn = conn;
this.buff = buff;
}
/**
* Runs this thread.
*/
public void run()
{
try
{
BufferedReader rd = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"));
Logger logger = LogManager.getLogger();
while(!stop)
{
AbstractCommand cmd = readNextCommand(rd);
// If there is nothing to be read, wait for some time and then retry.
if(cmd == null)
{
Thread.sleep(500);
continue;
}
String msg = "Server: " + cmd.toString();
if(!msg.endsWith("\n"))
msg += '\n';
logger.logp(Level.INFO, this.getClass().getName(), "run", msg);
synchronized(buff)
{
buff.add(cmd);
buff.notify();
}
}
}
catch(IOException e)
{
server.readerExited();
}
catch(InterruptedException e)
{
server.readerExited();
}
}
/**
* Stop this thread.
*/
void stopThread()
{
stop = true;
}
/**
* Returns the next command from the connection, or null if nothing is available.
*
* @return next command from the connection, or null if nothing is available.
*/
private AbstractCommand readNextCommand(BufferedReader rd) throws IOException
{
if(!rd.ready())
return null;
String line = rd.readLine();
if(line == null)
return null;
StringTokenizer tok = new StringTokenizer(line);
String type = tok.nextToken();
if(type.equals("MSG"))
return getNewMessage(tok, rd);
else if(isErrorCode(type))
return getNewErrorCommand(type, tok);
else
{
Command cmd = new Command(type);
if(hasTransactionID(type))
{
String token = tok.nextToken();
try
{
cmd.setTransactionID(new Integer(token));
}
catch(NumberFormatException e)
{
cmd.addParam(token);
}
}
while(tok.hasMoreTokens())
cmd.addParam(tok.nextToken());
return cmd;
}
}
/**
* Check if a string is an error code.
*
* @return true if this string represents an MSN error code, false otherwise.
*/
private boolean isErrorCode(String str)
{
if(str == null)
return false;
else if(str.length() != 3)
return false;
else
{
try
{
Integer.parseInt(str);
return true;
}
catch(NumberFormatException e)
{
return false;
}
}
}
/**
* Read the body part of a message from the server stream, given its length.
*
* @param rd reader to read from.
* @param count number of characters to be read
* @return the body of the message
* @exception IOException if an I/O error occurs.
*/
private char[] readMessageBody(BufferedReader rd, int count) throws IOException
{
char[] ret = new char[count];
rd.read(ret, 0, count);
return ret;
}
/**
* Get a new message from the connection.
*
* @param tok StringTokenizer to get passport, alias, and message length.
* @param rd Used to read the message body.
*/
private MsnMessage getNewMessage(StringTokenizer tok, BufferedReader rd) throws IOException
{
String passport = tok.nextToken();
String alias = tok.nextToken();
int count = Integer.parseInt(tok.nextToken());
char[] data = readMessageBody(rd, count);
MsnMessage msg = new MsnMessage(passport, alias, data);
return msg;
}
/**
* Returns an error command as specified by parameters.
*
* @param type the type of the error command.
* @param tok StringTokenizer to get transaction id, if any.
*/
private ErrorCommand getNewErrorCommand(String type, StringTokenizer tok)
{
ErrorCommand err = new ErrorCommand(type);
if(tok.hasMoreTokens())
{
try
{
err.setTransactionID(new Integer(tok.nextToken()));
}
catch(NumberFormatException e)
{
// This is a server generated error - no transaction ID available
}
}
return err;
}
/**
* Checks whether this command can have a transaction ID.
*
* @param type the type identifier of command to check.
* @return true, if this command will have transaction id, false otherwise.
*/
private boolean hasTransactionID(String type)
{
return (!type.equals("MSG") && !type.equals("NLN") &&
!type.equals("FLN") && !type.equals("RNG") &&
!type.equals("JOI") && !type.equals("BYE") &&
!type.equals("OUT") && !type.equals("BPR") &&
!type.equals("CHL") && !type.equals("GTC") &&
!type.equals("LSG") && !type.equals("LST") &&
!type.equals("BPR") && !type.equals("QNG"));
}
}