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

ChatClientFrame.java

Go to the documentation of this file.
00001 
00002 import java.awt.Color;
00003 import java.awt.Dimension;
00004 import java.awt.Font;
00005 import java.awt.Toolkit;
00006 import java.awt.event.ActionEvent;
00007 import java.awt.event.ActionListener;
00008 import java.awt.event.KeyEvent;
00009 import java.awt.event.KeyListener;
00010 import java.awt.event.MouseAdapter;
00011 import java.awt.event.MouseEvent;
00012 
00013 import javax.swing.GroupLayout;
00014 import javax.swing.JButton;
00015 import javax.swing.JEditorPane;
00016 import javax.swing.JFrame;
00017 import javax.swing.JLabel;
00018 import javax.swing.JScrollPane;
00019 import javax.swing.JTextField;
00020 import javax.swing.Timer;
00021 import javax.swing.WindowConstants;
00022 
00023 /**
00024  *  Chat client GUI front-end
00025  * 
00026  *  @author Mikica B Kocic
00027  */
00028 public class ChatClientFrame extends JFrame 
00029        implements ActionListener, KeyListener, ChatClient.Context
00030 {
00031     /**
00032      *  Implements java.io.Serializable interface
00033      */
00034     private static final long serialVersionUID = 7406765627069159382L;
00035 
00036     /**
00037      *  Initial message content of the input text message field.  
00038      */
00039     private static final String defaultInputMsg = "<type message here>";
00040     
00041     /**
00042      *  Host name or IP address of the remote chat server. Default: localhost 
00043      */
00044     private String host = "127.0.0.1";
00045     
00046     /**
00047      *  TCP port where to connect to on remote chat server. Default: 2000
00048      */
00049     private int port = 2000;
00050     
00051     /**
00052      *  Instance of threaded connection to chat server.
00053      */
00054     private ChatClient connection;
00055     
00056     /**
00057      *  Interconnect delay timer. Value -1 means 'disabled'.
00058      */
00059     private int reconnectTimeout = -1;
00060     
00061     /**
00062      *  Retry counter of failed connecting attempts.
00063      */
00064     private int reconnectRetryCount = 0;
00065     
00066     /*  GUI components
00067      */
00068     private JButton     sendButton;
00069     private JTextField  inputMsg;
00070     private JLabel      idLabel;
00071     private JTextField  userId;
00072     private JEditorPane logArea;
00073     
00074     /**
00075      *  Creates a new instance of the <code>ChatClientFrame</code>.
00076      *  
00077      *  @param args the command line arguments passed to main
00078      */
00079     public ChatClientFrame( String args[] )
00080     {
00081         super( "IP1-4.1.1: Yet Another Simple Chat Client" );
00082         
00083         setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE );
00084 
00085         //////////////////////////////////////////////////////////////////////////////////
00086         
00087         /* Top level components
00088          */
00089         Font textFont = new Font( Font.SANS_SERIF, Font.PLAIN, 14 );
00090         
00091         inputMsg = new JTextField( defaultInputMsg, 30 );
00092         inputMsg.addKeyListener( this );
00093         inputMsg.setFont( textFont );
00094         inputMsg.selectAll ();
00095 
00096         sendButton = new JButton ();
00097         sendButton.setText( "Send" );
00098 
00099         sendButton.addMouseListener(
00100                 new MouseAdapter () {
00101                     public void mouseClicked( MouseEvent evt ) {
00102                         sendButton_Clicked( evt );
00103                     }
00104                 }
00105             );
00106 
00107         idLabel = new JLabel( "My ID:" );
00108         idLabel.setFont( textFont );
00109         
00110         userId = new JTextField( System.getProperty( "user.name" ) );
00111         userId.addKeyListener( this );
00112         userId.setFont( textFont );
00113 
00114         logArea = new JEditorPane ();
00115         logArea.setFont( textFont );
00116         logArea.setBackground( new Color( 255, 255, 224 ) );
00117         logArea.setForeground( Color.BLACK );
00118         logArea.setEditable( false );
00119         
00120         logArea.setContentType( "text/html" );
00121         logArea.setText( "<html><head></head><body>\n</body></html>" ); 
00122 
00123         JScrollPane logPane = new JScrollPane ();
00124         logPane.setViewportView( logArea );
00125 
00126         //////////////////////////////////////////////////////////////////////////////////
00127         
00128         /* Layout (self explained?): 
00129          *     Upper: sendButton, inputMsg, idLabel and userId 
00130          *     Lower: scrolled logArea 
00131          */
00132         GroupLayout layout = new GroupLayout( getContentPane () );
00133         getContentPane().setLayout( layout );
00134         layout.setAutoCreateContainerGaps( true );
00135         layout.setAutoCreateGaps( true );
00136 
00137         layout.setHorizontalGroup
00138         (
00139             layout
00140                 .createParallelGroup( GroupLayout.Alignment.LEADING )
00141                 .addGroup
00142                 ( 
00143                     layout
00144                         .createSequentialGroup ()
00145                         .addGroup
00146                         ( 
00147                             layout
00148                                 .createParallelGroup( GroupLayout.Alignment.LEADING )
00149                                 .addGroup
00150                                 (
00151                                     GroupLayout.Alignment.TRAILING, 
00152                                     layout
00153                                         .createSequentialGroup ()
00154                                         .addComponent( sendButton )
00155                                         .addComponent( inputMsg )
00156                                         .addComponent( idLabel )
00157                                         .addComponent( userId )
00158                                 )
00159                                 .addComponent( logPane )
00160                         )
00161                 )
00162         );
00163         
00164         layout.setVerticalGroup
00165         (
00166             layout
00167                 .createParallelGroup( GroupLayout.Alignment.LEADING )
00168                 .addGroup
00169                 (
00170                     layout
00171                         .createSequentialGroup ()
00172                         .addGroup
00173                         (
00174                             layout
00175                                 .createParallelGroup( GroupLayout.Alignment.BASELINE )
00176                                 .addComponent( sendButton )
00177                                 .addComponent( inputMsg )
00178                                 .addComponent( idLabel )
00179                                 .addComponent( userId )
00180                         )
00181                         .addComponent( logPane )
00182                 )
00183         );
00184         
00185         pack();
00186         
00187         //////////////////////////////////////////////////////////////////////////////////
00188         
00189         /* Adjust window dimensions not to exceed screen dimensions ...
00190          */
00191         Dimension win = new Dimension( 1024, 600 );
00192         Dimension scsz = Toolkit.getDefaultToolkit().getScreenSize();
00193         win.width  = Math.min( win.width, scsz.width );
00194         win.height = Math.min( win.height, scsz.height - 40 );
00195         setSize( win );
00196         
00197         /* ... then center window on the screen.
00198          */
00199         setLocation( ( scsz.width - win.width )/2, ( scsz.height - 40 - win.height )/2 );
00200         
00201         /* Parse arguments: [ <host> [ <port> ] ]
00202          */
00203         if ( args.length >= 1 )
00204         {
00205             host = args[ 0 ];
00206             if( args.length >= 2 ) try {
00207                 port = Integer.parseInt( args[ 1 ] );
00208             } catch ( NumberFormatException e ) {
00209                 // TODO might report that we are using default port?
00210             }
00211         }
00212         
00213         /* Default 'usage' info...
00214          */
00215         logMessage( "<table border='0' cellspacing='2' align='right' color='#0000FF'>"
00216                 + "<td><b>Usage:</b>&nbsp;</td>\n"
00217                 + "<td><table border='0' cellspacing='0' bgcolor='#E0F0E0'>"
00218                 + "<tr><td><code>:open [ &lt;hostname&gt; [ &lt;port&gt; ] ]</code></td>"
00219                 + "<td><code><em>-- open new connection</em></code></td></tr>\n"
00220                 + "<tr><td><code>:close</code></td>"
00221                 + "<td><code><em>-- close current connection</em></code></td></tr>\n"
00222                 + "<tr><td><code>:exit</code></td>"
00223                 + "<td><code><em>-- quit application</em></code></td></tr>\n"
00224                 + "<tr><td><code>:who</code></td>"
00225                 + "<td><code><em>-- send 'wwhhoo' to chat server</em></code></td></tr>\n"
00226                 + "</table>"
00227                 + "</td></tr></table>"
00228                 );
00229 
00230         /* Open communication link to server...
00231          */
00232         connection = new ChatClient( host, port, this );
00233         connection.start ();
00234 
00235         /* Instantiate connection monitor timer (for reconnect supervision)
00236          */
00237         Timer timer = new Timer( 1000, this );
00238         timer.start ();
00239 
00240         /* Ready for user to type in something...
00241          */
00242         inputMsg.selectAll ();
00243         inputMsg.requestFocus ();
00244     }
00245     
00246     /**
00247      *  Parses input message and send it to chat server.
00248      */
00249     private void sendButton_Clicked( MouseEvent evt ) 
00250     {
00251         parseInputMessage ();
00252     }
00253 
00254     /**
00255      *  Logs message formated with limited HTML (limited because of JEditoPane)
00256      *  
00257      *  @param str    message that will be logged
00258      */
00259     @Override
00260     public void logMessage( String str )
00261     {
00262         final String strCopy = new String( str );
00263         
00264         java.awt.EventQueue.invokeLater( 
00265                 new Runnable() {
00266                     public void run() {
00267                         synchronized( logArea )
00268                         {
00269                             /* Append the string to the end of <body> element...
00270                              */
00271                             String html = logArea.getText ();
00272                             html = html.replace( "</body>", strCopy + "\n</body>" );
00273                             logArea.setText( html );
00274                         }
00275                     }
00276                 }
00277             );
00278     }
00279     
00280     /**
00281      *  Updates status message by updating window title
00282      *  
00283      *  @param str    new status message
00284      */
00285     @Override
00286     public void logStatus( String str )
00287     {
00288         final String strCopy = new String( str );
00289         
00290         java.awt.EventQueue.invokeLater( 
00291                 new Runnable() {
00292                     public void run() {
00293                         setTitle( "IP1-4.1.1 Chat Client: " + strCopy );
00294                     }
00295                 }
00296             );
00297     }
00298 
00299     /**
00300      *  Handle events from the Swing Timer. If connection is detected to be down, it will 
00301      *  try to reconnect to chat server after some period of time. If reconnection
00302      *  retry count exceeded maximum, timer will stop retrying.
00303      */
00304     public void actionPerformed( ActionEvent ae )
00305     {
00306         final int maxRetryCount = 3;
00307         final int reconnectDelay = 2;
00308         
00309         if ( connection.isAlive () ) {
00310             reconnectTimeout = -1; // disables timer
00311             return;
00312         }
00313 
00314         if ( reconnectRetryCount >= maxRetryCount ) {
00315 
00316             if ( reconnectRetryCount == maxRetryCount ) {
00317                 
00318                 ++reconnectRetryCount;
00319                 
00320                 logMessage( "<br/><code>Press ENTER to quit or type<br/><br/>"
00321                     + "&nbsp;&nbsp; :open [ &lt;hostname&gt; [ &lt;port&gt; ] ]<br/><br/>"
00322                     + "to open new connection...</code><br/><br/>" 
00323                     );
00324                 logStatus( "Dead" );
00325             }
00326             
00327             return; // leave to user to quit by pressing ENTER
00328         } 
00329             
00330         if ( reconnectTimeout < 0 )  {
00331             logStatus( "Disconnected" );
00332             logMessage( "<br/><code>Reconnecting in " 
00333                     + reconnectDelay + " seconds...</code>"
00334                     );
00335             reconnectTimeout = reconnectDelay; // start timer
00336             return;
00337         }
00338         
00339         if ( --reconnectTimeout > 0 ) {
00340             return;
00341         }
00342         
00343         logMessage( "<code>retry #"
00344                + ( ++reconnectRetryCount ) 
00345                + " of max " 
00346                + maxRetryCount
00347                + ":</code><br/>"
00348                );
00349 
00350         reconnectTimeout = -1; // disables timer and restarts new connection
00351 
00352         connection = new ChatClient( host, port , this );
00353         connection.start ();
00354     }
00355 
00356     /**
00357      *  Implements <code>KeyListener</code>'s key pressed event.
00358      */
00359     public void keyPressed( KeyEvent ke ) {
00360         
00361         int keyCode = ke.getKeyCode ();
00362         
00363         if( keyCode == KeyEvent.VK_ENTER )
00364         {
00365             parseInputMessage ();
00366         }
00367     }
00368 
00369     /**
00370      *  Parses input message from inputMsg and sends it to chat server. Also performs
00371      *  various commands :open, :close, :exit ...
00372      */
00373     public void parseInputMessage ()
00374     {
00375         /* Split string into words, removing all leading, trailing 
00376          * and superfluous (btw words) white-spaces
00377          */
00378         String[] args = inputMsg.getText().trim().split( "\\s{1,}" );
00379         
00380         String cmd = args.length > 0 ? args[0] : "";
00381 
00382         if ( cmd.equalsIgnoreCase( ":who" ) ) {
00383             inputMsg.setText( "" );
00384             connection.send( "wwhhoo" );
00385             return;
00386         }
00387         else if ( cmd.equalsIgnoreCase( ":close" ) ) {
00388             inputMsg.setText( "" );
00389             reconnectRetryCount = Integer.MAX_VALUE; // suppresses reconnection
00390             connection.close ();
00391             return;
00392         }
00393         else if ( cmd.equalsIgnoreCase( ":open" ) ) {
00394             inputMsg.setText( "" );
00395             reconnectRetryCount = Integer.MAX_VALUE; // suppresses reconnection
00396             connection.close ();
00397             
00398             if ( args.length >= 2 )
00399             {
00400                 host = args[ 1 ];
00401                 port = 2000; // default port
00402                 if( args.length >= 3 ) try {
00403                     port = Integer.parseInt( args[ 2 ] );
00404                 } catch ( NumberFormatException e ) {
00405                     // TODO handle this
00406                 }
00407             }
00408             
00409             connection = new ChatClient( host, port , this );
00410             connection.start ();
00411             reconnectRetryCount = 0;
00412             return;
00413         }
00414         else if ( cmd.equalsIgnoreCase( ":exit" ) ) {
00415             inputMsg.setText( "" );
00416             connection.close ();
00417             System.exit( 0 );
00418         }
00419 
00420         /* Default task: send message to chat server
00421          */
00422         if ( defaultInputMsg.equalsIgnoreCase( inputMsg.getText () ) ) {
00423             /* ignore default input message */
00424         } else if ( userId.getText ().trim ().length () == 0 ) { // an empty userId
00425             connection.send( inputMsg.getText () );
00426             inputMsg.setText( "" );
00427         } else {
00428             connection.send( inputMsg.getText (), userId.getText () );
00429             inputMsg.setText( "" );
00430         }
00431 
00432         if ( ! connection.isAlive () )
00433         {
00434             System.exit( 0 );
00435         }
00436     }
00437     
00438     /**
00439      *  Implements <code>KeyListener</code>'s key released event.
00440      */
00441     public void keyReleased( KeyEvent ke )
00442     {
00443         /* unused */
00444     }
00445     
00446     /**
00447      *  Implements <code>KeyListener</code>'s key typed event.
00448      */
00449     public void keyTyped( KeyEvent ke )
00450     {
00451         /* unused */
00452     }
00453 
00454     /**
00455      *  Main entry point
00456      *  
00457      *  @param args the command line arguments
00458      */
00459     public static void main( String args[] ) 
00460     {
00461         final String[] copyOfArgs = args;
00462         
00463         java.awt.EventQueue.invokeLater( 
00464                 new Runnable() {
00465                     public void run() {
00466                         new ChatClientFrame( copyOfArgs ).setVisible( true );
00467                     }
00468                 }
00469             );
00470     }
00471 }
00472 
00473 /*! 
00474  *  \mainpage Yet Another Chat Client
00475  *
00476  *  \section s_intro Introduction
00477  *  
00478  *  The package implements solution to \ref p_task as a part of 
00479  *  the <a href="http://dsv.su.se/utbildning/distans/ip1" target="_blank"><b>SU/IP1 
00480  *  course</b></a>.
00481  *  
00482  *  \image html chatClient.png
00483  *  
00484  *  \section s_desc Description
00485  *  
00486  *  The application connects to remote chat server and waits for user input.
00487  *  User messages are prefixed with user ID (entered in separate text field) and sent 
00488  *  to server. Initial user ID is retrieved using 
00489  *  <code>System.getProperty( "user.name" )</code>.
00490  *  
00491  *  The message format is:
00492  *  
00493  *   - <code>[ &lt;user-id&gt; :: ] &lt;message&gt; &lt;new-line&gt;</code>
00494  *  
00495  *  It is expected that server distributes (broadcasts)
00496  *  messages to all participants, so the user should receive its own message back from
00497  *  the server immediately.
00498  *  
00499  *  Inbound messages are captured by the instance of ChatClient class in a separate
00500  *  thread ChatClient.run().
00501  *  Received messages are presented to the user in <code>JEditPane</code> component of the
00502  *  ChatClientFrame GUI class. The client uses HTML to markup messages. 
00503  *  
00504  *  Chat client accepts following commands from the user:
00505  *  
00506  *   - <code>:open [ &lt;host&gt; [ &lt;port&gt; ] ]</code> -- opens new connection
00507  *   - <code>:close</code> -- closes current connection
00508  *   - <code>:exit</code> -- quits application
00509  *   - <code>:who</code> -- sends "wwhhoo" to chat server
00510  *  
00511  *  In case of unsuccessful connection, chat client will retry three times to reconnect.
00512  *  After third unsuccessful reconnect, user can acknowledge mishap and quit application
00513  *  simply buy pressing ENTER. Alternatively, the user may enter <code>:open</code> 
00514  *  command and try to (re)connect to the same or some other chat server.
00515  *  
00516  *  One can start application with the following command:
00517  *  
00518  *   - <code>java -jar <a href="../chatClient.jar">chatClient.jar</a>
00519  *     [ &lt;host&gt; [ &lt;port&gt; ] ]</code>
00520  *   
00521  *  where the default \em host is <code>127.0.0.1</code> and the default \em port is 
00522  *  <code>2000</code>
00523  *  
00524  *  \section s_jar Executable
00525  *  
00526  *  The jar file of the package can be found <a href="../chatClient.jar"><b>here</b></a>.
00527  *   
00528  *  \section s_src Sources
00529  *  
00530  *  Source files:
00531  *   - \ref ChatClient.java
00532  *   - \ref ChatClientFrame.java
00533  */
00534 /*! \page p_task IP1-4.1.1 Uppgift
00535  *
00536  *  Gör ett fristående program med ett grafiskt användargränssnitt som implementerar
00537  *  en chat-klient som kan sända och ta emot text-meddelanden. Chat-klienten ska 
00538  *  koppla upp sig till en chat-server via en stream-socket. När en chat-klient 
00539  *  sänder ett text-meddelande till chat-servern så sänder chat-servern detta 
00540  *  text-meddelande till alla anslutna chat-klienter. Chat-klienten ska alltså 
00541  *  både kunna:
00542  *   
00543  *   - Sända text-meddelanden, inskrivet i exempelvis ett text-fält, till chat-servern 
00544  *   - Ta emot text-meddelanden från chat-servern och visa dessa text-meddelanden i en 
00545  *     lämplig swing-komponent, exempelvis en text-area
00546  *   
00547  *  Chat-klienten ska gå att startas på tre sätt:
00548  *  
00549  *   -# <code>java Client</code> 
00550  *      (då ska host default bli <code>127.0.0.1</code> och port default bli 
00551  *      <code>2000</code>)
00552  *   -# <code>java Client &lt;host&gt;</code> (då ska port default bli <code>2000</code>)
00553  *   -# <code>java Client &lt;host&gt; &lt;port&gt;</code>
00554  *   
00555  *  Efter detta ska chat-klienten försöka koppla sig till chat-servern. Om kopplingen
00556  *  lyckas så ska det i chat-klientens titelrad stå vilken server och port man är
00557  *  uppkopplad till. Om kopplingen inte lyckas så ska programmet avslutas. 
00558  * 
00559  *  Frivillig utökning är att även ta med så att chat-klienten klarar av om kopplingen 
00560  *  bryts till chat-servern (exempelvis om chat-servern avslutas) genom att exempelvis 
00561  *  meddela användaren och avsluta sig själv. 
00562  */

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