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