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).
404 	 * @param messageType CANNOT BE NULL
405 	 * @param data may be null
406 	 * @return
407 	 */
408 	public boolean sendToAll(IToken messageType, Serializable data) {
409 		if (messageType == null) return false;
410 		
411 		TCMessage message = new TCMessage(botId, TCRecipient.GLOBAL, messageType, data, owner.getSimTime());
412 		
413 		synchronized(mutex) {
414 			if (!connected.getFlag() || session == null) return false;
415 			try {
416 				session.write(message);
417 			} catch (Exception e) {
418 				log.warning(ExceptionToString.process("Failed to sendToAll: " + data, e));
419 				return false;
420 			}			
421 			return true;
422 		}
423 	}
424 	
425 	/**
426 	 * Sends message to connected bots of YOUR TEAM (not to all bots connected to the TC server).
427 	 * @param messageType CANNOT BE NULL
428 	 * @param data may be null
429 	 * @return
430 	 */
431 	public boolean sendToTeam(IToken messageType, Serializable data) {
432 		if (messageType == null) return false;
433 		
434 		TCMessage message = new TCMessage(botId, TCRecipient.TEAM, messageType, data, owner.getSimTime());
435 		
436 		synchronized(mutex) {
437 			if (!connected.getFlag() || session == null) return false;
438 			try {
439 				session.write(message);
440 			} catch (Exception e) {
441 				log.warning(ExceptionToString.process("Failed to sendToTeam: " + data, e));
442 				return false;
443 			}			
444 			return true;			
445 		}
446 	}
447 	
448 	public boolean sendToChannel(int channelId, IToken messageType, Serializable data) {
449 		if (messageType == null) return false;
450 		
451 		TCMessage message = new TCMessage(botId, TCRecipient.CHANNEL, messageType, data, owner.getSimTime());
452 		message.setChannelId(channelId);
453 		
454 		synchronized(mutex) {
455 			if (!connected.getFlag() || session == null) return false;
456 			try {
457 				session.write(message);
458 			} catch (Exception e) {
459 				log.warning(ExceptionToString.process("Failed to sendToChannel(" + channelId + "): " + data, e));
460 				return false;
461 			}			
462 			return true;
463 		}
464 	}
465 	
466 	public boolean sendPrivate(UnrealId targetBotId, IToken messageType, Serializable data) {
467 		if (messageType == null) return false;
468 		
469 		TCMessage message = new TCMessage(botId, TCRecipient.PRIVATE, messageType, data, owner.getSimTime());
470 		message.setTargetId(targetBotId);
471 		
472 		synchronized(mutex) {
473 			if (!connected.getFlag() || session == null) return false;
474 			try {
475 				session.write(message);
476 			} catch (Exception e) {
477 				log.warning(ExceptionToString.process("Failed to sendPrivate(" + targetBotId.getStringId() + "): " + data, e));
478 				return false;
479 			}			
480 			return true;
481 		}
482 	}
483 	
484 	// ======
485 	// EVENTS
486 	// ======
487 	
488 	protected void connected(ConnectFuture event) {
489 		log.info("Connected to TC at " + getHost() + ":" + getPort());		
490 		
491 		this.session = event.getSession();
492 		
493 		connectFuture.removeListener(connectionListener);
494 		connectFuture = null;
495 		
496 		log.info("Sending REGISTER request, expecting TCInfoStatus reply...");
497 		
498 		registerTries = 1;
499 		sendRegisterRequest();
500 	}
501 
502 	
503 	private void sendRegisterRequest() {
504 		TCRequestRegister request = new TCRequestRegister(owner.getSimTime());
505 		TCRequestMessage message = new TCRequestMessage(botId, request);
506 		synchronized(mutex) {
507 			session.write(message);
508 		}
509 	}
510 
511 	// ================================
512 	// IMPLEMENTATING {@link IOHandler}
513 	// ================================
514 	
515 	@Override
516 	public void exceptionCaught(IoSession session, Throwable exception) throws Exception {
517 		log.warning(ExceptionToString.process("TCMinaClient Exception", exception));
518 		stop();
519 	}
520 
521 	@Override
522 	public void messageReceived(IoSession session, Object message) throws Exception {		
523 		if (message == null) {
524 			log.warning("Invalid message: " + String.valueOf(message));
525 			return;
526 		}
527 		if (!(message instanceof TCMessage)) {
528 			log.warning("Invalid message: " + String.valueOf(message));
529 			return;
530 		}
531 		
532 		TCMessage tcMessage = (TCMessage)message;
533 		
534 		if (tcMessage.getSource() == null) {
535 			log.warning("TCMessage.getSource() is NULL, cannot process: " + String.valueOf(message));
536 			return;
537 		}
538 		if (tcMessage.getMessageType() == null) {
539 			log.warning("TCMessage.getMessageType() is NULL, cannot process: " + String.valueOf(message));
540 			return;
541 		}
542 		if (tcMessage.getTarget() == null) {
543 			log.warning("TCMessage.getTarget() is NULL, cannot process the message: " + String.valueOf(message));
544 			return;
545 		}
546 		
547 		switch(tcMessage.getTarget()) {
548 		case GLOBAL:
549 		case TEAM:
550 		case CHANNEL:
551 		case PRIVATE:
552 			Serializable data;
553 			try {
554 				data = tcMessage.getMessage();
555 			} catch (Exception e) {
556 				log.warning(ExceptionToString.process("Invalid request data, failed to deserialize TCMessage.getMessage(): " + String.valueOf(tcMessage), e));
557 				return;
558 			}
559 			botMessage(tcMessage, data);
560 			return;
561 		case TC_INFO:
562 			if (!(tcMessage instanceof TCInfoMessage)) {
563 				log.warning("TCMessage recipient is " + tcMessage.getTarget() + ", but the message is not TCInfoMessage: " + String.valueOf(tcMessage));
564 				return;
565 			}
566 			TCInfoData infoData = null;
567 			try {
568 				infoData = (TCInfoData)tcMessage.getMessage();
569 			} catch (Exception e) {
570 				log.warning("Invalid request data, failed to deserialize TCMessage.getMessage() as TCInfoMessageData: " + String.valueOf(tcMessage));
571 				return;
572 			}
573 			infoMessage((TCInfoMessage)tcMessage, infoData);				
574 			return;
575 		case TC_REQUEST:
576 			log.warning("Received TC_REQUEST message, cannot process: " + String.valueOf(message));
577 			return;
578 		}
579 	}
580 
581 	@Override
582 	public void messageSent(IoSession session, Object message) throws Exception {
583 	}
584 
585 	@Override
586 	public void sessionClosed(IoSession session) throws Exception {
587 		synchronized(mutex) {
588 			if (timer != null) {
589 				timer.cancel();
590 				timer = null;
591 			}
592 			
593 			session = null;
594 			
595 			log.warning("TC Server connection closed.");
596 			
597 			connected.setFlag(false);
598 			connecting.setFlag(false);			
599 		}
600 	}
601 
602 	@Override
603 	public void sessionCreated(IoSession session) throws Exception {
604 	}
605 
606 	@Override
607 	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
608 	}
609 
610 	@Override
611 	public void sessionOpened(IoSession session) throws Exception {
612 	}
613 	
614 	// ==================
615 	// TCMESSAGE HANDLERS
616 	// ==================
617 	
618 	private void botMessage(TCMessage message, Serializable data) {
619 		notify(message, data);
620 	}
621 
622 	private void infoMessage(TCInfoMessage message, TCInfoData data) {
623 		if (data.getMessageType() == TCInfoStatus.MESSAGE_TYPE) {
624 			status(message, (TCInfoStatus)data);
625 		} else 
626 		if (data.getMessageType() == TCInfoBotJoined.MESSAGE_TYPE) {
627 			botJoined(message, (TCInfoBotJoined)data);
628 		} else
629 		if (data.getMessageType() == TCInfoBotLeft.MESSAGE_TYPE) {
630 			botLeft(message, (TCInfoBotLeft)data);
631 		} else 
632 		if (data.getMessageType() == TCInfoRequestFailed.MESSAGE_TYPE) {
633 			requestFailed(message, (TCInfoRequestFailed)data);
634 		} else
635 		if (data.getMessageType() == TCInfoRequestFailed.MESSAGE_TYPE) {
636 			status(message, (TCInfoStatus)data);
637 		} else
638 		if (data.getMessageType() == TCInfoTeamChannelBotJoined.MESSAGE_TYPE) {
639 			channelBotJoined(message, (TCInfoTeamChannelBotJoined)data);
640 		} else	
641 		if (data.getMessageType() == TCInfoTeamChannelBotLeft.MESSAGE_TYPE) {
642 			channelBotLeft(message, (TCInfoTeamChannelBotLeft)data);
643 		} else	
644 		if (data.getMessageType() == TCInfoTeamChannelCreated.MESSAGE_TYPE) {
645 			channelCreated(message, (TCInfoTeamChannelCreated)data);
646 		} else
647 		if (data.getMessageType() == TCInfoTeamChannelDestroyed.MESSAGE_TYPE) {
648 			channelDestroyed(message, (TCInfoTeamChannelDestroyed)data);
649 		} else {
650 			log.warning("Unhandled INFO message type: " + data.getMessageType().getToken());
651 		}
652 	}
653 	
654 	// =======================
655 	// TCMESSAGE DATA HANDLERS
656 	// =======================
657 
658 	private void botJoined(TCInfoMessage message, TCInfoBotJoined data) {
659 		if (data.getBotId() == null) {
660 			log.warning("TCInfoBotJoined.getBotId() is NULL!");
661 			return;
662 		}
663 		
664 		log.info("Bot " + data.getBotId().getStringId() + " has joined TC.");
665 		
666 		synchronized(mutex) {
667 			allBots.add(data.getBotId());
668 			
669 			if (team == null) return;
670 			
671 			if (data.getTeam() != team.getTeam()) {
672 				return;
673 			}
674 			
675 			team.getConnectedBots().add(data.getBotId());
676 		}
677 		
678 		notify(message, data);
679 	}
680 
681 	private void botLeft(TCInfoMessage message, TCInfoBotLeft data) {
682 		if (data.getBotId() == null) {
683 			log.warning("TCInfoBotLeft.getBotId() is NULL!");
684 			return;
685 		}
686 		
687 		log.warning("Bot " + data.getBotId().getStringId() + " has left TC.");
688 		
689 		synchronized(mutex) {
690 			allBots.add(data.getBotId());
691 			
692 			if (team == null) return;
693 			
694 			if (data.getTeam() != team.getTeam()) {
695 				return;
696 			}
697 			
698 			team.getConnectedBots().remove(data.getBotId());
699 			for (TCChannel channel : team.getChannels().values()) {
700 				channel.getConnectedBots().remove(data.getBotId());
701 			}
702 		}	
703 		
704 		notify(message, data);
705 	}
706 
707 	private void requestFailed(TCInfoMessage message, TCInfoRequestFailed data) {
708 		if (connecting.getFlag()) {
709 			log.warning("Failed to register, will retry in " + RETRY_REGISTER_PERIOD_SECS + " seconds...");
710 			if (timer == null) timer = new Timer(); 
711 			timer.schedule(new TimerTask() {
712 				@Override
713 				public void run() {
714 					++registerTries;
715 					log.warning("Trying to register again (" + registerTries + ")...");					
716 					sendRegisterRequest();
717 				}				
718 			}, RETRY_REGISTER_PERIOD_SECS * 1000);
719 			return;
720 		} 
721 		
722 		// CONNECTED TO THE SERVER
723 		// => process as user request
724 		
725 		failRequestFuture(null, data);			
726 		
727 		notify(message, data);		
728 	}
729 
730 	private void status(TCInfoMessage message, TCInfoStatus data) {
731 		log.info("Received TCInfoStatus message...");
732 		
733 		synchronized(mutex) {
734 			allBots = new HashSet<UnrealId>(data.getAllBots());
735 			team = data.getTeam();
736 		
737 			if (connecting.getFlag()) {
738 				log.info("Connected to TC Server at " + address.getHostName() + ":" + address.getPort());
739 				
740 				timer = null;
741 				
742 				connected.setFlag(true);
743 				connecting.setFlag(false);
744 			} else {
745 				requestFinishedUnsync(TCRequestGetStatus.MESSAGE_TYPE, data.getRequestId(), data);
746 			}
747 		}
748 		
749 		notify(message, data);
750 	}
751 
752 	private void channelBotJoined(TCInfoMessage message, TCInfoTeamChannelBotJoined data) {
753 		if (data.getBotId() == null) {
754 			log.warning("TCInfoTeamChannelBotJoined.getBotId() is NULL!");
755 		}
756 		synchronized(mutex) {
757 			TCChannel channel = team.getChannels().get(data.getChannelId());
758 			if (channel == null) {
759 				log.warning("Bot " + data.getBotId().getStringId() + " has joined unknown channel " + data.getChannelId() + "! Requesting STATUS...");
760 				requestGetStatus();
761 				return;
762 			}
763 			channel.getConnectedBots().add(data.getBotId());
764 			
765 			requestFinishedUnsync(TCRequestJoinChannel.MESSAGE_TYPE, data.getRequestId(), data);
766 		}
767 		notify(message, data);
768 	}
769 
770 	private void channelBotLeft(TCInfoMessage message, TCInfoTeamChannelBotLeft data) {
771 		if (data.getBotId() == null) {
772 			log.warning("TCInfoTeamChannelBotLeft.getBotId() is NULL!");
773 		}
774 		synchronized(mutex) {
775 			TCChannel channel = team.getChannels().get(data.getChannelId());
776 			if (channel == null) {
777 				log.warning("Bot " + data.getBotId().getStringId() + " has left unknown channel " + data.getChannelId() + "! Requesting STATUS...");
778 				requestGetStatus();
779 				return;
780 			}
781 			channel.getConnectedBots().remove(data.getBotId());
782 			
783 			requestFinishedUnsync(TCRequestLeaveChannel.MESSAGE_TYPE, data.getRequestId(), data);
784 		}
785 		notify(message, data);
786 	}
787 
788 	private void channelCreated(TCInfoMessage message, TCInfoTeamChannelCreated data) {
789 		if (team == null) return;
790 		if (data.getChannel() == null) {
791 			log.warning("TCInfoTeamChannelCreated.getChannel() is NULL!");
792 			return;
793 		}
794 		synchronized(mutex) {
795 			team.getChannels().put(data.getChannel().getChannelId(), data.getChannel().clone());
796 			requestFinishedUnsync(TCRequestCreateChannel.MESSAGE_TYPE, data.getRequestId(), data);
797 		}
798 		notify(message, data);
799 	}
800 
801 	private void channelDestroyed(TCInfoMessage message, TCInfoTeamChannelDestroyed data) {
802 		synchronized(mutex) {
803 			if (team == null) return;
804 			team.getChannels().remove(data.getChannelId());
805 			requestFinishedUnsync(TCRequestDestroyChannel.MESSAGE_TYPE, data.getRequestId(), data);
806 		}
807 		notify(message, data);
808 	}
809 	
810 	// ========================
811 	// NOTIFICATION TO THE USER
812 	// ========================
813 	
814 	@SuppressWarnings("rawtypes")
815 	private void requestFinishedUnsync(IToken requestMessageType, long requestId, Object result) {
816 		Map<Long, RequestFuture<?>> futures = requestFutures.get(requestMessageType);
817 		if (futures == null) return;
818 		RequestFuture requestFuture = futures.remove(requestId);
819 		if (requestFuture == null) return;
820 		requestFuture.setResult(result);
821 	}
822 
823 	/**
824 	 * @param requestMessageType can be null (all request message types are going to be checked...)
825 	 * @param data
826 	 */
827 	private void failRequestFuture(IToken requestMessageType, TCInfoRequestFailed data) {
828 		synchronized(mutex) {
829 			if (requestMessageType == null) {
830 				for (IToken messageType : requestFutures.keySet()) {
831 					Map<Long, RequestFuture<?>> futures = requestFutures.get(messageType);
832 					RequestFuture<?> requestFuture = futures.remove(data.getRequestId());
833 					if (requestFuture != null) {
834 						requestFuture.computationException(new TCInfoRequestFailedException(requestFuture.message, data, log, this));
835 					}
836 				}
837 			} else {
838 				Map<Long, RequestFuture<?>> futures = requestFutures.get(requestMessageType);
839 				if (futures == null) return;
840 				RequestFuture<?> requestFuture = futures.remove(data.getRequestId());
841 				if (requestFuture == null) return;
842 				requestFuture.computationException(new TCInfoRequestFailedException(requestFuture.message, data, log, this));				
843 			}
844 		}
845 	}
846 	
847 	private void notify(TCMessage message, Serializable data) {
848 		if (data instanceof IWorldChangeEvent && data instanceof IWorldEvent) {
849 			teamWorldView.notify((IWorldChangeEvent)data);
850 		} 
851 		if (message instanceof IWorldChangeEvent && message instanceof IWorldEvent) {
852 			teamWorldView.notify(message);
853 		}
854 	}
855 	
856 	// =========
857 	// LIFECYCLE
858 	// =========
859 
860 	public void stop() {
861 		synchronized(mutex) {
862 			log.info("Stopping TCMinaClient!");
863 			
864 			allBots.clear();
865 			team = null;
866 			
867 			if (timer != null) {
868 				timer.cancel();
869 				timer = null;
870 			}
871 			
872 			try {
873 				if (session != null) {
874 					session.close(true);
875 				}
876 			} catch (Exception e) {				
877 			}
878 			session = null;
879 			
880 			try {
881 				connected.setFlag(false);
882 			} catch (Exception e) {
883 			}
884 			try {
885 				connecting.setFlag(false);
886 			} catch (Exception e) {			
887 			}
888 			
889 			for (IToken messageType : requestFutures.keySet()) {
890 				Map<Long, RequestFuture<?>> futures = requestFutures.get(messageType);
891 				for (RequestFuture<?> future : futures.values()) {
892 					future.cancel(true);					
893 				}				
894 			}
895 			requestFutures.clear();
896 			
897 			log.info("TCMinaClient stopped!");
898 		}
899 	}
900 
901 }