Coverage Report - com.buckosoft.fibs.net.ClientConnection
 
Classes in this File Line Coverage Branch Coverage Complexity
ClientConnection
0%
0/185
0%
0/62
4.8
ClientConnection$1
N/A
N/A
4.8
ClientConnection$PingTimerTask
0%
0/5
0%
0/2
4.8
 
 1  
 /******************************************************************************
 2  
  * ClientConnection.java - Manage a connection to the FIBS server
 3  
  * $Id$
 4  
  * 
 5  
  * BuckoFIBS - Backgammon by BuckoSoft
 6  
  * Copyright(c) 2009,2010 - Dick Balaska - BuckoSoft, Corp.
 7  
  * 
 8  
  * $Log$
 9  
  * Revision 1.10  2013/10/01 07:13:23  dick
 10  
  * Send our ping message once a minute.
 11  
  *
 12  
  * Revision 1.9  2013/09/25 03:23:00  dick
 13  
  * isConnected() returns if we are up.
 14  
  *
 15  
  * Revision 1.8  2013/09/19 07:34:44  dick
 16  
  * Add a 25 minute keep-alive ping timer.
 17  
  *
 18  
  * Revision 1.7  2013/09/08 06:16:43  dick
 19  
  * Don't print stack traces after failing to connect.  The message is enough.
 20  
  *
 21  
  * Revision 1.6  2011/06/15 00:41:51  dick
 22  
  * resetFIBSCookieMonster() becomes just reset().
 23  
  *
 24  
  * Revision 1.5  2011/05/22 05:48:15  dick
 25  
  * Point to the "new" cvs location.
 26  
  *
 27  
  * Revision 1.4  2011/05/21 05:07:46  dick
 28  
  * readMessage goes in a try block.
 29  
  *
 30  
  * Revision 1.3  2010/03/03 13:12:21  inim
 31  
  * Replaced (c) sign in comment mangled by CVS default encoding back to UTF-8
 32  
  *
 33  
  * Revision 1.2  2010/03/03 12:19:49  inim
 34  
  * Moved source to UTF8 encoding from CP1252 encoding. To this end all source files' (c) message was updated to "Copyright© 2009,2010 - Dick Balaska - BuckoSoft, Corp.". This replaces the (c) sign to UTF8, and adds the new year 2010.
 35  
  *
 36  
  * Revision 1.1  2010/02/04 05:57:53  inim
 37  
  * Mavenized project folder layout
 38  
  *
 39  
  * Revision 1.20  2009/02/27 05:49:50  dick
 40  
  * Check for bare cr and bare lf at the beginning of a line.
 41  
  *
 42  
  * Revision 1.19  2009/02/25 08:12:14  dick
 43  
  * Check for a leading line feed, not a cr.
 44  
  *
 45  
  * Revision 1.18  2009/02/24 05:36:59  dick
 46  
  * Handle connectionAborted (on write).
 47  
  *
 48  
  * Revision 1.17  2009/02/17 14:45:44  dick
 49  
  * Wrap debug with DEBUG.
 50  
  *
 51  
  * Revision 1.16  2009/02/14 12:29:19  dick
 52  
  * Check for runon messages with just a cr between them.
 53  
  *
 54  
  * Revision 1.15  2009/02/02 08:38:12  dick
 55  
  * login with the app signature instead of hardcoded BuckoFIBS.
 56  
  *
 57  
  * Revision 1.14  2009/01/29 08:27:42  dick
 58  
  * Fix the pushback handling.
 59  
  *
 60  
  * Revision 1.13  2009/01/28 19:42:28  dick
 61  
  * Prettier cvs link in the javadoc.
 62  
  *
 63  
  * Revision 1.12  2009/01/28 08:30:24  dick
 64  
  * Handle the Malformed Messages (Bad_board) as documented as occuring in http://www.fibs.com/fcm/ .
 65  
  * However, we do begin and end regexp on ALL messages.
 66  
  * ClientConnection now supports a pushback buffer so that CookieMonster can deal with the second message later.
 67  
  *
 68  
  * Revision 1.11  2009/01/27 19:17:33  dick
 69  
  * Display the cookie number with the string in stdout.
 70  
  *
 71  
  * Revision 1.10  2009/01/26 17:35:15  dick
 72  
  * Push the CookieMonster down to the ClientConnection.  ClientAdapter now emits the cookie with the string.
 73  
  * There are many messages besides the known "BadBoard" messages that are a result of runon messages (missing crlf).
 74  
  * CookieMonster is going to need to do a full regexp scan and see if there is anything on the right that needs to be pushed back as another message.
 75  
  *
 76  
  * Revision 1.9  2009/01/12 21:48:53  dick
 77  
  * Add setFibsAttributes.
 78  
  *
 79  
  * Revision 1.8  2009/01/12 07:47:07  dick
 80  
  * Update to use the interfaces as ClientConnection is used across multiple applications.
 81  
  *
 82  
  * Revision 1.7  2009/01/09 07:37:48  dick
 83  
  * Turn off debug.
 84  
  *
 85  
  * Revision 1.6  2009/01/08 18:33:49  dick
 86  
  * Only shutdown the socket if it exists.
 87  
  *
 88  
  * Revision 1.5  2009/01/07 04:29:16  dick
 89  
  * Javadoc.
 90  
  *
 91  
  * Revision 1.4  2009/01/07 02:36:56  dick
 92  
  * Better handling when destroying ourselves.
 93  
  *
 94  
  * Revision 1.3  2009/01/05 07:17:20  dick
 95  
  * Use the selected profileId when determining values for the connection.
 96  
  *
 97  
  * Revision 1.2  2008/12/17 04:39:19  dick
 98  
  * Close the socket before deleting it.
 99  
  *
 100  
  * Revision 1.1  2008/12/13 06:57:11  dick
 101  
  * Move ClientConnection, CookieMonster, and FIBSMessages to their own network package.
 102  
  *
 103  
  * Revision 1.8  2008/12/13 06:19:45  dick
 104  
  * getDisplayXmit() becomes isDisplayXmit().
 105  
  *
 106  
  * Revision 1.7  2008/12/12 14:44:41  dick
 107  
  * Handle partial lines received.
 108  
  *
 109  
  * Revision 1.6  2008/12/11 20:25:01  dick
 110  
  * Echo the Xmit messages to the screen if pref enabled.
 111  
  *
 112  
  * Revision 1.5  2008/12/10 17:58:12  dick
 113  
  * Fix the list lock.
 114  
  * Output network messages in color.
 115  
  *
 116  
  * Revision 1.4  2008/12/09 19:31:44  dick
 117  
  * close the sockets on shutdown.
 118  
  *
 119  
  * Revision 1.3  2008/12/09 01:48:24  dick
 120  
  * Buffer receiving doesn't monitor for logins.  CookieMonster does that now.
 121  
  *
 122  
  * Revision 1.2  2008/03/31 07:07:41  dick
 123  
  * Login using clips message handling.
 124  
  *
 125  
  * Revision 1.1  2008/03/30 05:40:33  dick
 126  
  * Early BuckoFIBS networking.  We connect.
 127  
  *
 128  
  */
 129  
 
 130  
 /* 
 131  
  * This program is free software: you can redistribute it and/or modify
 132  
  * it under the terms of the GNU General Public License as published by
 133  
  * the Free Software Foundation, either version 3 of the License, or
 134  
  * (at your option) any later version.
 135  
  *
 136  
  * This program is distributed in the hope that it will be useful,
 137  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 138  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 139  
  * GNU General Public License for more details.
 140  
  *
 141  
  * You should have received a copy of the GNU General Public License
 142  
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 143  
  *
 144  
  * The Original Code is BuckoFIBS, <http://www.buckosoft.com/BuckoFIBS/>.
 145  
  * The Initial Developer of the Original Code is Dick Balaska and BuckoSoft, Corp.
 146  
  * 
 147  
  */
 148  
 package com.buckosoft.fibs.net;
 149  
 
 150  
 import java.io.IOException;
 151  
 import java.io.InputStream;
 152  
 import java.io.OutputStream;
 153  
 import java.net.Socket;
 154  
 import java.net.SocketException;
 155  
 import java.net.UnknownHostException;
 156  
 import java.util.LinkedList;
 157  
 import java.util.Timer;
 158  
 import java.util.TimerTask;
 159  
 
 160  
 
 161  
 /** Manage a connection to the FIBS server.
 162  
  * This object runs in it's own Thread.
 163  
  * The only synchronized object is the outbound message queue.
 164  
  * @author Dick Balaska
 165  
  * @since 2008/03/29
 166  
  * @version $Revision$ <br> $Date$
 167  
  * @see <a href="http://cvs.buckosoft.com/Projects/BuckoFIBS/BuckoFIBS/src/main/java/com/buckosoft/fibs/net/ClientConnection.java">cvs ClientConnection.java</a>
 168  
  */
 169  0
 public class ClientConnection extends Thread {
 170  
         private        final static boolean DEBUG = false;
 171  
         
 172  
         final static String        eol = "\r\n";
 173  
         
 174  0
         private        Socket        sock = null;
 175  0
         private        InputStream is = null;
 176  0
         private        OutputStream os = null;
 177  
 
 178  0
         private        ClientAdapter                clientAdapter = null;
 179  
         private        CookieMonster                cookieMonster;
 180  
         private        FIBSAttributes                fibsAttributes;
 181  0
         private        boolean                                shuttingDown = false;
 182  0
         private        LinkedList<String>        outMessages = new LinkedList<String>();
 183  0
         private        Boolean                                listLock = new Boolean(false);
 184  0
         private        String                                leftover = null;
 185  0
         private        String                                pushbackString = null;
 186  0
         private        final int                        resetCountdown = 60*1;                // must send something every 25 minutes or one minute.
 187  0
         private        int                                        timerCountdown = resetCountdown;
 188  
         private        Timer                                timer;
 189  
         private        PingTimerTask                pingTimerTask;
 190  
 
 191  0
         public ClientConnection() {
 192  0
                 cookieMonster = new CookieMonster();
 193  0
                 cookieMonster.setClientConnection(this);
 194  0
                 timer = new Timer();
 195  0
                 pingTimerTask = new PingTimerTask();
 196  0
                 timer.scheduleAtFixedRate(pingTimerTask, 1000, 1000);
 197  0
         }
 198  
 
 199  0
         private class PingTimerTask extends TimerTask {
 200  
 
 201  
                 /* (non-Javadoc)
 202  
                  * @see java.util.TimerTask#run()
 203  
                  */
 204  
                 @Override
 205  
                 public void run() {
 206  0
                         if (--timerCountdown <= 0) {
 207  0
                                 timerCountdown = resetCountdown;
 208  0
                                 sendMessage("\r\n");
 209  
                         }        
 210  0
                 }        
 211  
         }
 212  
 
 213  
         /** Are we connected to the fibs server?
 214  
          * @return true if we are.
 215  
          */
 216  
         public boolean isConnected() {
 217  0
                 return(sock != null);
 218  
         }
 219  
 
 220  
         /** Set the ClientAdapter that we will be interfacing with
 221  
          * @param clientAdapter The object that we communicate with using the ClientAdapter interface
 222  
          */
 223  
         public void setClientAdapter(ClientAdapter clientAdapter) {
 224  0
                 this.clientAdapter = clientAdapter;
 225  0
         }
 226  
 
 227  
         /** Set the FIBSAttributes that we fetch our server attributes from
 228  
          * @param fibsAttributes The FIBSAtrributes that we need.
 229  
          */
 230  
         public void setFibsAttributes(FIBSAttributes fibsAttributes) {
 231  0
                 this.fibsAttributes = fibsAttributes;
 232  0
         }
 233  
 
 234  
         /** Reset the state of the cookie monster
 235  
          */
 236  
         public void resetFIBSCookieMonster() {
 237  0
                 this.cookieMonster.reset();
 238  0
         }
 239  
 
 240  
         public void pushBack(String s) {
 241  0
                 pushbackString = s;
 242  0
         }
 243  
 
 244  
         /** Queue a message up for transmission to the server
 245  
          * @param s The message to send (must include eol).
 246  
          */
 247  
         public        void sendMessage(String s) {
 248  0
                 if (s == null)
 249  0
                         throw new RuntimeException("Can't send a null message");
 250  
                 if (DEBUG)
 251  
                         System.out.println("User sent '" + s + "'");
 252  0
                 accessOutMessages(s);
 253  0
                 timerCountdown = resetCountdown;
 254  0
         }
 255  
 
 256  
         /** Kill the connection to the server.
 257  
          */
 258  
         public void shutDown() {
 259  0
                 shuttingDown = true;
 260  
                 try {
 261  0
                         if (timer != null) {
 262  0
                                 timer.cancel();
 263  0
                                 timer = null;
 264  
                         }
 265  0
                         if (pingTimerTask != null) {
 266  0
                                 pingTimerTask.cancel();
 267  0
                                 pingTimerTask = null;
 268  
                         }
 269  0
                 } catch (Exception e1) {
 270  0
                         e1.printStackTrace();
 271  0
                 }
 272  
                 try {
 273  0
                         if (os != null)
 274  0
                                 os.close();
 275  0
                         os = null;
 276  0
                         if (is != null)
 277  0
                                 is.close();
 278  0
                         is = null;
 279  0
                         if (sock != null) {
 280  0
                                 sock.shutdownInput();
 281  0
                                 sock.shutdownOutput();
 282  0
                                 sock.close();
 283  
                         }
 284  0
                         sock = null;
 285  0
                 } catch (SocketException sex) {        
 286  0
                 } catch (IOException e) {
 287  0
                         e.printStackTrace();
 288  0
                 }
 289  0
         }
 290  
 
 291  
         /** Either append a message to the end, or remove a message from the head
 292  
          * @param s a network message or null
 293  
          */
 294  
         private void accessOutMessages(String s) {
 295  0
                 synchronized (listLock) {
 296  0
                         if (s == null)
 297  0
                                 outMessages.remove();
 298  
                         else
 299  0
                                 outMessages.add(s);
 300  0
                 }
 301  0
         }
 302  
 
 303  
         /* (non-Javadoc)
 304  
          * @see java.lang.Thread#start()
 305  
          */
 306  
         @Override
 307  
         public synchronized void run() {
 308  0
                 sock = null;
 309  0
                 String server = this.fibsAttributes.getServerName();
 310  0
                 int port = this.fibsAttributes.getServerPort();
 311  0
                 this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.SYSTEM, "Connecting to Server:" + eol);
 312  0
                 this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.SYSTEM, server + ":" + port + eol);
 313  
                 try {
 314  0
                         sock = new Socket(server, port);
 315  0
                 } catch (UnknownHostException e) {
 316  0
                         this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.SYSTEM, "Can't connect: Unknown host");
 317  0
                         e.printStackTrace();
 318  0
                         return;
 319  0
                 } catch (IOException e) {
 320  0
                         this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.SYSTEM, "Can't connect: " + e.getLocalizedMessage());
 321  
                         //e.printStackTrace();
 322  0
                         return;
 323  0
                 } catch (Exception e) {
 324  0
                         this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.SYSTEM, "Can't connect: " + e.getLocalizedMessage());
 325  
                         //e.printStackTrace();
 326  0
                         return;
 327  0
                 }
 328  
 
 329  
                 try {
 330  0
                         is = sock.getInputStream();
 331  0
                         os = sock.getOutputStream();
 332  
                 
 333  0
                         while (!shuttingDown) {
 334  0
                                 if (pushbackString != null) {
 335  0
                                         String s = pushbackString;
 336  0
                                         pushbackString = null;
 337  0
                                         handleMessage(s);
 338  0
                                         continue;
 339  
                                 }
 340  0
                                 if (is.available() > 0) {
 341  
                                         try {
 342  0
                                                 readMessage();
 343  0
                                         } catch (Exception e) {
 344  0
                                                 System.err.println(e.getMessage());
 345  0
                                                 e.printStackTrace();
 346  0
                                         }
 347  0
                                         continue;
 348  
                                 }
 349  0
                                 if (!outMessages.isEmpty()) {
 350  0
                                         writeMessage();
 351  0
                                         continue;
 352  
                                 }
 353  0
                                 Thread.sleep(100);
 354  
                         }
 355  0
                 } catch (IOException e) {
 356  0
                         e.printStackTrace();
 357  0
                 } catch (InterruptedException e) {
 358  0
                         e.printStackTrace();
 359  0
                 }
 360  0
                 if (sock != null) {
 361  
                         try {
 362  0
                                 sock.shutdownInput();
 363  0
                                 sock.shutdownOutput();
 364  0
                                 sock.close();
 365  0
                                 sock = null;
 366  0
                         } catch (SocketException e) {
 367  0
                         } catch (IOException e) {
 368  0
                                 e.printStackTrace();
 369  0
                         }
 370  
                 }
 371  0
         }
 372  
 
 373  
         private        void readMessage() {
 374  
                 byte[] b;
 375  
                 try {
 376  0
                         int r = is.available();
 377  0
                         b = new byte[r];
 378  0
                         is.read(b);
 379  0
                 } catch (IOException e) {
 380  0
                         e.printStackTrace();
 381  0
                         shuttingDown = true;
 382  0
                         return;
 383  0
                 }
 384  0
                 String s = new String(b);
 385  
                 if (DEBUG)
 386  
                         System.out.println(s);
 387  
                 if (DEBUG)
 388  
                         this.clientAdapter.writeSystemMessageln(ClientAdapter.MessageRoute.DEBUG, s);
 389  0
                 boolean hasLeftOver = false;
 390  0
                 if (!s.endsWith("\r\n")) {
 391  
                         if (DEBUG)
 392  
                                 System.out.println("Doesn't end with cr/lf" + eol);
 393  0
                         hasLeftOver = true;
 394  
                 }
 395  0
                 String[] ss = s.split("\r\n");
 396  0
                 if (ss.length == 0)
 397  0
                         return;
 398  
                 if (DEBUG)
 399  
                         System.out.println("Got " + ss.length + " lines" + eol);
 400  0
                 if (leftover != null) {
 401  0
                         ss[0] = leftover + ss[0];
 402  0
                         leftover = null;
 403  
                 }
 404  0
                 for (int i=0; i<ss.length; i++) {
 405  0
                         if (i == ss.length-1 && hasLeftOver && !ss[i].startsWith("login:"))
 406  0
                                 leftover = ss[i];
 407  
                         else
 408  0
                                 handleMessage(ss[i]);
 409  0
                         while (pushbackString != null) {
 410  0
                                 String t = pushbackString;
 411  0
                                 pushbackString = null;
 412  0
                                 handleMessage(t);
 413  0
                         }
 414  
                 }
 415  0
         }
 416  
 
 417  
         /** Send the login message to FIBS.
 418  
          */
 419  
         public void sendLogin() {
 420  0
                 StringBuffer sb = new StringBuffer();
 421  0
                 sb.append("login ");
 422  0
                 sb.append(this.fibsAttributes.getAppSignature());
 423  0
                 sb.append(" 1008 ");
 424  0
                 sb.append(this.fibsAttributes.getUserName());
 425  0
                 sb.append(" ");
 426  0
                 sb.append(this.fibsAttributes.getUserPassword());
 427  0
                 sb.append(eol);
 428  0
                 this.sendMessage(sb.toString());
 429  0
         }
 430  
  
 431  
         private        void writeMessage() {
 432  0
                 String s = outMessages.getFirst();
 433  0
                 if (this.fibsAttributes.isDisplayXmit()) {
 434  0
                         if (s.startsWith("login"))
 435  0
                                 this.clientAdapter.writeSystemMessageln(ClientAdapter.MessageRoute.NETWORKOUT, "login...");
 436  
                         else
 437  0
                                 this.clientAdapter.writeSystemMessage(ClientAdapter.MessageRoute.NETWORKOUT, s);
 438  
                 }
 439  0
                 if (this.fibsAttributes.isStdoutNetworkMessages())
 440  0
                         System.out.println("writeNetMessage: '" + s + "'");
 441  
                 try {
 442  0
                         if (os == null) {
 443  0
                                 shuttingDown = true;
 444  0
                                 return;
 445  
                         }
 446  0
                         os.write(s.getBytes());
 447  0
                 } catch (Exception e) {
 448  0
                         this.clientAdapter.writeSystemMessageln(ClientAdapter.MessageRoute.ERROR, "Disconnected from FIBS");
 449  0
                         this.clientAdapter.writeSystemMessageln(ClientAdapter.MessageRoute.ERROR, e.getLocalizedMessage());
 450  0
                         this.clientAdapter.connectionAborted();
 451  0
                         e.printStackTrace();
 452  0
                         shuttingDown = true;
 453  0
                 }
 454  0
                 this.accessOutMessages(null);
 455  0
         }
 456  
         
 457  
         private        void handleMessage(String s) {
 458  0
                 if (s.length() > 1 && s.charAt(0) == 13)        // I have seen two '5's runon with just a cr between them,
 459  0
                         s = s.substring(1);                                                // not a crlf.  Check for that here.
 460  0
                 if (s.length() > 1 && s.charAt(0) == 10)        // I have seen two '5's runon with just a lf between them,
 461  0
                         s = s.substring(1);                                                // not a crlf.  Check for that here.
 462  0
                 int cookie = this.cookieMonster.fIBSCookie(s);
 463  0
                 if (this.fibsAttributes.isStdoutNetworkMessages())
 464  0
                         System.out.println("Handle: " + cookie + "'" + s + "'");
 465  0
                 if (this.pushbackString != null) {
 466  0
                         s = s.substring(0, s.length()-this.pushbackString.length());
 467  
                         if (DEBUG)
 468  
                                 System.out.println("Truncating to '" + s + "'");
 469  
                 }
 470  0
                 this.clientAdapter.dispatch(cookie, s);
 471  0
         }
 472  
 }