View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client;
2   
3   import java.io.Serializable;
4   import java.net.InetSocketAddress;
5   import java.util.Collections;
6   import java.util.HashSet;
7   import java.util.Map;
8   import java.util.Set;
9   import java.util.Timer;
10  import java.util.TimerTask;
11  import java.util.logging.Logger;
12  
13  import org.apache.mina.core.future.ConnectFuture;
14  import org.apache.mina.core.future.IoFutureListener;
15  import org.apache.mina.core.service.IoHandler;
16  import org.apache.mina.core.session.IdleStatus;
17  import org.apache.mina.core.session.IoSession;
18  import org.apache.mina.filter.codec.ProtocolCodecFilter;
19  import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
20  import org.apache.mina.transport.socket.nio.NioSocketConnector;
21  
22  import cz.cuni.amis.pogamut.base.communication.translator.event.IWorldChangeEvent;
23  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
24  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEvent;
25  import cz.cuni.amis.pogamut.unreal.communication.messages.UnrealId;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Player;
27  import cz.cuni.amis.pogamut.ut2004.teamcomm.bot.UT2004TCClient;
28  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestCreateChannel;
29  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestDestroyChannel;
30  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestGetStatus;
31  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestJoinChannel;
32  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestLeaveChannel;
33  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.client.messages.TCRequestRegister;
34  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCInfoData;
35  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCInfoMessage;
36  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCMessage;
37  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCRecipient;
38  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.messages.TCRequestMessage;
39  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.model.TCChannel;
40  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.model.TCTeam;
41  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.TCMinaServer;
42  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoBotJoined;
43  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoBotLeft;
44  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoRequestFailed;
45  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoRequestFailedException;
46  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoStatus;
47  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelBotJoined;
48  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelBotLeft;
49  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelCreated;
50  import cz.cuni.amis.pogamut.ut2004.teamcomm.mina.server.messages.TCInfoTeamChannelDestroyed;
51  import cz.cuni.amis.utils.ExceptionToString;
52  import cz.cuni.amis.utils.NullCheck;
53  import cz.cuni.amis.utils.flag.Flag;
54  import cz.cuni.amis.utils.flag.ImmutableFlag;
55  import cz.cuni.amis.utils.future.FutureWithListeners;
56  import cz.cuni.amis.utils.maps.HashMapMap;
57  import cz.cuni.amis.utils.token.IToken;
58  
59  public class TCMinaClient implements IoHandler {
60  	
61  	private static final int RETRY_REGISTER_PERIOD_SECS = 3;
62  
63  	private Object mutex = new Object();
64  	
65  	private UT2004TCClient owner;
66  	
67  	private IWorldView teamWorldView;
68  	
69  	private UnrealId botId;
70  	
71  	private int botTeam;
72  	
73  	private InetSocketAddress address;
74  	
75  	private Logger log;
76  	
77  	private Flag<Boolean> connected = new Flag<Boolean>(false);
78  	
79  	private Flag<Boolean> connecting = new Flag<Boolean>(false);
80  	
81  	private IoFutureListener<ConnectFuture> connectionListener = new IoFutureListener<ConnectFuture>() {
82  
83  		@Override
84  		public void operationComplete(ConnectFuture event) {
85  			connected(event);
86  		}
87  	};
88  	
89  	private Timer timer;
90  	
91  	private NioSocketConnector ioConnector;
92  	
93  	private ConnectFuture connectFuture;
94  	
95  	private IoSession session;
96  	
97  	private Set<UnrealId> allBots = new HashSet<UnrealId>();
98  	
99  	private TCTeam team;
100 	
101 	private int registerTries = 0;
102 
103 	public TCMinaClient(UT2004TCClient owner, InetSocketAddress connectToAddress, IWorldView teamWorldView, Logger log) {
104 		this.owner = owner;
105 		this.teamWorldView = teamWorldView;
106 		this.botId = owner.getBotId();
107 		this.botTeam = owner.getBotTeam();
108 		this.address = connectToAddress;
109 		NullCheck.check(this.address, "connectToAddress");
110 		this.log = log;
111 		NullCheck.check(this.log, "log");
112 	}
113 	
114 	// =============================
115 	// PUBLIC INTERFACE - CONNECTION
116 	// =============================
117 	
118 	public String getHost() {
119 		return address.getHostName();
120 	}
121 	
122 	public int getPort() {
123 		return address.getPort();
124 	}
125 	
126 	/**
127 	 * WorldView used by this {@link TCMinaClient}. We're auto-propagating incoming messages to this.
128 	 * @return
129 	 */
130 	public IWorldView getWorldView() {
131 		return teamWorldView;
132 	}
133 		
134 	public ImmutableFlag<Boolean> getConnected() {
135 		return connected.getImmutable();
136 	}
137 	
138 	public ImmutableFlag<Boolean> getConnecting() {
139 		return connecting.getImmutable();
140 	}
141 	
142 	public void connect() {		
143 		synchronized(mutex) {
144 			if (connected.getFlag()) return;
145 			if (connecting.getFlag()) return;
146 			log.warning("Connecting to TC at " + getHost() + ":" + getPort() + " ...");
147 			connecting.setFlag(true);
148 		}
149 		
150 		try {
151 			ioConnector = new NioSocketConnector();
152 			
153 			ioConnector.setHandler(this);
154 			
155 			ioConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
156 			
157 			connectFuture = ioConnector.connect(address);
158 			
159 			connectFuture.addListener(connectionListener);
160 		} catch (Exception e1) {
161 			try {
162 				connecting.setFlag(false);
163 			} catch (Exception e2) {				
164 			}
165 		}
166 		
167 	}
168 	
169 	// =========================
170 	// PUBLIC INTERFACE - STATUS
171 	// =========================
172 	
173 	public boolean isConnected(UnrealId botId) {
174 		if (botId == null) return false;
175 		if (!getConnected().getFlag()) return false;
176 		return allBots.contains(botId);
177 	}
178 	
179 	public boolean isConnected(Player bot) {
180 		if (bot == null) return false;
181 		if (!getConnected().getFlag()) return false;
182 		return allBots.contains(bot.getId());
183 	}
184 	
185 	public boolean isConnectedToMyTeam(UnrealId botId) {
186 		if (botId == null) return false;
187 		if (!getConnected().getFlag()) return false;
188 		return team.getConnectedBots().contains(botId);
189 	}
190 	
191 	public boolean isConnectedToMyTeam(Player bot) {
192 		if (bot == null) return false;
193 		if (!getConnected().getFlag()) return false;
194 		return team.getConnectedBots().contains(bot.getId());
195 	}
196 	
197 	public boolean isConnectedToChannel(UnrealId botId, int channelId) {
198 		if (botId == null) return false;
199 		if (!getConnected().getFlag()) return false;		
200 		TCChannel channel = team.getChannels().get(channelId);
201 		if (channel == null) return false;
202 		return channel.getConnectedBots().contains(botId);
203 	}
204 	
205 	public boolean isConnectedToChannel(Player bot, int channelId) {
206 		if (bot == null) return false;
207 		if (!getConnected().getFlag()) return false;		
208 		TCChannel channel = team.getChannels().get(channelId);
209 		if (channel == null) return false;
210 		return channel.getConnectedBots().contains(bot.getId());
211 	}
212 	
213 	public boolean isChannelExist(int channelId) {
214 		if (!getConnected().getFlag()) return false;		
215 		TCChannel channel = team.getChannels().get(channelId);
216 		if (channel == null) return false;
217 		return true;
218 	}
219 	
220 	/**
221 	 * Returns set of all bots connected to the same TC server.
222 	 * @return READ-ONLY!
223 	 */
224 	public Set<UnrealId> getConnectedAllBots() {
225 		if (!getConnected().getFlag()) return new HashSet<UnrealId>();
226 		return Collections.unmodifiableSet(allBots);
227 	}
228 	
229 	/**
230 	 * Returns set of bots of the same team connected to the same TC server.
231 	 * @return READ-ONLY!
232 	 */
233 	public Set<UnrealId> getConnectedTeamBots() {
234 		if (!getConnected().getFlag()) return new HashSet<UnrealId>();
235 		return Collections.unmodifiableSet(team.getConnectedBots());
236 	}
237 	
238 	/**
239 	 * Returns set of bots connected to the channel of 'channelId' of the same TC server.
240 	 * @return READ-ONLY!
241 	 */
242 	public Set<UnrealId> getConnectedChannelBots(int channelId) {
243 		if (!getConnected().getFlag()) return new HashSet<UnrealId>();
244 		TCChannel channel = team.getChannels().get(channelId);
245 		if (channel == null) return new HashSet<UnrealId>();
246 		return Collections.unmodifiableSet(channel.getConnectedBots());
247 	}
248 	
249 	/**
250 	 * Returns details about bots/channels connected to the team.
251 	 * 
252 	 * Returns NULL if not connected!
253 	 * 
254 	 * @return READ-ONLY!
255 	 */
256 	public TCTeam getTeam() {		
257 		synchronized(mutex) {
258 			if (!getConnected().getFlag()) return null;
259 			if (team == null) return null;
260 			return team.clone();			
261 		}
262 	}
263 	
264 	/**
265 	 * Returns details about bots/channels connected to the team.
266 	 * 
267 	 * Returns NULL if not connected or non-existing channel!
268 	 * 
269 	 * @return READ-ONLY!
270 	 */
271 	public TCChannel getChannel(int channelId) {		
272 		synchronized(mutex) {
273 			if (!getConnected().getFlag()) return null;
274 			if (team == null) return null;
275 			TCChannel channel = team.getChannels().get(channelId);
276 			if (channel == null) return null;
277 			return channel.clone();
278 		}
279 	}
280 	
281 	// ===========================
282 	// PUBLIC INTERFACE - REQUESTS
283 	// ===========================
284 	
285 	public static class RequestFuture<T> extends FutureWithListeners<T> {
286 		
287 		private TCRequestMessage message;
288 		
289 		public RequestFuture(TCRequestMessage message) {
290 			this.message = message;
291 		}
292 	}
293 	
294 	/**
295 	 * MessageType - request Id - request future.
296 	 */
297 	private HashMapMap<IToken, Long, RequestFuture<?>> requestFutures = new HashMapMap<IToken, Long, RequestFuture<?>>();
298 	
299 	
300 	/**
301 	 * Request to create a new channel, the channel will gets channelId assigned by {@link TCMinaServer}.
302 	 * 
303 	 * When created, you will be automatically added to it as its creator.
304 	 * 
305 	 * @return 
306 	 */
307 	public RequestFuture<TCInfoTeamChannelCreated> requestCreateChannel() {
308 		TCRequestCreateChannel data = new TCRequestCreateChannel(owner.getSimTime());
309 		TCRequestMessage message = new TCRequestMessage(botId, data);
310 		RequestFuture<TCInfoTeamChannelCreated> future = new RequestFuture<TCInfoTeamChannelCreated>(message);
311 		synchronized(mutex) {	
312 			if (!getConnected().getFlag()) return null;
313 			requestFutures.put(data.getMessageType(), data.getRequestId(), future);
314 			session.write(message);			
315 			
316 		}
317 		return future;
318 	}
319 	
320 	/**
321 	 * Request to destroy an existing channel. Note that you must be channel's creator in able to do this!
322 	 * @param channelId
323 	 * @return 
324 	 */
325 	public RequestFuture<TCInfoTeamChannelDestroyed> requestDestroyChannel(int channelId) {
326 		TCRequestDestroyChannel data = new TCRequestDestroyChannel(owner.getSimTime());
327 		data.setChannelId(channelId);
328 		TCRequestMessage message = new TCRequestMessage(botId, data);
329 		RequestFuture<TCInfoTeamChannelDestroyed> future = new RequestFuture<TCInfoTeamChannelDestroyed>(message);
330 		synchronized(mutex) {	
331 			if (!getConnected().getFlag()) return null;
332 			TCChannel channel = team.getChannels().get(channelId);
333 			if (channel == null) return null;
334 			if (!channel.getCreator().equals(botId)) return null;
335 			requestFutures.put(data.getMessageType(), data.getRequestId(), future);
336 			session.write(message);						
337 		}
338 		return future;
339 	}
340 	
341 	/**
342 	 * Request {@link TCInfoStatus} update from {@link TCMinaServer}.
343 	 * @return request future or NULL if not connected
344 	 */
345 	public RequestFuture<TCInfoStatus> requestGetStatus() {
346 		TCRequestGetStatus data = new TCRequestGetStatus(owner.getSimTime());
347 		TCRequestMessage message = new TCRequestMessage(botId, data);
348 		RequestFuture<TCInfoStatus> future = new RequestFuture<TCInfoStatus>(message);
349 		synchronized(mutex) {	
350 			if (!getConnected().getFlag()) return null;
351 			requestFutures.put(data.getMessageType(), data.getRequestId(), future);
352 			session.write(message);			
353 			
354 		}
355 		return future;
356 	}
357 	
358 	/**
359 	 * Request to join an existing channel.
360 	 * @param channelId
361 	 * @return 
362 	 */
363 	public RequestFuture<TCInfoTeamChannelBotJoined> requestJoinChannel(int channelId) {
364 		TCRequestJoinChannel data = new TCRequestJoinChannel(owner.getSimTime());
365 		data.setChannelId(channelId);
366 		TCRequestMessage message = new TCRequestMessage(botId, data);
367 		RequestFuture<TCInfoTeamChannelBotJoined> future = new RequestFuture<TCInfoTeamChannelBotJoined>(message);
368 		synchronized(mutex) {	
369 			if (!getConnected().getFlag()) return null;
370 			TCChannel channel = team.getChannels().get(channelId);
371 			if (channel == null) return null;
372 			requestFutures.put(data.getMessageType(), data.getRequestId(), future);
373 			session.write(message);						
374 		}
375 		return future;
376 	}
377 	
378 	/**
379 	 * Request to leave an existing channel. Leaving a channel you have created will NOT destroy it.
380 	 * @param channelId
381 	 * @return
382 	 */
383 	public RequestFuture<TCInfoTeamChannelBotLeft> requestLeaveChannel(int channelId) {
384 		TCRequestLeaveChannel data = new TCRequestLeaveChannel(owner.getSimTime());
385 		data.setChannelId(channelId);
386 		TCRequestMessage message = new TCRequestMessage(botId, data);
387 		RequestFuture<TCInfoTeamChannelBotLeft> future = new RequestFuture<TCInfoTeamChannelBotLeft>(message);
388 		synchronized(mutex) {	
389 			if (!getConnected().getFlag()) return null;
390 			TCChannel channel = team.getChannels().get(channelId);
391 			if (channel == null) return null;
392 			requestFutures.put(data.getMessageType(), data.getRequestId(), future);
393 			session.write(message);						
394 		}
395 		return future;
396 	}
397 	
398 	// ================================
399 	// PUBLIC INTERFACE - COMMUNICATION
400 	// ================================
401 	
402 	/**
403 	 * Sends message to ALL connected bots (not just to your team) EXCLUDING you (you will NOT receive the message).
404 	 * @param messageType CANNOT BE NULL
405 	 * @param data may be null
406 	 * @return
407 	 */
408 	public boolean sendToAllOthers(IToken messageType, Serializable data) {
409 		return sendToAll(messageType, data, false);
410 	}
411 	
412 	/**
413 	 * Sends message to ALL connected bots (not just to your team) INCLUDING you (you WILL receive the message AS WELL).
414 	 * @param messageType CANNOT BE NULL
415 	 * @param data may be null
416 	 * @return
417 	 */
418 	public boolean sendToAll(IToken messageType, Serializable data) {
419 		return sendToAll(messageType, data, true);
420 	}
421 	
422 	/**
423 	 * Sends message to ALL connected bots (not just to your team)
424 	 * @param messageType CANNOT BE NULL
425 	 * @param data may be null
426 	 * @param includeMe whether the message should be send to YOU (your client) as well
427 	 * @return
428 	 */
429 	public boolean sendToAll(IToken messageType, Serializable data, boolean includeMe) {
430 		if (messageType == null) return false;
431 		
432 		TCMessage message = new TCMessage(botId, TCRecipient.GLOBAL, !includeMe, messageType, data, owner.getSimTime());
433 		
434 		synchronized(mutex) {
435 			if (!connected.getFlag() || session == null) return false;
436 			try {
437 				session.write(message);
438 			} catch (Exception e) {
439 				log.warning(ExceptionToString.process("Failed to sendToAll: " + data, e));
440 				return false;
441 			}			
442 			return true;
443 		}
444 	}
445 	
446 	/**
447 	 * Sends message to connected bots of YOUR TEAM (not to all bots connected to the TC server) EXCLUDING you (you will NOT receive the message).
448 	 * @param messageType CANNOT BE NULL
449 	 * @param data may be null.
450 	 * @return
451 	 */
452 	public boolean sendToTeamOthers(IToken messageType, Serializable data, boolean includeMe) {
453 		return sendToTeam(messageType, data, false);
454 	}
455 	
456 	/**
457 	 * Sends message to connected bots of YOUR TEAM (not to all bots connected to the TC server) INCLUDING you (you WILL receive the message AS WELL).
458 	 * @param messageType CANNOT BE NULL
459 	 * @param data may be null.
460 	 * @return
461 	 */
462 	public boolean sendToTeam(IToken messageType, Serializable data) {
463 		return sendToTeam(messageType, data, false);
464 	}
465 	
466 	/**
467 	 * Sends message to connected bots of YOUR TEAM (not to all bots connected to the TC server).
468 	 * @param messageType CANNOT BE NULL
469 	 * @param data may be null.
470 	 * @param includeMe whether the message should be send to YOU (your client) as well
471 	 * @return
472 	 */
473 	public boolean sendToTeam(IToken messageType, Serializable data, boolean includeMe) {
474 		if (messageType == null) return false;
475 		
476 		TCMessage message = new TCMessage(botId, TCRecipient.TEAM, includeMe, messageType, data, owner.getSimTime());
477 		
478 		synchronized(mutex) {
479 			if (!connected.getFlag() || session == null) return false;
480 			try {
481 				session.write(message);
482 			} catch (Exception e) {
483 				log.warning(ExceptionToString.process("Failed to sendToTeam: " + data, e));
484 				return false;
485 			}			
486 			return true;			
487 		}
488 	}
489 	
490 	/**
491 	 * Sends message to a CONCRETE CHANNEL you are connected to EXCLUDING you (you will NOT receive the message). 
492 	 * Note that you cannot send messages to channels you are not connected to.
493 	 * 
494 	 * @param channelId
495 	 * @param messageType
496 	 * @param data
497 	 * @return
498 	 */
499 	public boolean sendToChannelOthers(int channelId, IToken messageType, Serializable data) {
500 		return sendToChannel(channelId, messageType, data, false);
501 	}
502 	
503 	/**
504 	 * Sends message to a CONCRETE CHANNEL you are connected to INCLUDING you (you WILL receive the message AS WELL). 
505 	 * Note that you cannot send messages to channels you are not connected to.
506 	 * 
507 	 * @param channelId
508 	 * @param messageType
509 	 * @param data
510 	 * @return
511 	 */
512 	public boolean sendToChannel(int channelId, IToken messageType, Serializable data) {
513 		return sendToChannel(channelId, messageType, data, true);
514 	}
515 	
516 	/**
517 	 * Sends message to a CONCRETE CHANNEL you are connected to. Note that you cannot send messages to channels
518 	 * you are not connected to.
519 	 * 
520 	 * @param channelId
521 	 * @param messageType
522 	 * @param data
523 	 * @param includeMe whether the message should be send to YOU (your client) as well
524 	 * @return
525 	 */
526 	public boolean sendToChannel(int channelId, IToken messageType, Serializable data, boolean includeMe) {
527 		if (messageType == null) return false;
528 		
529 		TCMessage message = new TCMessage(botId, TCRecipient.CHANNEL, includeMe, messageType, data, owner.getSimTime());
530 		message.setChannelId(channelId);
531 		
532 		synchronized(mutex) {
533 			if (!connected.getFlag() || session == null) return false;
534 			try {
535 				session.write(message);
536 			} catch (Exception e) {
537 				log.warning(ExceptionToString.process("Failed to sendToChannel(" + channelId + "): " + data, e));
538 				return false;
539 			}			
540 			return true;
541 		}
542 	}
543 	
544 	/**
545 	 * Sends private message to a concrete bot. Note that you can use this to send messages to yourself as well.
546 	 * @param targetBotId
547 	 * @param messageType
548 	 * @param data
549 	 * @return
550 	 */
551 	public boolean sendPrivate(UnrealId targetBotId, IToken messageType, Serializable data) {
552 		if (messageType == null) return false;
553 		
554 		TCMessage message = new TCMessage(botId, TCRecipient.PRIVATE, false, messageType, data, owner.getSimTime());
555 		message.setTargetId(targetBotId);
556 		
557 		synchronized(mutex) {
558 			if (!connected.getFlag() || session == null) return false;
559 			try {
560 				session.write(message);
561 			} catch (Exception e) {
562 				log.warning(ExceptionToString.process("Failed to sendPrivate(" + targetBotId.getStringId() + "): " + data, e));
563 				return false;
564 			}			
565 			return true;
566 		}
567 	}
568 	
569 	// ======
570 	// EVENTS
571 	// ======
572 	
573 	protected void connected(ConnectFuture event) {
574 		log.info("Connected to TC at " + getHost() + ":" + getPort());		
575 		
576 		this.session = event.getSession();
577 		
578 		connectFuture.removeListener(connectionListener);
579 		connectFuture = null;
580 		
581 		log.info("Sending REGISTER request, expecting TCInfoStatus reply...");
582 		
583 		registerTries = 1;
584 		sendRegisterRequest();
585 	}
586 
587 	
588 	private void sendRegisterRequest() {
589 		TCRequestRegister request = new TCRequestRegister(owner.getSimTime());
590 		TCRequestMessage message = new TCRequestMessage(botId, request);
591 		synchronized(mutex) {
592 			session.write(message);
593 		}
594 	}
595 
596 	// ================================
597 	// IMPLEMENTATING {@link IOHandler}
598 	// ================================
599 	
600 	@Override
601 	public void exceptionCaught(IoSession session, Throwable exception) throws Exception {
602 		log.warning(ExceptionToString.process("TCMinaClient Exception", exception));
603 		stop();
604 	}
605 
606 	@Override
607 	public void messageReceived(IoSession session, Object message) throws Exception {		
608 		if (message == null) {
609 			log.warning("Invalid message: " + String.valueOf(message));
610 			return;
611 		}
612 		if (!(message instanceof TCMessage)) {
613 			log.warning("Invalid message: " + String.valueOf(message));
614 			return;
615 		}
616 		
617 		TCMessage tcMessage = (TCMessage)message;
618 		
619 		if (tcMessage.getSource() == null) {
620 			log.warning("TCMessage.getSource() is NULL, cannot process: " + String.valueOf(message));
621 			return;
622 		}
623 		if (tcMessage.getMessageType() == null) {
624 			log.warning("TCMessage.getMessageType() is NULL, cannot process: " + String.valueOf(message));
625 			return;
626 		}
627 		if (tcMessage.getTarget() == null) {
628 			log.warning("TCMessage.getTarget() is NULL, cannot process the message: " + String.valueOf(message));
629 			return;
630 		}
631 		
632 		switch(tcMessage.getTarget()) {
633 		case GLOBAL:
634 		case TEAM:
635 		case CHANNEL:
636 		case PRIVATE:
637 			Serializable data;
638 			try {
639 				data = tcMessage.getMessage();
640 			} catch (Exception e) {
641 				log.warning(ExceptionToString.process("Invalid request data, failed to deserialize TCMessage.getMessage(): " + String.valueOf(tcMessage), e));
642 				return;
643 			}
644 			botMessage(tcMessage, data);
645 			return;
646 		case TC_INFO:
647 			if (!(tcMessage instanceof TCInfoMessage)) {
648 				log.warning("TCMessage recipient is " + tcMessage.getTarget() + ", but the message is not TCInfoMessage: " + String.valueOf(tcMessage));
649 				return;
650 			}
651 			TCInfoData infoData = null;
652 			try {
653 				infoData = (TCInfoData)tcMessage.getMessage();
654 			} catch (Exception e) {
655 				log.warning("Invalid request data, failed to deserialize TCMessage.getMessage() as TCInfoMessageData: " + String.valueOf(tcMessage));
656 				return;
657 			}
658 			infoMessage((TCInfoMessage)tcMessage, infoData);				
659 			return;
660 		case TC_REQUEST:
661 			log.warning("Received TC_REQUEST message, cannot process: " + String.valueOf(message));
662 			return;
663 		}
664 	}
665 
666 	@Override
667 	public void messageSent(IoSession session, Object message) throws Exception {
668 	}
669 
670 	@Override
671 	public void sessionClosed(IoSession session) throws Exception {
672 		synchronized(mutex) {
673 			if (timer != null) {
674 				timer.cancel();
675 				timer = null;
676 			}
677 			
678 			session = null;
679 			
680 			log.warning("TC Server connection closed.");
681 			
682 			connected.setFlag(false);
683 			connecting.setFlag(false);			
684 		}
685 	}
686 
687 	@Override
688 	public void sessionCreated(IoSession session) throws Exception {
689 	}
690 
691 	@Override
692 	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
693 	}
694 
695 	@Override
696 	public void sessionOpened(IoSession session) throws Exception {
697 	}
698 	
699 	// ==================
700 	// TCMESSAGE HANDLERS
701 	// ==================
702 	
703 	private void botMessage(TCMessage message, Serializable data) {
704 		notify(message, data);
705 	}
706 
707 	private void infoMessage(TCInfoMessage message, TCInfoData data) {
708 		if (data.getMessageType() == TCInfoStatus.MESSAGE_TYPE) {
709 			status(message, (TCInfoStatus)data);
710 		} else 
711 		if (data.getMessageType() == TCInfoBotJoined.MESSAGE_TYPE) {
712 			botJoined(message, (TCInfoBotJoined)data);
713 		} else
714 		if (data.getMessageType() == TCInfoBotLeft.MESSAGE_TYPE) {
715 			botLeft(message, (TCInfoBotLeft)data);
716 		} else 
717 		if (data.getMessageType() == TCInfoRequestFailed.MESSAGE_TYPE) {
718 			requestFailed(message, (TCInfoRequestFailed)data);
719 		} else
720 		if (data.getMessageType() == TCInfoRequestFailed.MESSAGE_TYPE) {
721 			status(message, (TCInfoStatus)data);
722 		} else
723 		if (data.getMessageType() == TCInfoTeamChannelBotJoined.MESSAGE_TYPE) {
724 			channelBotJoined(message, (TCInfoTeamChannelBotJoined)data);
725 		} else	
726 		if (data.getMessageType() == TCInfoTeamChannelBotLeft.MESSAGE_TYPE) {
727 			channelBotLeft(message, (TCInfoTeamChannelBotLeft)data);
728 		} else	
729 		if (data.getMessageType() == TCInfoTeamChannelCreated.MESSAGE_TYPE) {
730 			channelCreated(message, (TCInfoTeamChannelCreated)data);
731 		} else
732 		if (data.getMessageType() == TCInfoTeamChannelDestroyed.MESSAGE_TYPE) {
733 			channelDestroyed(message, (TCInfoTeamChannelDestroyed)data);
734 		} else {
735 			log.warning("Unhandled INFO message type: " + data.getMessageType().getToken());
736 		}
737 	}
738 	
739 	// =======================
740 	// TCMESSAGE DATA HANDLERS
741 	// =======================
742 
743 	private void botJoined(TCInfoMessage message, TCInfoBotJoined data) {
744 		if (data.getBotId() == null) {
745 			log.warning("TCInfoBotJoined.getBotId() is NULL!");
746 			return;
747 		}
748 		
749 		log.info("Bot " + data.getBotId().getStringId() + " has joined TC.");
750 		
751 		synchronized(mutex) {
752 			allBots.add(data.getBotId());
753 			
754 			if (team == null) return;
755 			
756 			if (data.getTeam() != team.getTeam()) {
757 				return;
758 			}
759 			
760 			team.getConnectedBots().add(data.getBotId());
761 		}
762 		
763 		notify(message, data);
764 	}
765 
766 	private void botLeft(TCInfoMessage message, TCInfoBotLeft data) {
767 		if (data.getBotId() == null) {
768 			log.warning("TCInfoBotLeft.getBotId() is NULL!");
769 			return;
770 		}
771 		
772 		log.warning("Bot " + data.getBotId().getStringId() + " has left TC.");
773 		
774 		synchronized(mutex) {
775 			allBots.add(data.getBotId());
776 			
777 			if (team == null) return;
778 			
779 			if (data.getTeam() != team.getTeam()) {
780 				return;
781 			}
782 			
783 			team.getConnectedBots().remove(data.getBotId());
784 			for (TCChannel channel : team.getChannels().values()) {
785 				channel.getConnectedBots().remove(data.getBotId());
786 			}
787 		}	
788 		
789 		notify(message, data);
790 	}
791 
792 	private void requestFailed(TCInfoMessage message, TCInfoRequestFailed data) {
793 		if (connecting.getFlag()) {
794 			log.warning("Failed to register, will retry in " + RETRY_REGISTER_PERIOD_SECS + " seconds...");
795 			if (timer == null) timer = new Timer(); 
796 			timer.schedule(new TimerTask() {
797 				@Override
798 				public void run() {
799 					++registerTries;
800 					log.warning("Trying to register again (" + registerTries + ")...");					
801 					sendRegisterRequest();
802 				}				
803 			}, RETRY_REGISTER_PERIOD_SECS * 1000);
804 			return;
805 		} 
806 		
807 		// CONNECTED TO THE SERVER
808 		// => process as user request
809 		
810 		failRequestFuture(null, data);			
811 		
812 		notify(message, data);		
813 	}
814 
815 	private void status(TCInfoMessage message, TCInfoStatus data) {
816 		log.info("Received TCInfoStatus message...");
817 		
818 		synchronized(mutex) {
819 			allBots = new HashSet<UnrealId>(data.getAllBots());
820 			team = data.getTeam();
821 		
822 			if (connecting.getFlag()) {
823 				log.info("Connected to TC Server at " + address.getHostName() + ":" + address.getPort());
824 				
825 				timer = null;
826 				
827 				connected.setFlag(true);
828 				connecting.setFlag(false);
829 			} else {
830 				requestFinishedUnsync(TCRequestGetStatus.MESSAGE_TYPE, data.getRequestId(), data);
831 			}
832 		}
833 		
834 		notify(message, data);
835 	}
836 
837 	private void channelBotJoined(TCInfoMessage message, TCInfoTeamChannelBotJoined data) {
838 		if (data.getBotId() == null) {
839 			log.warning("TCInfoTeamChannelBotJoined.getBotId() is NULL!");
840 		}
841 		synchronized(mutex) {
842 			TCChannel channel = team.getChannels().get(data.getChannelId());
843 			if (channel == null) {
844 				log.warning("Bot " + data.getBotId().getStringId() + " has joined unknown channel " + data.getChannelId() + "! Requesting STATUS...");
845 				requestGetStatus();
846 				return;
847 			}
848 			channel.getConnectedBots().add(data.getBotId());
849 			
850 			requestFinishedUnsync(TCRequestJoinChannel.MESSAGE_TYPE, data.getRequestId(), data);
851 		}
852 		notify(message, data);
853 	}
854 
855 	private void channelBotLeft(TCInfoMessage message, TCInfoTeamChannelBotLeft data) {
856 		if (data.getBotId() == null) {
857 			log.warning("TCInfoTeamChannelBotLeft.getBotId() is NULL!");
858 		}
859 		synchronized(mutex) {
860 			TCChannel channel = team.getChannels().get(data.getChannelId());
861 			if (channel == null) {
862 				log.warning("Bot " + data.getBotId().getStringId() + " has left unknown channel " + data.getChannelId() + "! Requesting STATUS...");
863 				requestGetStatus();
864 				return;
865 			}
866 			channel.getConnectedBots().remove(data.getBotId());
867 			
868 			requestFinishedUnsync(TCRequestLeaveChannel.MESSAGE_TYPE, data.getRequestId(), data);
869 		}
870 		notify(message, data);
871 	}
872 
873 	private void channelCreated(TCInfoMessage message, TCInfoTeamChannelCreated data) {
874 		if (team == null) return;
875 		if (data.getChannel() == null) {
876 			log.warning("TCInfoTeamChannelCreated.getChannel() is NULL!");
877 			return;
878 		}
879 		synchronized(mutex) {
880 			team.getChannels().put(data.getChannel().getChannelId(), data.getChannel().clone());
881 			requestFinishedUnsync(TCRequestCreateChannel.MESSAGE_TYPE, data.getRequestId(), data);
882 		}
883 		notify(message, data);
884 	}
885 
886 	private void channelDestroyed(TCInfoMessage message, TCInfoTeamChannelDestroyed data) {
887 		synchronized(mutex) {
888 			if (team == null) return;
889 			team.getChannels().remove(data.getChannelId());
890 			requestFinishedUnsync(TCRequestDestroyChannel.MESSAGE_TYPE, data.getRequestId(), data);
891 		}
892 		notify(message, data);
893 	}
894 	
895 	// ========================
896 	// NOTIFICATION TO THE USER
897 	// ========================
898 	
899 	@SuppressWarnings("rawtypes")
900 	private void requestFinishedUnsync(IToken requestMessageType, long requestId, Object result) {
901 		Map<Long, RequestFuture<?>> futures = requestFutures.get(requestMessageType);
902 		if (futures == null) return;
903 		RequestFuture requestFuture = futures.remove(requestId);
904 		if (requestFuture == null) return;
905 		requestFuture.setResult(result);
906 	}
907 
908 	/**
909 	 * @param requestMessageType can be null (all request message types are going to be checked...)
910 	 * @param data
911 	 */
912 	private void failRequestFuture(IToken requestMessageType, TCInfoRequestFailed data) {
913 		synchronized(mutex) {
914 			if (requestMessageType == null) {
915 				for (IToken messageType : requestFutures.keySet()) {
916 					Map<Long, RequestFuture<?>> futures = requestFutures.get(messageType);
917 					RequestFuture<?> requestFuture = futures.remove(data.getRequestId());
918 					if (requestFuture != null) {
919 						requestFuture.computationException(new TCInfoRequestFailedException(requestFuture.message, data, log, this));
920 					}
921 				}
922 			} else {
923 				Map<Long, RequestFuture<?>> futures = requestFutures.get(requestMessageType);
924 				if (futures == null) return;
925 				RequestFuture<?> requestFuture = futures.remove(data.getRequestId());
926 				if (requestFuture == null) return;
927 				requestFuture.computationException(new TCInfoRequestFailedException(requestFuture.message, data, log, this));				
928 			}
929 		}
930 	}
931 	
932 	private void notify(TCMessage message, Serializable data) {
933 		if (data instanceof IWorldChangeEvent && data instanceof IWorldEvent) {
934 			teamWorldView.notify((IWorldChangeEvent)data);
935 		} 
936 		if (message instanceof IWorldChangeEvent && message instanceof IWorldEvent) {
937 			teamWorldView.notify(message);
938 		}
939 	}
940 	
941 	// =========
942 	// LIFECYCLE
943 	// =========
944 
945 	public void stop() {
946 		synchronized(mutex) {
947 			log.info("Stopping TCMinaClient!");
948 			
949 			allBots.clear();
950 			team = null;
951 			
952 			if (timer != null) {
953 				timer.cancel();
954 				timer = null;
955 			}
956 			
957 			try {
958 				if (session != null) {
959 					session.close(true);
960 				}
961 			} catch (Exception e) {				
962 			}
963 			session = null;
964 			
965 			try {
966 				connected.setFlag(false);
967 			} catch (Exception e) {
968 			}
969 			try {
970 				connecting.setFlag(false);
971 			} catch (Exception e) {			
972 			}
973 			
974 			for (IToken messageType : requestFutures.keySet()) {
975 				Map<Long, RequestFuture<?>> futures = requestFutures.get(messageType);
976 				for (RequestFuture<?> future : futures.values()) {
977 					future.cancel(true);					
978 				}				
979 			}
980 			requestFutures.clear();
981 			
982 			log.info("TCMinaClient stopped!");
983 		}
984 	}
985 
986 }