1 /* JChessBoard -- a chess game 2 * Copyright (C) 2000-2004 Claus Divossen <claus.divossen@gmx.de> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program; if not, write to the Free Software 16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 */ 18 19 /* $Id: BoardConnector.java,v 1.52 2004/12/26 23:12:14 cdivossen Exp $ */ 20 21 package jchessboard; 22 23 import java.io.BufferedReader; 24 import java.io.BufferedWriter; 25 import java.io.IOException; 26 import java.io.InputStreamReader; 27 import java.io.OutputStreamWriter; 28 import java.net.Socket; 29 30 import javax.swing.JCheckBox; 31 import javax.swing.JOptionPane; 32 33 /** 34 * BoardConnector implements two-way non-blocking communication for 35 * two JChessBoard programs. It monitors a Socket for incoming messages, 36 * 37 * Earlier versions also included much protocol information. The 38 * protocol (including the steps performed when each message is 39 * received) is now part of the Protocol class. 40 */ 41 class BoardConnector implements Runnable { 42 // This class provides the connection between two JChessBoard's. 43 // Connections to gnuchess and chess servers will be made with 44 // other classes or subclasses of a generic Connector class, that 45 // will be an super class of this class. 46 private Socket so; 47 private BufferedReader fromOther; 48 private BufferedWriter toOther; 49 private String otherHostname = ""; 50 private boolean isEnabled = false; 51 private boolean isServer; 52 private JChessBoard jcb; //Shortcut to call methods of this instance. 53 private Thread thisThread; 54 private int port; 55 getVersion()56 public static String getVersion() { 57 return "$Id: BoardConnector.java,v 1.52 2004/12/26 23:12:14 cdivossen Exp $"; 58 } 59 isEnabled()60 public boolean isEnabled() { 61 return isEnabled; 62 } 63 sendString(String s)64 public void sendString(String s) { 65 if (isEnabled) { 66 try { 67 toOther.write(s + "\n"); 68 toOther.flush(); 69 } catch (IOException e) { 70 // Something happenned to the socket. It's no longer connected. 71 isEnabled = false; 72 // Board has been disconnected. 73 jcb.connectionClosed(); 74 JOptionPane.showMessageDialog( 75 null, 76 "The connection was lost!\n" + e.toString(), 77 "IOException in outOfSync", 78 JOptionPane.ERROR_MESSAGE); 79 } 80 } 81 } 82 83 /** 84 * Blocks waiting for input; when a message is received, places the entire 85 * message into the message queue. 86 */ run()87 public void run() { 88 89 // Finish low-level protocol connection 90 if (isServer) { 91 // Only the basic functions to ensure the connection is valid 92 isEnabled = serverHandshake(); 93 } else { 94 isEnabled = clientHandshake(); 95 } 96 97 // Handle messages until disconnected 98 try { 99 boolean done = false; 100 while (isEnabled && !done) { 101 // Block for the next line 102 final String line = fromOther.readLine(); 103 if(line!=null) { 104 javax.swing.SwingUtilities.invokeLater(new Runnable() { 105 public void run() { 106 jcb.protocol.handleInput(line); 107 } 108 }); 109 } 110 } 111 } catch (IOException e) { 112 } 113 isEnabled = false; 114 try { 115 if (so != null) 116 so.close(); 117 } catch (IOException e) { 118 } 119 // Thread is done. We'll be garbage collected later. 120 } 121 shutdown()122 public void shutdown() { 123 isEnabled = false; 124 thisThread.interrupt(); 125 } 126 closeConnection()127 public void closeConnection() { 128 try { 129 if (so != null) { 130 toOther.write("quit\n"); 131 toOther.flush(); 132 toOther.close(); 133 fromOther.close(); 134 if (so != null) { 135 // so.shutdownInput(); 136 so.close(); 137 } 138 } 139 } catch (IOException e) { 140 } 141 isEnabled = false; 142 thisThread.interrupt(); 143 if (thisThread != Thread.currentThread()) { 144 try { 145 thisThread.join(); 146 } catch (InterruptedException e) { 147 } 148 } 149 } 150 getOtherHostName()151 public String getOtherHostName() { 152 if (otherHostname != null) { 153 return otherHostname; 154 } else { 155 return ""; 156 } 157 } 158 159 /** 160 * Encapsulates the steps taken by the server when initiating a 161 * connection. Too bad it's not this easy for people. 162 */ serverHandshake()163 public boolean serverHandshake() { 164 boolean handshakeStatus = false; 165 try { 166 // Same as sendString(), but doesn't need isEnabled 167 toOther.write(getVersion() + "\n"); 168 toOther.flush(); 169 String line = fromOther.readLine(); 170 if (line != null && line.equals(getVersion())) { 171 // The protocols match! 172 // Ask the user to accept or decline the connection. This gets 173 // invokeLater()ed; meanwhile, we just sit around queueing 174 // messages and waiting. 175 final BoardConnector bc = this; 176 javax.swing.SwingUtilities.invokeLater(new Runnable() { 177 public void run() { 178 // Make the items for the incoming connection dialog 179 Object[] options = { "Accept", "Reject" }; 180 Object[] message = new Object[2]; 181 message[0] = 182 "Incoming game request from " 183 + bc.getOtherHostName() 184 + "."; 185 JCheckBox newWindowCheckBox = 186 new JCheckBox("New window"); 187 // Look for unconnected windows 188 JChessBoard newJCB = null; 189 java.util.Iterator windowIter = 190 JChessBoard.windowList.iterator(); 191 while (windowIter.hasNext() && (newJCB == null)) { 192 JChessBoard candidate = 193 (JChessBoard) windowIter.next(); 194 if (candidate.getBoardConnector()==null) { 195 newJCB = candidate; 196 } 197 } 198 // If there is at least one window not currently connected, 199 if (newJCB != null) { 200 newWindowCheckBox.setEnabled(true); 201 newWindowCheckBox.setSelected(false); 202 newJCB.showMessage("Incoming connection..."); 203 } 204 // But if all windows are already connected, 205 else { 206 newWindowCheckBox.setEnabled(false); 207 newWindowCheckBox.setSelected(true); 208 } 209 message[1] = newWindowCheckBox; 210 int selection = 211 JOptionPane.showOptionDialog( 212 newJCB, 213 message, 214 "Game request", 215 JOptionPane.YES_NO_OPTION, 216 JOptionPane.QUESTION_MESSAGE, 217 null, 218 options, 219 options[0]); 220 if (selection == 0) { 221 // Connection was accepted 222 if (newWindowCheckBox.isSelected()) { 223 // Wants to open in a new window 224 if (newJCB != null) { 225 // Set that mangled JCB window back to normal 226 newJCB.showMessage("Not connected."); 227 newJCB.prepareMove(); 228 } 229 // Create the new JChessboard 230 newJCB = new JChessBoard(""); 231 newJCB.gameTable.clear(); // Remove the first game created by default. 232 } 233 // If newJCB was null, the newWindowCheckBox was forced selected, 234 // and we've got a new newJCB. If there was a newJCB, then we've 235 // still got it here. Either way, newJCB is valid. 236 // Finally, set up for a new game 237 newJCB.setBoardConnector(bc); 238 jcb = newJCB; 239 jcb.connectionEstablished(); 240 jcb.protocol.sendWelcome(); 241 // Must have BoardConnector before we can send the message! 242 } else { 243 bc.sendString(Protocol.REJECTED_MESSAGE); 244 bc.closeConnection(); 245 if (newJCB != null) { 246 newJCB.showMessage("Connection rejected."); 247 newJCB.connectionIndicator.setNoConnection(); 248 } 249 } 250 } 251 }); 252 handshakeStatus = true; 253 } else { 254 handshakeStatus = false; 255 } 256 } catch (IOException e) { 257 // Something happenned to the socket. It's no longer connected. 258 handshakeStatus = false; 259 } 260 return handshakeStatus; 261 } 262 263 /** 264 * Encapsulates the steps taken by the client when initiating a 265 * connection. Too bad it's not this easy for people. 266 */ clientHandshake()267 public boolean clientHandshake() { 268 boolean handshakeStatus = false; 269 270 String line = null; 271 // Would show the user a message, but we don't even know we're 272 // connecting, yet... 273 274 try { 275 // Step 1: wait for version and acknowledge 276 // Read the protocol line 277 line = fromOther.readLine(); 278 if (line != null) { 279 // If it's the right protocol 280 if (line.equals(getVersion())) { 281 // Send our protocol (assures other side all is correct) 282 toOther.write(getVersion() + "\n"); 283 toOther.flush(); 284 // We're ready to go! Our JChessBoard has been polling our status; 285 // tell it to get ready for welcome/reject 286 handshakeStatus = true; 287 } else { 288 // Protocol didn't match 289 // --JAM: Would be nice to give the user an indication, but 290 // we're still not in the Swing thread 291 closeConnection(); 292 handshakeStatus = false; 293 } 294 } else { 295 // --JAM: Would be nice to give the user an indication, but 296 // we're still not in the Swing thread 297 handshakeStatus = false; 298 } 299 } catch (IOException ioe) { 300 handshakeStatus = false; 301 } 302 303 return handshakeStatus; 304 } 305 BoardConnector(JChessBoard b, Socket s)306 public BoardConnector(JChessBoard b, Socket s) { 307 // Used for incoming connections. 308 jcb = b; 309 so = s; 310 try { 311 fromOther = 312 new BufferedReader(new InputStreamReader(so.getInputStream())); 313 toOther = 314 new BufferedWriter( 315 new OutputStreamWriter(so.getOutputStream())); 316 otherHostname = so.getInetAddress().getHostName(); 317 isServer = true; 318 isEnabled = true; 319 // Must start thread to avoid garbage collection 320 (thisThread = new Thread(this)).start(); 321 } catch (Exception e) { 322 // Probably useless, but just in case... 323 isEnabled = false; 324 } 325 } 326 BoardConnector(JChessBoard b, String hostname, int networkPort)327 public BoardConnector(JChessBoard b, String hostname, int networkPort) { 328 // Used for outgoing connections. 329 port = networkPort; 330 otherHostname = hostname; 331 332 // Open the socket 333 try { 334 // Start the connection indicator 335 b.connectionIndicator.setWaiting(); 336 // These things are handled while GUI thread draws. 337 so = new Socket(otherHostname, port); 338 fromOther = 339 new BufferedReader(new InputStreamReader(so.getInputStream())); 340 toOther = 341 new BufferedWriter( 342 new OutputStreamWriter(so.getOutputStream())); 343 344 jcb = b; 345 isServer = false; 346 isEnabled = true; 347 (thisThread = new Thread(this)).start(); 348 } catch (Exception e) { 349 b.showMessage("Could not connect to \""+hostname+"\": "+e.getMessage()); 350 b.connectionIndicator.setNoConnection(); 351 isEnabled = false; 352 } 353 } 354 } 355