• Main Page
  • Related Pages
  • Classes
  • Files
  • File List

ChatClient.java

Go to the documentation of this file.
00001 
00002 import java.io.BufferedReader;
00003 import java.io.IOException;
00004 import java.io.InputStreamReader;
00005 import java.io.OutputStreamWriter;
00006 import java.io.PrintWriter;
00007 import java.net.Socket;
00008 import java.net.UnknownHostException;
00009 import java.text.SimpleDateFormat;
00010 import java.util.Calendar;
00011 
00012 /**
00013  *  Threaded chat client.
00014  *
00015  *  @author Mikica B Kocic
00016  */
00017 public class ChatClient extends Thread 
00018 {
00019     /**
00020      *  Provides message presentation context to instance of <code>ChatClient</code>.
00021      */
00022     public interface Context
00023     {
00024         /**
00025          *  Logs messages formated with HTML tags
00026          *  
00027          *  @param str    message that will be logged
00028          */
00029         public abstract void logMessage( String str );
00030 
00031         /**
00032          *  Updates status message of the parent
00033          *  
00034          *  @param str    new status message
00035          */
00036         public abstract void logStatus( String str );
00037     }
00038 
00039     //////////////////////////////////////////////////////////////////////////////////////
00040 
00041     /**
00042      *  Host name or IP address of the remote chat server 
00043      */
00044     private String host;
00045     
00046     /**
00047      *  TCP port where to connect to on remote chat server 
00048      */
00049     private int port;
00050 
00051     /**
00052      *  Output stream to remote server
00053      */
00054     private PrintWriter out;
00055     
00056     /**
00057      *  Instance of the TCP socket to chat server.
00058      */
00059     private Socket socket;
00060     
00061     /**
00062      *  Indicates if thread is (or should be) running
00063      */
00064     private volatile boolean running;
00065     
00066     /**
00067      *  Event (call-back) context for the instance
00068      */
00069     private Context context;
00070 
00071     //////////////////////////////////////////////////////////////////////////////////////
00072 
00073     /**
00074      *  Creates new instance of <code>ChatClient</code> that posts messages 
00075      *  to specified <code>Context</code>. 
00076      *  
00077      *  @param host     host name or ip address of the chat server
00078      *  @param port     tcp port
00079      *  @param context  where to log messages (also error and info messages)
00080      */
00081     public ChatClient( String host, int port, Context context )
00082     {
00083         assert( context != null );
00084 
00085         this.socket   = null;
00086         this.host     = host;
00087         this.port     = port;
00088         this.context  = context;
00089         this.out      = null;   
00090         this.running  = false;
00091     }
00092 
00093     /**
00094      * Starts the thread.
00095      */
00096     @Override
00097     public void start ()
00098     {
00099         if ( isAlive () || running ) {
00100             return;  // Allow only one thread per instance
00101         }
00102 
00103         running = true;
00104         super.start ();
00105     }
00106 
00107     /**
00108      *  Sends message (appended with new-line) to chat server
00109      *  
00110      *  @param message   message to be sent
00111      */
00112     public void send( String message )
00113     {
00114         synchronized( this )
00115         {
00116             if ( out == null || message == null ) {
00117                 return;
00118             }
00119             
00120             out.println( message );
00121             out.flush ();
00122         }
00123     }
00124 
00125     /**
00126      *  Sends message (appended with new-line) to chat server prefixed with userID
00127      *  
00128      *  @param message    message to be sent
00129      *  @param userId    user identifier
00130      */
00131     public void send( String message, String userId )
00132     {
00133         send( userId + " :: " + message );
00134     }
00135 
00136     /**
00137      *  Closes the connection gracefully
00138      */
00139     public void close ()
00140     {
00141         synchronized( this )
00142         {
00143             running = false;
00144 
00145             if ( socket != null && ! socket.isClosed () ) {
00146                 try {
00147                     socket.close ();
00148                 } catch( IOException e ) {
00149                     /* ignore */
00150                 }
00151             }
00152         }
00153     }
00154     
00155     /**
00156      *  Escapes HTML reserved characters (as we are logging HTML tagged messages)
00157      */
00158     private static String EscapeHTML( String str )
00159     {
00160         return str.replaceAll( "&",  "&amp;"  ) 
00161                   .replaceAll( "<",  "&lt;"   )
00162                   .replaceAll( ">",  "&gt;"   ) 
00163                   .replaceAll( "\"", "&quot;" );
00164     }
00165 
00166     /**
00167      *  Reports an error message to log 
00168      */
00169     private void reportError( String str )
00170     {
00171         context.logMessage(
00172             "<code>" + now () 
00173                      + "&nbsp;&nbsp;<span style='color:red'>" 
00174                      + EscapeHTML( str ) 
00175                      + "</span></code><br/>"
00176             );
00177     }
00178 
00179     /**
00180      *  Reports an info message to log 
00181      */
00182     private void reportInfo( String str )
00183     {
00184         context.logMessage(
00185             "<code>" + now () 
00186                      + "&nbsp;&nbsp;<span style='color:green'>"
00187                      + EscapeHTML( str )
00188                      + "</span></code><br/>"
00189             );
00190     }
00191 
00192     /**
00193      *  Gets current time stamp
00194      *  
00195      *  @return time in ISO format 
00196      */
00197     private static String now ()
00198     {
00199         Calendar cal = Calendar.getInstance ();
00200         SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
00201         return sdf.format( cal.getTime() );
00202     }
00203 
00204     /**
00205      *  Connects socket, then reads messages from server while <code>running</code> flag 
00206      *  is enabled. Finally, closes connection in graceful manner.
00207      */
00208     @Override
00209     public void run ()
00210     {
00211         reportInfo( "Connecting to " + host + ":" + port + "..." );
00212         context.logStatus( "Connecting to " + host + ":" + port + "..." );
00213 
00214         //////////////////////////////////////////////////////////////////////////////////
00215         /* Open connection
00216          */
00217         try
00218         {
00219             synchronized( this ) {
00220                 socket = new Socket( host, port );
00221             }
00222         }
00223         catch( UnknownHostException e )
00224         {
00225             reportError( "'Unknown host' exception while creating socket" );
00226             reportError( e.toString () );
00227             running = false;
00228         }
00229         catch( IOException e )
00230         {
00231             reportError( "I/O exception while connecting" );
00232             reportError( e.toString () );
00233             running = false;
00234         }
00235 
00236         //////////////////////////////////////////////////////////////////////////////////
00237         /* Get input stream. Consider input characters UTF-8 encoded.
00238          * TODO: It would be nice to have this as a parameter.
00239          */
00240         InputStreamReader reader = null;
00241         try
00242         {
00243             if ( socket != null ) {
00244                 reader = new InputStreamReader( socket.getInputStream (), "utf-8" );
00245             }
00246         }
00247         catch( IOException e )
00248         {
00249             reportError( "I/O exception while getting input stream" );
00250             reportError( e.toString () );
00251             running = false;
00252             reader = null;
00253         }
00254         
00255         //////////////////////////////////////////////////////////////////////////////////
00256         /* Get output stream. Encode our character strings as UTF-8.
00257          */
00258         try 
00259         {
00260             if ( socket != null ) {
00261                 out = new PrintWriter(
00262                         new OutputStreamWriter( socket.getOutputStream(), "utf-8" ),
00263                         /*autoflush*/ true );
00264             }
00265         }
00266         catch( IOException e )
00267         {
00268             reportError( "I/O exception while getting output stream" );
00269             reportError( e.toString () );
00270             running = false;
00271             out = null;
00272         }
00273 
00274         //////////////////////////////////////////////////////////////////////////////////
00275         /* Finally connected... (if running == true survived until here)
00276          */
00277         BufferedReader in = null;
00278         if ( running ) {
00279             context.logStatus( "Connected to " + host + ":" + port );
00280             reportInfo( "Connected; type a message, then press Enter or click 'Send'" );
00281             reportInfo( "All your messages will be prefixed with your ID..." );
00282             in = new BufferedReader( reader );
00283         }
00284         
00285         //////////////////////////////////////////////////////////////////////////////////
00286         /* Read messages from the socket and dump them on log area
00287          */
00288         while( running )
00289         {
00290             try
00291             {
00292                 String message = in.readLine ();
00293 
00294                 /* Parse input with syntax: [ [ <userId> ] ":: " ] <message>
00295                  * where default userId is [Anonymous].
00296                  */
00297                 String[] parts = message.split( ":: ", 2 );
00298                 String userId = "[Anonymous]";
00299                 
00300                 if ( message.startsWith( "WWHHOO: " ) ) {
00301                     userId = "WWHHOO";
00302                     message = message.substring( 8 );
00303                 } else if ( parts.length == 0 ) {
00304                     message = parts[0];
00305                 } else if ( parts[0].trim().length () == 0 && parts.length >= 2 ) {
00306                     message = parts[1];
00307                 } else if ( parts.length >= 2 ) {
00308                     userId  = parts[0].trim ();
00309                     message = parts[1];
00310                 } else {
00311                     message = parts[0];
00312                 }
00313 
00314                 context.logMessage(
00315                     "<code>" + now () 
00316                              + "&nbsp;&nbsp;" 
00317                              + EscapeHTML( userId ).trim () 
00318                              + ": <span style='"
00319                              + ( userId.equals( "Eliza" ) ? "color:red" : "color:blue" )
00320                              + "'>"
00321                              + EscapeHTML( message ).trim ()
00322                              + "</span></code><br/>"
00323                     );
00324             }
00325             catch( IOException e )
00326             {
00327                 reportError( "Connection lost!" );
00328                 reportError( e.toString () );
00329                 running = false;
00330             }
00331         }
00332 
00333         reportInfo( "Closing connection " + host + ":" + port + "..." );
00334 
00335         //////////////////////////////////////////////////////////////////////////////////
00336         /* Close connection gracefully
00337          */
00338         try
00339         {
00340             if ( out != null ) {
00341                 out.close ();
00342             }
00343             if ( in != null ) {
00344                 in.close ();
00345             }
00346             synchronized( this )
00347             {
00348                 if ( socket != null && ! socket.isClosed () ) {
00349                     socket.close ();
00350                 }
00351             }
00352         }
00353         catch( IOException e )
00354         {
00355             reportError( "I/O exception while closing connection" );
00356             reportError( e.toString () );
00357         }
00358         
00359         reportInfo( "Thread " + host + ":" + port + " completed." );
00360     }
00361 }

Generated on Thu Dec 16 2010 14:26:28 for Chat Client by  doxygen 1.7.2