1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package triptracker.client.net;
21
22 import static triptracker.core.ConnectionState.*;
23 import static triptracker.core.Protocol.*;
24
25 import java.io.BufferedReader;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStream;
30 import java.io.PrintStream;
31 import java.net.InetSocketAddress;
32 import java.net.Socket;
33 import java.util.Set;
34 import java.util.concurrent.CopyOnWriteArraySet;
35
36 import triptracker.core.ConnectionState;
37 import triptracker.core.Protocol;
38 import triptracker.core.Route;
39
40 /***
41 * Connects to the server through a socket.
42 */
43 public abstract class SocketConnection<E extends SocketListener> {
44 private Socket socket = null;
45 protected InputStream inStream = null;
46 protected OutputStream outStream = null;
47 protected BufferedReader in = null;
48 protected PrintStream out = null;
49
50 private String username;
51 private String host = HOST;
52 private int port = PORTNR;
53
54 protected boolean loggedIn = false;
55
56 /*** Listener set for Model View Controller (MVC) separation. */
57 protected Set<E> listeners = new CopyOnWriteArraySet<E>();
58
59 /***
60 * Connects to the hostname and port number set by {@link #setHost(String)}
61 * and {@link #setPort(int)}.
62 *
63 * @throws IOException on connection failure
64 * @see #connect(String, int)
65 */
66 public void connect() throws IOException {
67 connect(host, port);
68 }
69
70 /***
71 * Connect to server on specified port.
72 *
73 * @param host server hostname
74 * @param port destination port
75 * @throws IOException on connection failure
76 * @see #connect(String)
77 */
78 public void connect(String host, int port) throws IOException {
79
80 if (!isClosed()) {
81 disconnect();
82 }
83
84
85 socket = new Socket();
86
87 try {
88 socket.connect(new InetSocketAddress(host, port));
89 } catch (IOException e) {
90 socket = null;
91 throw e;
92 }
93
94
95 inStream = socket.getInputStream();
96 outStream = socket.getOutputStream();
97 in = new BufferedReader(new InputStreamReader(inStream));
98 out = new PrintStream(outStream, true);
99
100 connectionUpdate(CONNECTED);
101
102
103 Thread t = new DataReaderThread();
104
105 t.setDaemon(true);
106 t.start();
107
108 }
109
110 /***
111 * Logon a user.
112 *
113 * @param type type of user
114 * @param user username
115 * @param pass password
116 * @throws IOException on connection failure
117 */
118 protected void logon(int type, String user, String pass)
119 throws IOException {
120
121 if (!isConnected()) {
122 connect();
123 }
124
125 this.username = user;
126
127
128 sendMessage(type, user, pass);
129 }
130
131 /***
132 * Sets route to track
133 */
134 public void setRoute(Route route){
135 sendMessage(SET_ROUTE, route.getRouteId());
136 }
137
138 /***
139 * Request a list of routes for a given user. When the requested list
140 * is received from the server.
141 */
142 public void getRoutes(String username){
143 sendMessage(VIEW_ROUTES, username);
144
145 }
146
147 /***
148 * Sends a message to the server
149 *
150 * @param message
151 */
152 protected void sendMessage(Object... message) {
153 if (!isConnected()){
154
155
156
157
158 System.out.println("socket not connected");
159
160
161
162 return;
163 }
164
165 try {
166 send(outStream, message);
167 } catch (IOException e) {
168 socketErrorEvent(e);
169 }
170
171
172
173 }
174
175 /***
176 * Checks if the client is connected to the server.
177 *
178 * @return true if connected, false if disconnected
179 */
180 public boolean isConnected() {
181 if (socket == null) {
182 return false;
183 } else {
184 return socket.isConnected();
185 }
186 }
187
188 /***
189 * Checks if the client connection is closed.
190 *
191 * @return true if connected, false if disconnected
192 */
193 public boolean isClosed() {
194 if (socket == null) {
195 return true;
196 } else {
197 return socket.isClosed();
198 }
199 }
200
201 /***
202 * Checks if the client is logged in.
203 *
204 * @return true if logged in, false if not logged in
205 */
206 public boolean isLoggedIn(){
207 return loggedIn;
208 }
209
210 /***
211 * Checks for messages recieved from the server, and pass them to the
212 * message handler for further processing. Subclasses can override this
213 * method to provide their own message loop if message processing needs to
214 * be more fine grained than simple line based delimiting.
215 */
216 protected void messageLoop() throws IOException {
217 String message;
218
219 while (true) {
220 message = in.readLine();
221 if (message == null) {
222 break;
223
224 } else if (!messageHandler(message)) {
225 break;
226 }
227 }
228 }
229
230 /***
231 * Message handler for data received on socket. Subclasses must implement
232 * this method to provide handing of recieved messages.
233 *
234 * @param message received message from server
235 * @return true if more messages can be processed, false to end the message
236 * loop, stop message handling and close the socket connection
237 */
238 protected abstract boolean messageHandler(String message);
239
240 /***
241 * Disconnect from the socket.
242 *
243 * @throws IOException on socket error
244 */
245 public void disconnect() throws IOException {
246 if (loggedIn) {
247 sendMessage(QUIT);
248 loggedIn = false;
249 }
250
251 if (isClosed()) {
252 return;
253 }
254
255
256
257 socket.close();
258 socket = null;
259
260
261 connectionUpdate(DISCONNECTED);
262 }
263
264 /***
265 * Register a listener for server events.
266 *
267 * @param listener event receiver to register
268 */
269 public void addListener(E listener) {
270 listeners.add(listener);
271 }
272
273 /***
274 * Remove listener from listener queue.
275 *
276 * @param listener event receiver to remove
277 */
278 public void removeListener(E listener) {
279 listeners.remove(listener);
280 }
281
282 /***
283 * Reports a broken listener, removes it from the listener queue and logs
284 * the error.
285 *
286 * @param listener broken listener
287 * @param exception exception thrown by listener
288 */
289 protected void brokenListener(SocketListener listener,
290 RuntimeException exception) {
291 System.err.println("Unexpected exception in listener");
292 exception.printStackTrace();
293
294
295 listeners.remove(listener);
296 }
297
298 /***
299 * Publish a socket error event to all listeners. This generally means that
300 * the client socket connection has been closed.
301 *
302 * @param exception socket exception thrown
303 */
304 protected void socketErrorEvent(IOException exception) {
305 for (SocketListener listener : listeners) {
306 try {
307 listener.socketError(exception);
308 } catch (RuntimeException e) {
309 brokenListener(listener, e);
310 }
311 }
312 }
313
314 /***
315 * Publish an invalid message event to all listeners. This signals an
316 * unsupported or malformed message recieved from the server.
317 *
318 * @param message full message
319 */
320 protected void invalidMessage(String message) {
321
322
323 for (SocketListener listener : listeners) {
324 try {
325 listener.invalidMessage(message);
326 } catch (RuntimeException e) {
327 brokenListener(listener, e);
328 }
329 }
330 }
331
332 /***
333 * Publish a connection state change event to all listeners. This notifies
334 * the clients that the connection state of the underlying socket has
335 * changed.
336 *
337 * @param state new connection state
338 */
339 protected void connectionUpdate(ConnectionState state) {
340
341
342
343 loggedIn = (state == AUTH_SUCCESS);
344
345 for (SocketListener listener : listeners) {
346 try {
347 listener.connectionUpdate(state);
348 } catch (RuntimeException e) {
349 brokenListener(listener, e);
350 }
351 }
352 }
353
354 /***
355 * Inner class to receive incomming messages.
356 */
357 private class DataReaderThread extends Thread {
358 /***
359 * Start the message loop that recieves messages from the socket.
360 */
361 @Override
362 public void run() {
363 try {
364
365 messageLoop();
366 } catch (IOException e) {
367 socketErrorEvent(e);
368
369 } catch (RuntimeException e) {
370 e.printStackTrace();
371 } finally {
372
373 try {
374 in.close();
375 out.close();
376 socket.close();
377 } catch (IOException e) {
378
379 } finally {
380 connectionUpdate(DISCONNECTED);
381 }
382 }
383 }
384 }
385
386 /***
387 * Returns the current hostname. By default the hostname is set to
388 * {@link Protocol#HOST}.
389 *
390 * @return hostname
391 */
392 public String getHost() {
393 return host;
394 }
395
396 /***
397 * Set hostname to connect to. By default the hostname is set to
398 * {@link Protocol#HOST}.
399 *
400 * @param host host to set
401 */
402 public void setHost(String host) {
403 this.host = host;
404 }
405
406 /***
407 * Returns the current port. By default the port number is set to
408 * {@link Protocol#PORTNR}.
409 *
410 * @return port number
411 */
412 public int getPort() {
413 return port;
414 }
415
416 /***
417 * Set port to connect to. By default the port number is set to
418 * {@link Protocol#PORTNR}.
419 *
420 * @param port The port to set.
421 */
422 public void setPort(int port) {
423 this.port = port;
424 }
425
426 /***
427 * Returns the name of the active user. This is set during login when
428 * connecting to the server.
429 *
430 * @return active user name
431 */
432 public String getUsername() {
433 return username;
434 }
435 }