diff --git a/src/net/java/sip/communicator/impl/protocol/irc/ChannelManager.java b/src/net/java/sip/communicator/impl/protocol/irc/ChannelManager.java new file mode 100644 index 000000000..3e99a5742 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/irc/ChannelManager.java @@ -0,0 +1,1331 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.irc; + +import java.util.*; + +import net.java.sip.communicator.impl.protocol.irc.ModeParser.ModeEntry; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + +import com.ircclouds.irc.api.*; +import com.ircclouds.irc.api.domain.*; +import com.ircclouds.irc.api.domain.messages.*; +import com.ircclouds.irc.api.domain.messages.interfaces.*; +import com.ircclouds.irc.api.listeners.*; +import com.ircclouds.irc.api.state.*; + +/** + * Channel manager. + * + * @author Danny van Heumen + */ +public class ChannelManager +{ + /** + * Logger. + */ + private static final Logger LOGGER = Logger.getLogger(ChannelManager.class); + + /** + * IRCApi instance. + */ + private final IRCApi irc; + + /** + * Connection state. + */ + private final IIRCState connectionState; + + /** + * Provider. + */ + private final ProtocolProviderServiceIrcImpl provider; + + /** + * Identity manager instance. + */ + private final IdentityManager identity; + + /** + * Container for joined channels. + * + * There are two different cases: + * + *
+     * - null value: joining is initiated but still in progress.
+     * - non-null value: joining is finished, chat room instance is available.
+     * 
+ */ + private final Map joined = Collections + .synchronizedMap(new HashMap()); + + /** + * Constructor. + * @param irc the IRCApi instance + * @param connectionState the connection state + * @param provider the provider instance + * @param identity the identity manager instance + */ + public ChannelManager(final IRCApi irc, final IIRCState connectionState, + final ProtocolProviderServiceIrcImpl provider, + final IdentityManager identity) + { + if (irc == null) + { + throw new IllegalArgumentException("irc instance cannot be null"); + } + irc.addListener(new ManagerListener(irc)); + this.irc = irc; + if (connectionState == null) + { + throw new IllegalArgumentException( + "connectionState cannot be null"); + } + this.connectionState = connectionState; + if (provider == null) + { + throw new IllegalArgumentException("provider cannot be null"); + } + this.provider = provider; + if (identity == null) + { + throw new IllegalArgumentException( + "IdentityManager instance cannot be null"); + } + this.identity = identity; + } + + /** + * Get a set of channel type indicators. + * + * @return returns set of channel type indicators. + */ + public Set getChannelTypes() + { + return this.connectionState.getServerOptions().getChanTypes(); + } + + /** + * Check whether the user has joined a particular chat room. + * + * @param chatroom Chat room to check for. + * @return Returns true in case the user is already joined, or false if the + * user has not joined. + */ + public boolean isJoined(final ChatRoomIrcImpl chatroom) + { + return this.joined.get(chatroom.getIdentifier()) != null; + } + + /** + * Join a particular chat room. + * + * @param chatroom Chat room to join. + * @throws OperationFailedException failed to join the chat room + */ + public void join(final ChatRoomIrcImpl chatroom) + throws OperationFailedException + { + join(chatroom, ""); + } + + /** + * Join a particular chat room. + * + * Issue a join channel IRC operation and wait for the join operation to + * complete (either successfully or failing). + * + * @param chatroom The chatroom to join. + * @param password Optionally, a password that may be required for some + * channels. + * @throws OperationFailedException failed to join the chat room + */ + public void join(final ChatRoomIrcImpl chatroom, final String password) + throws OperationFailedException + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException( + "Please connect to an IRC server first"); + } + if (chatroom == null) + { + throw new IllegalArgumentException("chatroom cannot be null"); + } + if (password == null) + { + throw new IllegalArgumentException("password cannot be null"); + } + + final String chatRoomId = chatroom.getIdentifier(); + if (this.joined.containsKey(chatRoomId)) + { + // If we already joined this particular chatroom, no further action + // is required. + return; + } + + LOGGER.trace("Start joining channel " + chatRoomId); + final Result joinSignal = + new Result(); + synchronized (joinSignal) + { + LOGGER.trace("Issue join channel command to IRC library and wait " + + "for join operation to complete (un)successfully."); + + this.joined.put(chatRoomId, null); + synchronized (this.irc) + { + // TODO Refactor this ridiculous nesting of functions and + // classes. + this.irc.joinChannel(chatRoomId, password, + new Callback() + { + + @Override + public void onSuccess(final IRCChannel channel) + { + if (LOGGER.isTraceEnabled()) + { + LOGGER + .trace("Started callback for successful " + + "join of channel '" + + chatroom.getIdentifier() + "'."); + } + boolean isRequestedChatRoom = + channel.getName().equalsIgnoreCase(chatRoomId); + synchronized (joinSignal) + { + if (!isRequestedChatRoom) + { + // We joined another chat room than the one + // we requested initially. + if (LOGGER.isTraceEnabled()) + { + LOGGER.trace("Callback for successful " + + "join finished prematurely " + + "since we got forwarded from " + + "'" + + chatRoomId + + "' to '" + + channel.getName() + + "'. Joining of forwarded channel " + + "gets handled by Server Listener " + + "since that channel was not " + + "announced."); + } + // Remove original chat room id from + // joined-list since we aren't actually + // attempting to join this room anymore. + ChannelManager.this.joined + .remove(chatRoomId); + ChannelManager.this.provider.getMUC() + .fireLocalUserPresenceEvent( + chatroom, + LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOIN_FAILED, + "We got forwarded to channel '" + + channel.getName() + "'."); + // Notify waiting threads of finished + // execution. + joinSignal.setDone(); + joinSignal.notifyAll(); + // The channel that we were forwarded to + // will be handled by the Server Listener, + // since the channel join was unannounced, + // and we are done here. + return; + } + + try + { + ChannelManager.this.joined.put(chatRoomId, + chatroom); + ChannelManager.this.irc + .addListener(new ChatRoomListener( + ChannelManager.this.irc, chatroom)); + prepareChatRoom(chatroom, channel); + } + finally + { + // In any case, issue the local user + // presence, since the irc library notified + // us of a successful join. We should wait + // as long as possible though. First we need + // to fill the list of chat room members and + // other chat room properties. + ChannelManager.this.provider.getMUC() + .fireLocalUserPresenceEvent( + chatroom, + LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOINED, + null); + if (LOGGER.isTraceEnabled()) + { + LOGGER + .trace("Finished successful join " + + "callback for channel '" + + chatRoomId + + "'. Waking up original " + + "thread."); + } + // Notify waiting threads of finished + // execution. + joinSignal.setDone(); + joinSignal.notifyAll(); + } + } + } + + @Override + public void onFailure(final Exception e) + { + LOGGER + .trace("Started callback for failed attempt to " + + "join channel '" + chatRoomId + "'."); + synchronized (joinSignal) + { + try + { + ChannelManager.this.joined + .remove(chatRoomId); + ChannelManager.this.provider.getMUC() + .fireLocalUserPresenceEvent( + chatroom, + LocalUserChatRoomPresenceChangeEvent + .LOCAL_USER_JOIN_FAILED, + e.getMessage()); + } + finally + { + if (LOGGER.isTraceEnabled()) + { + LOGGER + .trace("Finished callback for " + + "failed attempt to join " + + "channel '" + + chatRoomId + + "'. Waking up original " + + "thread."); + } + // Notify waiting threads of finished + // execution + joinSignal.setDone(e); + joinSignal.notifyAll(); + } + } + } + }); + } + + try + { + while (!joinSignal.isDone()) + { + LOGGER.trace("Waiting for channel join message ..."); + // Wait until async channel join operation has finished. + joinSignal.wait(); + } + + LOGGER + .trace("Finished waiting for join operation for channel '" + + chatroom.getIdentifier() + "' to complete."); + // TODO How to handle 480 (+j): Channel throttle exceeded? + } + catch (InterruptedException e) + { + LOGGER.error("Wait for join operation was interrupted.", e); + throw new OperationFailedException(e.getMessage(), + OperationFailedException.INTERNAL_ERROR, e); + } + } + } + + /** + * Prepare a chat room for initial opening. + * + * @param channel The IRC channel which is the source of data. + * @param chatRoom The chatroom to prepare. + */ + private void prepareChatRoom(final ChatRoomIrcImpl chatRoom, + final IRCChannel channel) + { + final IRCTopic topic = channel.getTopic(); + chatRoom.updateSubject(topic.getValue()); + + for (IRCUser user : channel.getUsers()) + { + ChatRoomMemberIrcImpl member = + new ChatRoomMemberIrcImpl(this.provider, chatRoom, + user.getNick(), ChatRoomMemberRole.SILENT_MEMBER); + ChatRoomMemberRole role; + for (IRCUserStatus status : channel.getStatusesForUser(user)) + { + role = convertMemberMode(status.getChanModeType().charValue()); + member.addRole(role); + } + chatRoom.addChatRoomMember(member.getContactAddress(), member); + if (this.identity.getNick().equals(user.getNick())) + { + chatRoom.setLocalUser(member); + if (member.getRole() != ChatRoomMemberRole.SILENT_MEMBER) + { + ChatRoomLocalUserRoleChangeEvent event = + new ChatRoomLocalUserRoleChangeEvent(chatRoom, + ChatRoomMemberRole.SILENT_MEMBER, member.getRole(), + true); + chatRoom.fireLocalUserRoleChangedEvent(event); + } + } + } + } + + /** + * Set the subject of the specified chat room. + * + * @param chatroom The chat room for which to set the subject. + * @param subject The subject. + */ + public void setSubject(final ChatRoomIrcImpl chatroom, final String subject) + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException( + "Please connect to an IRC server first."); + } + if (chatroom == null) + { + throw new IllegalArgumentException("Cannot have a null chatroom"); + } + LOGGER.trace("Setting chat room topic to '" + subject + "'"); + synchronized (this.irc) + { + this.irc.changeTopic(chatroom.getIdentifier(), subject == null ? "" + : subject); + } + } + + /** + * Part from a joined chat room. + * + * @param chatroom The chat room to part from. + */ + public void leave(final ChatRoomIrcImpl chatroom) + { + LOGGER.trace("Leaving chat room '" + chatroom.getIdentifier() + "'."); + leave(chatroom.getIdentifier()); + } + + /** + * Part from a joined chat room. + * + * @param chatRoomName The chat room to part from. + */ + private void leave(final String chatRoomName) + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException("Not connected to an IRC server."); + } + + try + { + synchronized (this.irc) + { + this.irc.leaveChannel(chatRoomName); + } + } + catch (ApiException e) + { + LOGGER.warn("exception occurred while leaving channel", e); + } + } + + /** + * Grant user permissions to specified user. + * + * @param chatRoom chat room to grant permissions for + * @param userAddress user to grant permissions to + * @param mode mode to grant + */ + public void grant(final ChatRoomIrcImpl chatRoom, final String userAddress, + final Mode mode) + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException("Not connected to an IRC server."); + } + if (mode.getRole() == null) + { + throw new IllegalArgumentException( + "This mode does not modify user permissions."); + } + synchronized (this.irc) + { + this.irc.changeMode(chatRoom.getIdentifier() + " +" + + mode.getSymbol() + " " + userAddress); + } + } + + /** + * Revoke user permissions of chat room for user. + * + * @param chatRoom chat room + * @param userAddress user + * @param mode mode + */ + public void revoke(final ChatRoomIrcImpl chatRoom, + final String userAddress, final Mode mode) + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException("Not connected to an IRC server."); + } + if (mode.getRole() == null) + { + throw new IllegalArgumentException( + "This mode does not modify user permissions."); + } + synchronized (this.irc) + { + this.irc.changeMode(chatRoom.getIdentifier() + " -" + + mode.getSymbol() + " " + userAddress); + } + } + + /** + * Ban chat room member. + * + * @param chatroom chat room to ban from + * @param member member to ban + * @param reason reason for banning + * @throws OperationFailedException throws operation failed in case of + * trouble. + */ + public void banParticipant(final ChatRoomIrcImpl chatroom, + final ChatRoomMember member, final String reason) + throws OperationFailedException + { + // TODO Implement banParticipant. + throw new OperationFailedException("Not implemented yet.", + OperationFailedException.NOT_SUPPORTED_OPERATION); + } + + /** + * Kick channel member. + * + * @param chatroom channel to kick from + * @param member member to kick + * @param reason kick message to deliver + */ + public void kickParticipant(final ChatRoomIrcImpl chatroom, + final ChatRoomMember member, final String reason) + { + if (!this.connectionState.isConnected()) + { + return; + } + synchronized (this.irc) + { + this.irc.kick(chatroom.getIdentifier(), member.getContactAddress(), + reason); + } + } + + /** + * Issue invite command to IRC server. + * + * @param memberId member to invite + * @param chatroom channel to invite to + */ + public void invite(final String memberId, final ChatRoomIrcImpl chatroom) + { + if (!this.connectionState.isConnected()) + { + throw new IllegalStateException("Not connected to an IRC server."); + } + synchronized (this.irc) + { + this.irc.rawMessage("INVITE " + memberId + " " + + chatroom.getIdentifier()); + } + } + + /** + * Convert a member mode character to a ChatRoomMemberRole instance. + * + * @param modeSymbol The member mode character. + * @return Return the instance of ChatRoomMemberRole corresponding to the + * member mode character. + */ + private static ChatRoomMemberRole convertMemberMode(final char modeSymbol) + { + return Mode.bySymbol(modeSymbol).getRole(); + } + + /** + * The channel manager listener. This listener is used for any events that + * are not directly related to an open, managed chat room. This includes + * events signaling that a channel has been joined on initiative of the IRC + * server, such that it isn't managed yet. + * + * @author Danny van Heumen + */ + private final class ManagerListener extends VariousMessageListenerAdapter + { + /** + * IRC reply code for end of list. + */ + private static final int RPL_LISTEND = + IRCServerNumerics.CHANNEL_NICKS_END_OF_LIST; + + /** + * IRCApi instance. + */ + private final IRCApi irc; + + /** + * Constructor. + * + * @param irc IRCApi instance + */ + public ManagerListener(final IRCApi irc) + { + if (irc == null) + { + throw new IllegalArgumentException("irc cannot be null"); + } + this.irc = irc; + } + + /** + * Server numeric message. + * + * @param msg server numeric message + */ + @Override + public void onServerNumericMessage(final ServerNumericMessage msg) + { + switch (msg.getNumericCode().intValue()) + { + case RPL_LISTEND: + // CHANNEL_NICKS_END_OF_LIST indicates the end of a nick list as + // you will receive when joining a channel. This is used as the + // indicator that we have joined a channel. Now we have to + // determine whether or not we already know about this + // particular join attempt. If not, we continue to inform Jitsi + // and to create a listener for this new chat room. + final String text = msg.getText(); + final String channelName = text.substring(0, text.indexOf(' ')); + final ChatRoomIrcImpl chatRoom; + final IRCChannel channel; + synchronized (ChannelManager.this.joined) + { + // Synchronize the section that checks then adds a chat + // room. This way we can be sure that there are no 2 + // simultaneous creation events. + if (ChannelManager.this.joined.containsKey(channelName)) + { + LOGGER.trace("Chat room '" + channelName + + "' join event was announced or already " + + "finished. Stop handling this event."); + break; + } + // We aren't currently attempting to join, so this join is + // unannounced. + LOGGER.trace("Starting unannounced join of chat room '" + + channelName); + // Assuming that at the time that NICKS_END_OF_LIST is + // propagated, the channel join event has been completely + // handled by IRCApi. + channel = + ChannelManager.this.connectionState + .getChannelByName(channelName); + chatRoom = + new ChatRoomIrcImpl(channelName, + ChannelManager.this.provider); + ChannelManager.this.joined.put(channelName, chatRoom); + } + this.irc.addListener(new ChatRoomListener(this.irc, chatRoom)); + try + { + ChannelManager.this.provider.getMUC().openChatRoomWindow( + chatRoom); + } + catch (NullPointerException e) + { + LOGGER.error("failed to open chat room window", e); + } + ChannelManager.this.prepareChatRoom(chatRoom, channel); + ChannelManager.this.provider.getMUC() + .fireLocalUserPresenceEvent(chatRoom, + LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, + null); + LOGGER.trace("Unannounced join of chat room '" + channelName + + "' completed."); + break; + + default: + break; + } + } + } + + /** + * A chat room listener. + * + * A chat room listener is registered for each chat room that we join. The + * chat room listener updates chat room data and fires events based on IRC + * messages that report state changes for the specified channel. + * + * @author Danny van Heumen + * + */ + private final class ChatRoomListener + extends VariousMessageListenerAdapter + { + /** + * IRC error code for case when user cannot send a message to the + * channel, for example when this channel is moderated and user does not + * have VOICE (+v). + */ + private static final int IRC_ERR_CANNOTSENDTOCHAN = 404; + + /** + * IRC error code for case where user is not joined to that channel. + */ + private static final int IRC_ERR_NOTONCHANNEL = 442; + + /** + * IRCApi instance. + */ + private final IRCApi irc; + + /** + * Chat room for which this listener is working. + */ + private final ChatRoomIrcImpl chatroom; + + /** + * Constructor. Instantiate listener for the provided chat room. + * + * @param irc IRCApi instance + * @param chatroom the chat room + */ + private ChatRoomListener(final IRCApi irc, + final ChatRoomIrcImpl chatroom) + { + if (chatroom == null) + { + throw new IllegalArgumentException("chatroom cannot be null"); + } + this.chatroom = chatroom; + if (irc == null) + { + throw new IllegalArgumentException("irc cannot be null"); + } + this.irc = irc; + } + + /** + * Event in case of topic change. + * + * @param msg topic change message + */ + @Override + public void onTopicChange(final TopicMessage msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + this.chatroom.updateSubject(msg.getTopic().getValue()); + } + + /** + * Event in case of channel mode changes. + * + * @param msg channel mode message + */ + @Override + public void onChannelMode(final ChannelModeMessage msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + processModeMessage(msg); + } + + /** + * Event in case of channel join message. + * + * @param msg channel join message + */ + @Override + public void onChannelJoin(final ChanJoinMessage msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + final String user = msg.getSource().getNick(); + final ChatRoomMemberIrcImpl member = + new ChatRoomMemberIrcImpl(ChannelManager.this.provider, + this.chatroom, user, ChatRoomMemberRole.SILENT_MEMBER); + this.chatroom.fireMemberPresenceEvent(member, null, + ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED, null); + } + + /** + * Event in case of channel part. + * + * @param msg channel part message + */ + @Override + public void onChannelPart(final ChanPartMessage msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + final IRCUser user = msg.getSource(); + if (isMe(user)) + { + leaveChatRoom(); + return; + } + + final String userNick = msg.getSource().getNick(); + final ChatRoomMember member = + this.chatroom.getChatRoomMember(userNick); + if (member != null) + { + // When the account has been disabled, the chat room may return + // null. If that is NOT the case, continue handling. + try + { + this.chatroom.fireMemberPresenceEvent(member, null, + ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT, + msg.getPartMsg()); + } + catch (NullPointerException e) + { + LOGGER.warn( + "This should not have happened. Please report this " + + "as it is a bug.", e); + } + } + } + + /** + * Some of the generic message are relevant to us, so keep an eye on + * general numeric messages. + * + * @param msg IRC server numeric message + */ + public void onServerNumericMessage(final ServerNumericMessage msg) + { + final Integer code = msg.getNumericCode(); + if (code == null) + { + return; + } + final String raw = msg.getText(); + switch (code.intValue()) + { + case IRC_ERR_NOTONCHANNEL: + final String channel = raw.substring(0, raw.indexOf(" ")); + if (isThisChatRoom(channel)) + { + LOGGER + .warn("Just discovered that we are no longer joined to " + + "channel " + + channel + + ". Leaving quietly. (This is most likely due to a" + + " bug in the implementation.)"); + // If for some reason we missed the message that we aren't + // joined (anymore) to this particular chat room, correct + // our problem ASAP. + leaveChatRoom(); + } + break; + + case IRC_ERR_CANNOTSENDTOCHAN: + final String cannotSendChannel = + raw.substring(0, raw.indexOf(" ")); + if (isThisChatRoom(cannotSendChannel)) + { + final MessageIrcImpl message = + new MessageIrcImpl("", "text/plain", "UTF-8", null); + this.chatroom.fireMessageDeliveryFailedEvent( + ChatRoomMessageDeliveryFailedEvent.FORBIDDEN, + "This channel is moderated.", new Date(), message); + } + break; + + default: + break; + } + } + + /** + * Event in case of channel kick. + * + * @param msg channel kick message + */ + @Override + public void onChannelKick(final ChannelKick msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + if (!ChannelManager.this.connectionState.isConnected()) + { + LOGGER.error("Not currently connected to IRC Server. " + + "Aborting message handling."); + return; + } + + final String kickedUser = msg.getKickedNickname(); + final ChatRoomMember kickedMember = + this.chatroom.getChatRoomMember(kickedUser); + final String user = msg.getSource().getNick(); + if (kickedMember != null) + { + ChatRoomMember kicker = this.chatroom.getChatRoomMember(user); + this.chatroom.fireMemberPresenceEvent(kickedMember, kicker, + ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED, + msg.getText()); + } + if (isMe(kickedUser)) + { + LOGGER.debug( + "Local user is kicked. Removing chat room listener."); + this.irc.deleteListener(this); + ChannelManager.this.joined + .remove(this.chatroom.getIdentifier()); + ChannelManager.this.provider.getMUC() + .fireLocalUserPresenceEvent(this.chatroom, + LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED, + msg.getText()); + } + } + + /** + * Event in case of user quit. + * + * @param msg user quit message + */ + @Override + public void onUserQuit(final QuitMessage msg) + { + String user = msg.getSource().getNick(); + if (user == null) + { + return; + } + if (user.equals(ChannelManager.this.connectionState.getNickname())) + { + LOGGER.debug("Local user QUIT message received: removing chat " + + "room listener."); + this.irc.deleteListener(this); + return; + } + final ChatRoomMember member = this.chatroom.getChatRoomMember(user); + if (member != null) + { + this.chatroom.fireMemberPresenceEvent(member, null, + ChatRoomMemberPresenceChangeEvent.MEMBER_QUIT, + msg.getQuitMsg()); + } + } + + /** + * Event in case of nick change. + * + * @param msg nick change message + */ + @Override + public void onNickChange(final NickMessage msg) + { + if (msg == null) + { + return; + } + + final String oldNick = msg.getSource().getNick(); + final String newNick = msg.getNewNick(); + + final ChatRoomMemberIrcImpl member = + (ChatRoomMemberIrcImpl) this.chatroom + .getChatRoomMember(oldNick); + if (member != null) + { + member.setName(newNick); + this.chatroom.updateChatRoomMemberName(oldNick); + ChatRoomMemberPropertyChangeEvent evt = + new ChatRoomMemberPropertyChangeEvent(member, + this.chatroom, + ChatRoomMemberPropertyChangeEvent.MEMBER_NICKNAME, + oldNick, newNick); + this.chatroom.fireMemberPropertyChangeEvent(evt); + } + } + + /** + * Event in case of channel message arrival. + * + * @param msg channel message + */ + @Override + public void onChannelMessage(final ChannelPrivMsg msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + final MessageIrcImpl message = + MessageIrcImpl.newMessageFromIRC(msg.getText()); + final ChatRoomMemberIrcImpl member = + new ChatRoomMemberIrcImpl(ChannelManager.this.provider, + this.chatroom, msg.getSource().getNick(), + ChatRoomMemberRole.MEMBER); + this.chatroom.fireMessageReceivedEvent(message, member, new Date(), + ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); + } + + /** + * Event in case of channel action message arrival. + * + * @param msg channel action message + */ + @Override + public void onChannelAction(final ChannelActionMsg msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + String userNick = msg.getSource().getNick(); + ChatRoomMemberIrcImpl member = + new ChatRoomMemberIrcImpl(ChannelManager.this.provider, + this.chatroom, userNick, ChatRoomMemberRole.MEMBER); + MessageIrcImpl message = + MessageIrcImpl.newActionFromIRC(member, msg.getText()); + this.chatroom.fireMessageReceivedEvent(message, member, new Date(), + ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); + } + + /** + * Event in case of channel notice message arrival. + * + * @param msg channel notice message + */ + @Override + public void onChannelNotice(final ChannelNotice msg) + { + if (!isThisChatRoom(msg.getChannelName())) + { + return; + } + + final String userNick = msg.getSource().getNick(); + final ChatRoomMemberIrcImpl member = + new ChatRoomMemberIrcImpl(ChannelManager.this.provider, + this.chatroom, userNick, ChatRoomMemberRole.MEMBER); + final MessageIrcImpl message = + MessageIrcImpl.newNoticeFromIRC(member, msg.getText()); + this.chatroom.fireMessageReceivedEvent(message, member, new Date(), + ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); + } + + /** + * Leave this chat room. + */ + private void leaveChatRoom() + { + this.irc.deleteListener(this); + ChannelManager.this.joined.remove(this.chatroom.getIdentifier()); + LOGGER.debug("Leaving chat room " + this.chatroom.getIdentifier() + + ". Chat room listener removed."); + ChannelManager.this.provider.getMUC().fireLocalUserPresenceEvent( + this.chatroom, + LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_LEFT, null); + } + + /** + * Process mode changes. + * + * @param msg raw mode message + */ + private void processModeMessage(final ChannelModeMessage msg) + { + final ChatRoomMemberIrcImpl source = extractChatRoomMember(msg); + final ModeParser parser = new ModeParser(msg.getModeStr()); + for (ModeEntry mode : parser.getModes()) + { + switch (mode.getMode()) + { + case OWNER: + case OPERATOR: + case HALFOP: + case VOICE: + processRoleChange(source, mode); + break; + case LIMIT: + processLimitChange(source, mode); + break; + case BAN: + processBanChange(source, mode); + break; + case UNKNOWN: + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Unknown mode: " + + (mode.isAdded() ? "+" : "-") + + mode.getParams()[0] + ". Original mode string: '" + + msg.getModeStr() + "'"); + } + break; + default: + if (LOGGER.isInfoEnabled()) + { + LOGGER.info("Unsupported mode '" + + (mode.isAdded() ? "+" : "-") + mode.getMode() + + "' (from modestring '" + msg.getModeStr() + "')"); + } + break; + } + } + } + + /** + * Process changes for ban patterns. + * + * @param sourceMember the originating member + * @param mode the ban mode change + */ + private void processBanChange(final ChatRoomMemberIrcImpl sourceMember, + final ModeEntry mode) + { + final MessageIrcImpl banMessage = + new MessageIrcImpl( + "channel ban mask was " + + (mode.isAdded() ? "added" : "removed") + + ": " + + mode.getParams()[0] + + " by " + + (sourceMember.getContactAddress().length() == 0 + ? "server" + : sourceMember.getContactAddress()), + MessageIrcImpl.DEFAULT_MIME_TYPE, + MessageIrcImpl.DEFAULT_MIME_ENCODING, null); + this.chatroom.fireMessageReceivedEvent(banMessage, sourceMember, + new Date(), + ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); + } + + /** + * Process mode changes resulting in role manipulation. + * + * @param sourceMember the originating member + * @param mode the mode change + */ + private void processRoleChange( + final ChatRoomMemberIrcImpl sourceMember, final ModeEntry mode) + { + final String targetNick = mode.getParams()[0]; + final ChatRoomMemberIrcImpl targetMember = + (ChatRoomMemberIrcImpl) this.chatroom + .getChatRoomMember(targetNick); + final ChatRoomMemberRole originalRole = targetMember.getRole(); + if (mode.isAdded()) + { + targetMember.addRole(mode.getMode().getRole()); + } + else + { + targetMember.removeRole(mode.getMode().getRole()); + } + final ChatRoomMemberRole newRole = targetMember.getRole(); + if (newRole != originalRole) + { + // Mode change actually caused a role change. + final ChatRoomLocalUserRoleChangeEvent event = + new ChatRoomLocalUserRoleChangeEvent(this.chatroom, + originalRole, newRole, false); + if (isMe(targetMember.getContactAddress())) + { + this.chatroom.fireLocalUserRoleChangedEvent(event); + } + else + { + this.chatroom.fireMemberRoleEvent(targetMember, + newRole); + } + } + else + { + // Mode change did not cause an immediate role change. + // Display a system message for the mode change. + final String text = + sourceMember.getName() + + (mode.isAdded() ? " gives " + + mode.getMode().name().toLowerCase() + + " to " : " removes " + + mode.getMode().name().toLowerCase() + + " from ") + targetMember.getName(); + final MessageIrcImpl message = + new MessageIrcImpl(text, + MessageIrcImpl.DEFAULT_MIME_TYPE, + MessageIrcImpl.DEFAULT_MIME_ENCODING, null); + this.chatroom + .fireMessageReceivedEvent( + message, + sourceMember, + new Date(), + ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); + } + } + + /** + * Process mode change that represents a channel limit modification. + * + * @param sourceMember the originating member + * @param mode the limit mode change + */ + private void processLimitChange( + final ChatRoomMemberIrcImpl sourceMember, final ModeEntry mode) + { + final MessageIrcImpl limitMessage; + if (mode.isAdded()) + { + try + { + limitMessage = + new MessageIrcImpl( + "channel limit set to " + + Integer.parseInt(mode.getParams()[0]) + + " by " + + (sourceMember.getContactAddress() + .length() == 0 + ? "server" + : sourceMember.getContactAddress()), + "text/plain", "UTF-8", null); + } + catch (NumberFormatException e) + { + LOGGER.warn("server sent incorrect limit: " + + "limit is not a number", e); + return; + } + } + else + { + // TODO "server" is now easily fakeable if someone + // calls himself server. There should be some other way + // to represent the server if a message comes from + // something other than a normal chat room member. + limitMessage = + new MessageIrcImpl( + "channel limit removed by " + + (sourceMember.getContactAddress().length() == 0 + ? "server" + : sourceMember.getContactAddress()), + "text/plain", "UTF-8", null); + } + this.chatroom.fireMessageReceivedEvent(limitMessage, sourceMember, + new Date(), + ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); + } + + /** + * Extract chat room member identifier from message. + * + * @param msg raw mode message + * @return returns member instance + */ + private ChatRoomMemberIrcImpl extractChatRoomMember( + final ChannelModeMessage msg) + { + ChatRoomMemberIrcImpl member; + ISource source = msg.getSource(); + if (source instanceof IRCServer) + { + // TODO Created chat room member with creepy empty contact ID. + // Interacting with this contact might screw up other sections + // of code which is not good. Is there a better way to represent + // an IRC server as a chat room member? + member = + new ChatRoomMemberIrcImpl(ChannelManager.this.provider, + this.chatroom, "", ChatRoomMemberRole.ADMINISTRATOR); + } + else if (source instanceof IRCUser) + { + String nick = ((IRCUser) source).getNick(); + member = + (ChatRoomMemberIrcImpl) this.chatroom + .getChatRoomMember(nick); + } + else + { + throw new IllegalArgumentException("Unknown source type: " + + source.getClass().getName()); + } + return member; + } + + /** + * Test whether this listener corresponds to the chat room. + * + * @param chatRoomName chat room name + * @return returns true if this listener applies, false otherwise + */ + private boolean isThisChatRoom(final String chatRoomName) + { + return this.chatroom.getIdentifier().equalsIgnoreCase(chatRoomName); + } + + /** + * Test whether the source user is this user. + * + * @param user the source user + * @return returns true if this use, or false otherwise + */ + private boolean isMe(final IRCUser user) + { + return isMe(user.getNick()); + } + + /** + * Test whether the user nick is this user. + * + * @param name nick of the user + * @return returns true if so, false otherwise + */ + private boolean isMe(final String name) + { + final String userNick = + ChannelManager.this.connectionState.getNickname(); + if (userNick == null) + { + return false; + } + return userNick.equals(name); + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java b/src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java index 2e324aa50..c74f8fbe4 100644 --- a/src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImpl.java @@ -161,7 +161,8 @@ public ChatRoomIrcImpl(final String chatRoomName, final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); this.chatRoomName = - verifyName(connection.getChannelTypes(), chatRoomName); + verifyName(connection.getChannelManager().getChannelTypes(), + chatRoomName); this.isSystem = isSystem; } @@ -317,13 +318,13 @@ public void join() throws OperationFailedException OperationFailedException.NETWORK_FAILURE); } - if (connection.isJoined(this)) + if (connection.getChannelManager().isJoined(this)) { throw new OperationFailedException("Channel is already joined.", OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS); } - connection.join(this); + connection.getChannelManager().join(this); } /** @@ -339,7 +340,7 @@ public void join(final byte[] password) throws OperationFailedException { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.join(this, password.toString()); + connection.getChannelManager().join(this, password.toString()); } /** @@ -394,7 +395,7 @@ public boolean isJoined() { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - return connection.isJoined(this); + return connection.getChannelManager().isJoined(this); } /** @@ -408,7 +409,7 @@ public void leave() { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.leave(this); + connection.getChannelManager().leave(this); this.chatRoomMembers.clear(); } @@ -438,7 +439,8 @@ public void banParticipant(final ChatRoomMember chatRoomMember, { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.banParticipant(this, chatRoomMember, reason); + connection.getChannelManager().banParticipant(this, chatRoomMember, + reason); } /** @@ -454,7 +456,8 @@ public void kickParticipant(final ChatRoomMember chatRoomMember, { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.kickParticipant(this, chatRoomMember, reason); + connection.getChannelManager().kickParticipant(this, chatRoomMember, + reason); } /** @@ -653,7 +656,7 @@ public void setSubject(final String subject) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.setSubject(this, subject); + connection.getChannelManager().setSubject(this, subject); } catch (RuntimeException e) { @@ -817,7 +820,7 @@ public void invite(final String userAddress, final String reason) // some-one. final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.invite(userAddress, this); + connection.getChannelManager().invite(userAddress, this); } /** @@ -1343,7 +1346,7 @@ public void grantAdmin(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.grant(this, address, Mode.OPERATOR); + connection.getChannelManager().grant(this, address, Mode.OPERATOR); } /** @@ -1356,7 +1359,7 @@ public void grantMembership(final String address) // TODO currently Voice == Membership. final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.grant(this, address, Mode.VOICE); + connection.getChannelManager().grant(this, address, Mode.VOICE); } /** @@ -1368,7 +1371,7 @@ public void grantModerator(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.grant(this, address, Mode.HALFOP); + connection.getChannelManager().grant(this, address, Mode.HALFOP); } /** @@ -1380,7 +1383,7 @@ public void grantOwnership(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.grant(this, address, Mode.OWNER); + connection.getChannelManager().grant(this, address, Mode.OWNER); } /** @@ -1393,7 +1396,7 @@ public void grantVoice(final String address) // TODO currently Voice == Membership. final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.grant(this, address, Mode.VOICE); + connection.getChannelManager().grant(this, address, Mode.VOICE); } /** @@ -1405,7 +1408,7 @@ public void revokeAdmin(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.revoke(this, address, Mode.OPERATOR); + connection.getChannelManager().revoke(this, address, Mode.OPERATOR); } /** @@ -1420,7 +1423,7 @@ public void revokeMembership(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.revoke(this, address, Mode.VOICE); + connection.getChannelManager().revoke(this, address, Mode.VOICE); } /** @@ -1433,7 +1436,7 @@ public void revokeModerator(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.revoke(this, address, Mode.HALFOP); + connection.getChannelManager().revoke(this, address, Mode.HALFOP); } /** @@ -1446,7 +1449,7 @@ public void revokeOwnership(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.revoke(this, address, Mode.OWNER); + connection.getChannelManager().revoke(this, address, Mode.OWNER); } /** @@ -1458,7 +1461,7 @@ public void revokeVoice(final String address) { final IrcConnection connection = this.parentProvider.getIrcStack().getConnection(); - connection.revoke(this, address, Mode.VOICE); + connection.getChannelManager().revoke(this, address, Mode.VOICE); } /** diff --git a/src/net/java/sip/communicator/impl/protocol/irc/IrcConnection.java b/src/net/java/sip/communicator/impl/protocol/irc/IrcConnection.java index 118aef911..d11324c9d 100644 --- a/src/net/java/sip/communicator/impl/protocol/irc/IrcConnection.java +++ b/src/net/java/sip/communicator/impl/protocol/irc/IrcConnection.java @@ -9,7 +9,6 @@ import java.io.*; import java.util.*; -import net.java.sip.communicator.impl.protocol.irc.ModeParser.ModeEntry; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; @@ -17,7 +16,6 @@ import com.ircclouds.irc.api.*; import com.ircclouds.irc.api.domain.*; import com.ircclouds.irc.api.domain.messages.*; -import com.ircclouds.irc.api.domain.messages.interfaces.*; import com.ircclouds.irc.api.listeners.*; import com.ircclouds.irc.api.state.*; @@ -114,17 +112,9 @@ public class IrcConnection private final IdentityManager identity; /** - * Container for joined channels. - * - * There are two different cases: - * - *
-     * - null value: joining is initiated but still in progress.
-     * - non-null value: joining is finished, chat room instance is available.
-     * 
+ * The channel manager instance. */ - private final Map joined = Collections - .synchronizedMap(new HashMap()); + private final ChannelManager channel; /** * Constructor. @@ -165,6 +155,11 @@ public IrcConnection(final ProtocolProviderServiceIrcImpl provider, // instantiate identity manager for the connection this.identity = new IdentityManager(this.irc, this.connectionState); + // instantiate channel manager for the connection + this.channel = + new ChannelManager(this.irc, this.connectionState, this.provider, + this.identity); + // TODO Read IRC network capabilities based on RPL_ISUPPORT // (005) replies if available. This information should be // available in irc-api if possible. @@ -314,375 +309,13 @@ public IdentityManager getIdentityManager() } /** - * Get a set of channel type indicators. - * - * @return returns set of channel type indicators. - */ - public Set getChannelTypes() - { - if (!isConnected()) - { - throw new IllegalStateException("not connected to IRC server"); - } - return this.connectionState.getServerOptions().getChanTypes(); - } - - /** - * Set the subject of the specified chat room. - * - * @param chatroom The chat room for which to set the subject. - * @param subject The subject. - */ - public void setSubject(final ChatRoomIrcImpl chatroom, final String subject) - { - if (!isConnected()) - { - throw new IllegalStateException( - "Please connect to an IRC server first."); - } - if (chatroom == null) - { - throw new IllegalArgumentException("Cannot have a null chatroom"); - } - LOGGER.trace("Setting chat room topic to '" + subject + "'"); - synchronized (this.irc) - { - this.irc.changeTopic(chatroom.getIdentifier(), subject == null ? "" - : subject); - } - } - - /** - * Check whether the user has joined a particular chat room. - * - * @param chatroom Chat room to check for. - * @return Returns true in case the user is already joined, or false if the - * user has not joined. - */ - public boolean isJoined(final ChatRoomIrcImpl chatroom) - { - return this.joined.get(chatroom.getIdentifier()) != null; - } - - /** - * Join a particular chat room. - * - * @param chatroom Chat room to join. - * @throws OperationFailedException failed to join the chat room - */ - public void join(final ChatRoomIrcImpl chatroom) - throws OperationFailedException - { - join(chatroom, ""); - } - - /** - * Join a particular chat room. - * - * Issue a join channel IRC operation and wait for the join operation to - * complete (either successfully or failing). - * - * @param chatroom The chatroom to join. - * @param password Optionally, a password that may be required for some - * channels. - * @throws OperationFailedException failed to join the chat room - */ - public void join(final ChatRoomIrcImpl chatroom, final String password) - throws OperationFailedException - { - if (!isConnected()) - { - throw new IllegalStateException( - "Please connect to an IRC server first"); - } - if (chatroom == null) - { - throw new IllegalArgumentException("chatroom cannot be null"); - } - if (password == null) - { - throw new IllegalArgumentException("password cannot be null"); - } - - final String chatRoomId = chatroom.getIdentifier(); - if (this.joined.containsKey(chatRoomId)) - { - // If we already joined this particular chatroom, no further action - // is required. - return; - } - - LOGGER.trace("Start joining channel " + chatRoomId); - final Result joinSignal = - new Result(); - synchronized (joinSignal) - { - LOGGER.trace("Issue join channel command to IRC library and wait " - + "for join operation to complete (un)successfully."); - - this.joined.put(chatRoomId, null); - synchronized (this.irc) - { - // TODO Refactor this ridiculous nesting of functions and - // classes. - this.irc.joinChannel(chatRoomId, password, - new Callback() - { - - @Override - public void onSuccess(final IRCChannel channel) - { - if (LOGGER.isTraceEnabled()) - { - LOGGER - .trace("Started callback for successful " - + "join of channel '" - + chatroom.getIdentifier() + "'."); - } - boolean isRequestedChatRoom = - channel.getName().equalsIgnoreCase(chatRoomId); - synchronized (joinSignal) - { - if (!isRequestedChatRoom) - { - // We joined another chat room than the one - // we requested initially. - if (LOGGER.isTraceEnabled()) - { - LOGGER.trace("Callback for successful " - + "join finished prematurely " - + "since we got forwarded from " - + "'" - + chatRoomId - + "' to '" - + channel.getName() - + "'. Joining of forwarded channel " - + "gets handled by Server Listener " - + "since that channel was not " - + "announced."); - } - // Remove original chat room id from - // joined-list since we aren't actually - // attempting to join this room anymore. - IrcConnection.this.joined - .remove(chatRoomId); - IrcConnection.this.provider - .getMUC() - .fireLocalUserPresenceEvent( - chatroom, - LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_JOIN_FAILED, - "We got forwarded to channel '" - + channel.getName() + "'."); - // Notify waiting threads of finished - // execution. - joinSignal.setDone(); - joinSignal.notifyAll(); - // The channel that we were forwarded to - // will be handled by the Server Listener, - // since the channel join was unannounced, - // and we are done here. - return; - } - - try - { - IrcConnection.this.joined.put(chatRoomId, - chatroom); - IrcConnection.this.irc - .addListener(new ChatRoomListener( - IrcConnection.this.irc, chatroom)); - prepareChatRoom(chatroom, channel); - } - finally - { - // In any case, issue the local user - // presence, since the irc library notified - // us of a successful join. We should wait - // as long as possible though. First we need - // to fill the list of chat room members and - // other chat room properties. - IrcConnection.this.provider - .getMUC() - .fireLocalUserPresenceEvent( - chatroom, - LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_JOINED, - null); - if (LOGGER.isTraceEnabled()) - { - LOGGER - .trace("Finished successful join " - + "callback for channel '" - + chatRoomId - + "'. Waking up original " - + "thread."); - } - // Notify waiting threads of finished - // execution. - joinSignal.setDone(); - joinSignal.notifyAll(); - } - } - } - - @Override - public void onFailure(final Exception e) - { - LOGGER - .trace("Started callback for failed attempt to " - + "join channel '" + chatRoomId + "'."); - synchronized (joinSignal) - { - try - { - IrcConnection.this.joined - .remove(chatRoomId); - IrcConnection.this.provider - .getMUC() - .fireLocalUserPresenceEvent( - chatroom, - LocalUserChatRoomPresenceChangeEvent - .LOCAL_USER_JOIN_FAILED, - e.getMessage()); - } - finally - { - if (LOGGER.isTraceEnabled()) - { - LOGGER - .trace("Finished callback for " - + "failed attempt to join " - + "channel '" - + chatRoomId - + "'. Waking up original " - + "thread."); - } - // Notify waiting threads of finished - // execution - joinSignal.setDone(e); - joinSignal.notifyAll(); - } - } - } - }); - } - - try - { - while (!joinSignal.isDone()) - { - LOGGER.trace("Waiting for channel join message ..."); - // Wait until async channel join operation has finished. - joinSignal.wait(); - } - - LOGGER - .trace("Finished waiting for join operation for channel '" - + chatroom.getIdentifier() + "' to complete."); - // TODO How to handle 480 (+j): Channel throttle exceeded? - } - catch (InterruptedException e) - { - LOGGER.error("Wait for join operation was interrupted.", e); - throw new OperationFailedException(e.getMessage(), - OperationFailedException.INTERNAL_ERROR, e); - } - } - } - - /** - * Part from a joined chat room. - * - * @param chatroom The chat room to part from. - */ - public void leave(final ChatRoomIrcImpl chatroom) - { - LOGGER.trace("Leaving chat room '" + chatroom.getIdentifier() + "'."); - leave(chatroom.getIdentifier()); - } - - /** - * Part from a joined chat room. - * - * @param chatRoomName The chat room to part from. - */ - private void leave(final String chatRoomName) - { - if (!isConnected()) - { - throw new IllegalStateException("Not connected to an IRC server."); - } - - try - { - synchronized (this.irc) - { - this.irc.leaveChannel(chatRoomName); - } - } - catch (ApiException e) - { - LOGGER.warn("exception occurred while leaving channel", e); - } - } - - /** - * Ban chat room member. - * - * @param chatroom chat room to ban from - * @param member member to ban - * @param reason reason for banning - * @throws OperationFailedException throws operation failed in case of - * trouble. - */ - public void banParticipant(final ChatRoomIrcImpl chatroom, - final ChatRoomMember member, final String reason) - throws OperationFailedException - { - // TODO Implement banParticipant. - throw new OperationFailedException("Not implemented yet.", - OperationFailedException.NOT_SUPPORTED_OPERATION); - } - - /** - * Kick channel member. + * Get the channel manager instance. * - * @param chatroom channel to kick from - * @param member member to kick - * @param reason kick message to deliver + * @return returns the channel manager instance */ - public void kickParticipant(final ChatRoomIrcImpl chatroom, - final ChatRoomMember member, final String reason) + public ChannelManager getChannelManager() { - if (!isConnected()) - { - return; - } - synchronized (this.irc) - { - this.irc.kick(chatroom.getIdentifier(), member.getContactAddress(), - reason); - } - } - - /** - * Issue invite command to IRC server. - * - * @param memberId member to invite - * @param chatroom channel to invite to - */ - public void invite(final String memberId, final ChatRoomIrcImpl chatroom) - { - if (!isConnected()) - { - throw new IllegalStateException("Not connected to an IRC server."); - } - synchronized (this.irc) - { - this.irc.rawMessage("INVITE " + memberId + " " - + chatroom.getIdentifier()); - } + return this.channel; } /** @@ -828,109 +461,6 @@ public void message(final Contact contact, final Message message) } } - /** - * Grant user permissions to specified user. - * - * @param chatRoom chat room to grant permissions for - * @param userAddress user to grant permissions to - * @param mode mode to grant - */ - public void grant(final ChatRoomIrcImpl chatRoom, final String userAddress, - final Mode mode) - { - if (!isConnected()) - { - throw new IllegalStateException("Not connected to an IRC server."); - } - if (mode.getRole() == null) - { - throw new IllegalArgumentException( - "This mode does not modify user permissions."); - } - synchronized (this.irc) - { - this.irc.changeMode(chatRoom.getIdentifier() + " +" - + mode.getSymbol() + " " + userAddress); - } - } - - /** - * Revoke user permissions of chat room for user. - * - * @param chatRoom chat room - * @param userAddress user - * @param mode mode - */ - public void revoke(final ChatRoomIrcImpl chatRoom, - final String userAddress, final Mode mode) - { - if (!isConnected()) - { - throw new IllegalStateException("Not connected to an IRC server."); - } - if (mode.getRole() == null) - { - throw new IllegalArgumentException( - "This mode does not modify user permissions."); - } - synchronized (this.irc) - { - this.irc.changeMode(chatRoom.getIdentifier() + " -" - + mode.getSymbol() + " " + userAddress); - } - } - - /** - * Prepare a chat room for initial opening. - * - * @param channel The IRC channel which is the source of data. - * @param chatRoom The chatroom to prepare. - */ - private void prepareChatRoom(final ChatRoomIrcImpl chatRoom, - final IRCChannel channel) - { - final IRCTopic topic = channel.getTopic(); - chatRoom.updateSubject(topic.getValue()); - - for (IRCUser user : channel.getUsers()) - { - ChatRoomMemberIrcImpl member = - new ChatRoomMemberIrcImpl(this.provider, chatRoom, - user.getNick(), ChatRoomMemberRole.SILENT_MEMBER); - ChatRoomMemberRole role; - for (IRCUserStatus status : channel.getStatusesForUser(user)) - { - role = convertMemberMode(status.getChanModeType().charValue()); - member.addRole(role); - } - chatRoom.addChatRoomMember(member.getContactAddress(), member); - if (this.identity.getNick().equals(user.getNick())) - { - chatRoom.setLocalUser(member); - if (member.getRole() != ChatRoomMemberRole.SILENT_MEMBER) - { - ChatRoomLocalUserRoleChangeEvent event = - new ChatRoomLocalUserRoleChangeEvent(chatRoom, - ChatRoomMemberRole.SILENT_MEMBER, member.getRole(), - true); - chatRoom.fireLocalUserRoleChangedEvent(event); - } - } - } - } - - /** - * Convert a member mode character to a ChatRoomMemberRole instance. - * - * @param modeSymbol The member mode character. - * @return Return the instance of ChatRoomMemberRole corresponding to the - * member mode character. - */ - private static ChatRoomMemberRole convertMemberMode(final char modeSymbol) - { - return Mode.bySymbol(modeSymbol).getRole(); - } - /** * Calculate maximum message size that can be transmitted. * @@ -960,12 +490,6 @@ private final class ServerListener */ private static final int RPL_AWAY = 301; - /** - * IRC reply code for end of list. - */ - private static final int RPL_LISTEND = - IRCServerNumerics.CHANNEL_NICKS_END_OF_LIST; - /** * IRC error code for case of non-existing nick or channel name. */ @@ -1034,62 +558,6 @@ public void onServerNumericMessage(final ServerNumericMessage msg) switch (code.intValue()) { - case RPL_LISTEND: - // CHANNEL_NICKS_END_OF_LIST indicates the end of a nick list as - // you will receive when joining a channel. This is used as the - // indicator that we have joined a channel. Now we have to - // determine whether or not we already know about this - // particular join attempt. If not, we continue to inform Jitsi - // and to create a listener for this new chat room. - final String text = msg.getText(); - final String channelName = text.substring(0, text.indexOf(' ')); - final ChatRoomIrcImpl chatRoom; - final IRCChannel channel; - synchronized (IrcConnection.this.joined) - { - // Synchronize the section that checks then adds a chat - // room. This way we can be sure that there are no 2 - // simultaneous creation events. - if (IrcConnection.this.joined.containsKey(channelName)) - { - LOGGER.trace("Chat room '" + channelName - + "' join event was announced or already " - + "finished. Stop handling this event."); - break; - } - // We aren't currently attempting to join, so this join is - // unannounced. - LOGGER.trace("Starting unannounced join of chat room '" - + channelName); - // Assuming that at the time that NICKS_END_OF_LIST is - // propagated, the channel join event has been completely - // handled by IRCApi. - channel = - IrcConnection.this.connectionState - .getChannelByName(channelName); - chatRoom = new ChatRoomIrcImpl( - channelName, IrcConnection.this.provider); - IrcConnection.this.joined.put(channelName, chatRoom); - } - this.irc.addListener(new ChatRoomListener(this.irc, chatRoom)); - try - { - IrcConnection.this.provider.getMUC().openChatRoomWindow( - chatRoom); - } - catch (NullPointerException e) - { - LOGGER.error("failed to open chat room window", e); - } - IrcConnection.this.prepareChatRoom(chatRoom, channel); - IrcConnection.this.provider.getMUC().fireLocalUserPresenceEvent( - chatRoom, - LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED, - null); - LOGGER.trace("Unannounced join of chat room '" + channelName - + "' completed."); - break; - case ERR_NO_SUCH_NICK_CHANNEL: // TODO Check if target is Contact, then update contact presence // status to off-line since the nick apparently does not exist @@ -1276,658 +744,4 @@ public void onUserQuit(final QuitMessage msg) } } } - - /** - * A chat room listener. - * - * A chat room listener is registered for each chat room that we join. The - * chat room listener updates chat room data and fires events based on IRC - * messages that report state changes for the specified channel. - * - * @author Danny van Heumen - * - */ - private final class ChatRoomListener - extends VariousMessageListenerAdapter - { - /** - * IRC error code for case when user cannot send a message to the - * channel, for example when this channel is moderated and user does not - * have VOICE (+v). - */ - private static final int IRC_ERR_CANNOTSENDTOCHAN = 404; - - /** - * IRC error code for case where user is not joined to that channel. - */ - private static final int IRC_ERR_NOTONCHANNEL = 442; - - /** - * IRCApi instance. - */ - private final IRCApi irc; - - /** - * Chat room for which this listener is working. - */ - private final ChatRoomIrcImpl chatroom; - - /** - * Constructor. Instantiate listener for the provided chat room. - * - * @param irc IRCApi instance - * @param chatroom the chat room - */ - private ChatRoomListener(final IRCApi irc, - final ChatRoomIrcImpl chatroom) - { - if (chatroom == null) - { - throw new IllegalArgumentException("chatroom cannot be null"); - } - this.chatroom = chatroom; - if (irc == null) - { - throw new IllegalArgumentException("irc cannot be null"); - } - this.irc = irc; - } - - /** - * Event in case of topic change. - * - * @param msg topic change message - */ - @Override - public void onTopicChange(final TopicMessage msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - // FIXME Topic change event report message interprets HTML chars in - // channel name. - this.chatroom.updateSubject(msg.getTopic().getValue()); - } - - /** - * Event in case of channel mode changes. - * - * @param msg channel mode message - */ - @Override - public void onChannelMode(final ChannelModeMessage msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - processModeMessage(msg); - } - - /** - * Event in case of channel join message. - * - * @param msg channel join message - */ - @Override - public void onChannelJoin(final ChanJoinMessage msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - final String user = msg.getSource().getNick(); - final ChatRoomMemberIrcImpl member = - new ChatRoomMemberIrcImpl(IrcConnection.this.provider, - this.chatroom, user, ChatRoomMemberRole.SILENT_MEMBER); - this.chatroom.fireMemberPresenceEvent(member, null, - ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED, null); - } - - /** - * Event in case of channel part. - * - * @param msg channel part message - */ - @Override - public void onChannelPart(final ChanPartMessage msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - final IRCUser user = msg.getSource(); - if (isMe(user)) - { - leaveChatRoom(); - return; - } - - final String userNick = msg.getSource().getNick(); - final ChatRoomMember member = - this.chatroom.getChatRoomMember(userNick); - if (member != null) - { - // When the account has been disabled, the chat room may return - // null. If that is NOT the case, continue handling. - try - { - this.chatroom.fireMemberPresenceEvent(member, null, - ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT, - msg.getPartMsg()); - } - catch (NullPointerException e) - { - LOGGER.warn( - "This should not have happened. Please report this " - + "as it is a bug.", e); - } - } - } - - /** - * Some of the generic message are relevant to us, so keep an eye on - * general numeric messages. - * - * @param msg IRC server numeric message - */ - public void onServerNumericMessage(final ServerNumericMessage msg) - { - final Integer code = msg.getNumericCode(); - if (code == null) - { - return; - } - final String raw = msg.getText(); - switch (code.intValue()) - { - case IRC_ERR_NOTONCHANNEL: - final String channel = raw.substring(0, raw.indexOf(" ")); - if (isThisChatRoom(channel)) - { - LOGGER - .warn("Just discovered that we are no longer joined to " - + "channel " - + channel - + ". Leaving quietly. (This is most likely due to a" - + " bug in the implementation.)"); - // If for some reason we missed the message that we aren't - // joined (anymore) to this particular chat room, correct - // our problem ASAP. - leaveChatRoom(); - } - break; - - case IRC_ERR_CANNOTSENDTOCHAN: - final String cannotSendChannel = - raw.substring(0, raw.indexOf(" ")); - if (isThisChatRoom(cannotSendChannel)) - { - final MessageIrcImpl message = - new MessageIrcImpl("", "text/plain", "UTF-8", null); - this.chatroom.fireMessageDeliveryFailedEvent( - ChatRoomMessageDeliveryFailedEvent.FORBIDDEN, - "This channel is moderated.", new Date(), message); - } - break; - - default: - break; - } - } - - /** - * Event in case of channel kick. - * - * @param msg channel kick message - */ - @Override - public void onChannelKick(final ChannelKick msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - if (!IrcConnection.this.isConnected()) - { - LOGGER.error("Not currently connected to IRC Server. " - + "Aborting message handling."); - return; - } - - final String kickedUser = msg.getKickedNickname(); - final ChatRoomMember kickedMember = - this.chatroom.getChatRoomMember(kickedUser); - final String user = msg.getSource().getNick(); - if (kickedMember != null) - { - ChatRoomMember kicker = this.chatroom.getChatRoomMember(user); - this.chatroom.fireMemberPresenceEvent(kickedMember, kicker, - ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED, - msg.getText()); - } - if (isMe(kickedUser)) - { - LOGGER.debug( - "Local user is kicked. Removing chat room listener."); - this.irc.deleteListener(this); - IrcConnection.this.joined.remove(this.chatroom.getIdentifier()); - IrcConnection.this.provider.getMUC().fireLocalUserPresenceEvent( - this.chatroom, - LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED, - msg.getText()); - } - } - - /** - * Event in case of user quit. - * - * @param msg user quit message - */ - @Override - public void onUserQuit(final QuitMessage msg) - { - String user = msg.getSource().getNick(); - if (user == null) - { - return; - } - if (user.equals(IrcConnection.this.connectionState.getNickname())) - { - LOGGER.debug("Local user QUIT message received: removing chat " - + "room listener."); - this.irc.deleteListener(this); - return; - } - final ChatRoomMember member = this.chatroom.getChatRoomMember(user); - if (member != null) - { - this.chatroom.fireMemberPresenceEvent(member, null, - ChatRoomMemberPresenceChangeEvent.MEMBER_QUIT, - msg.getQuitMsg()); - } - } - - /** - * Event in case of nick change. - * - * @param msg nick change message - */ - @Override - public void onNickChange(final NickMessage msg) - { - if (msg == null) - { - return; - } - - final String oldNick = msg.getSource().getNick(); - final String newNick = msg.getNewNick(); - - final ChatRoomMemberIrcImpl member = - (ChatRoomMemberIrcImpl) this.chatroom - .getChatRoomMember(oldNick); - if (member != null) - { - member.setName(newNick); - this.chatroom.updateChatRoomMemberName(oldNick); - ChatRoomMemberPropertyChangeEvent evt = - new ChatRoomMemberPropertyChangeEvent(member, - this.chatroom, - ChatRoomMemberPropertyChangeEvent.MEMBER_NICKNAME, - oldNick, newNick); - this.chatroom.fireMemberPropertyChangeEvent(evt); - } - } - - /** - * Event in case of channel message arrival. - * - * @param msg channel message - */ - @Override - public void onChannelMessage(final ChannelPrivMsg msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - final MessageIrcImpl message = - MessageIrcImpl.newMessageFromIRC(msg.getText()); - final ChatRoomMemberIrcImpl member = - new ChatRoomMemberIrcImpl(IrcConnection.this.provider, - this.chatroom, msg.getSource().getNick(), - ChatRoomMemberRole.MEMBER); - this.chatroom.fireMessageReceivedEvent(message, member, new Date(), - ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); - } - - /** - * Event in case of channel action message arrival. - * - * @param msg channel action message - */ - @Override - public void onChannelAction(final ChannelActionMsg msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - String userNick = msg.getSource().getNick(); - ChatRoomMemberIrcImpl member = - new ChatRoomMemberIrcImpl(IrcConnection.this.provider, - this.chatroom, userNick, ChatRoomMemberRole.MEMBER); - MessageIrcImpl message = - MessageIrcImpl.newActionFromIRC(member, msg.getText()); - this.chatroom.fireMessageReceivedEvent(message, member, new Date(), - ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); - } - - /** - * Event in case of channel notice message arrival. - * - * @param msg channel notice message - */ - @Override - public void onChannelNotice(final ChannelNotice msg) - { - if (!isThisChatRoom(msg.getChannelName())) - { - return; - } - - final String userNick = msg.getSource().getNick(); - final ChatRoomMemberIrcImpl member = - new ChatRoomMemberIrcImpl(IrcConnection.this.provider, - this.chatroom, userNick, ChatRoomMemberRole.MEMBER); - final MessageIrcImpl message = - MessageIrcImpl.newNoticeFromIRC(member, msg.getText()); - this.chatroom.fireMessageReceivedEvent(message, member, new Date(), - ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED); - } - - /** - * Leave this chat room. - */ - private void leaveChatRoom() - { - this.irc.deleteListener(this); - IrcConnection.this.joined.remove(this.chatroom.getIdentifier()); - LOGGER.debug("Leaving chat room " + this.chatroom.getIdentifier() - + ". Chat room listener removed."); - IrcConnection.this.provider.getMUC().fireLocalUserPresenceEvent( - this.chatroom, - LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_LEFT, null); - } - - /** - * Process mode changes. - * - * @param msg raw mode message - */ - private void processModeMessage(final ChannelModeMessage msg) - { - final ChatRoomMemberIrcImpl source = extractChatRoomMember(msg); - final ModeParser parser = new ModeParser(msg.getModeStr()); - for (ModeEntry mode : parser.getModes()) - { - switch (mode.getMode()) - { - case OWNER: - case OPERATOR: - case HALFOP: - case VOICE: - processRoleChange(source, mode); - break; - case LIMIT: - processLimitChange(source, mode); - break; - case BAN: - processBanChange(source, mode); - break; - case UNKNOWN: - if (LOGGER.isInfoEnabled()) - { - LOGGER.info("Unknown mode: " - + (mode.isAdded() ? "+" : "-") - + mode.getParams()[0] + ". Original mode string: '" - + msg.getModeStr() + "'"); - } - break; - default: - if (LOGGER.isInfoEnabled()) - { - LOGGER.info("Unsupported mode '" - + (mode.isAdded() ? "+" : "-") + mode.getMode() - + "' (from modestring '" + msg.getModeStr() + "')"); - } - break; - } - } - } - - /** - * Process changes for ban patterns. - * - * @param sourceMember the originating member - * @param mode the ban mode change - */ - private void processBanChange(final ChatRoomMemberIrcImpl sourceMember, - final ModeEntry mode) - { - final MessageIrcImpl banMessage = - new MessageIrcImpl( - "channel ban mask was " - + (mode.isAdded() ? "added" : "removed") - + ": " - + mode.getParams()[0] - + " by " - + (sourceMember.getContactAddress().length() == 0 - ? "server" - : sourceMember.getContactAddress()), - MessageIrcImpl.DEFAULT_MIME_TYPE, - MessageIrcImpl.DEFAULT_MIME_ENCODING, null); - this.chatroom.fireMessageReceivedEvent(banMessage, sourceMember, - new Date(), - ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); - } - - /** - * Process mode changes resulting in role manipulation. - * - * @param sourceMember the originating member - * @param mode the mode change - */ - private void processRoleChange( - final ChatRoomMemberIrcImpl sourceMember, final ModeEntry mode) - { - final String targetNick = mode.getParams()[0]; - final ChatRoomMemberIrcImpl targetMember = - (ChatRoomMemberIrcImpl) this.chatroom - .getChatRoomMember(targetNick); - final ChatRoomMemberRole originalRole = targetMember.getRole(); - if (mode.isAdded()) - { - targetMember.addRole(mode.getMode().getRole()); - } - else - { - targetMember.removeRole(mode.getMode().getRole()); - } - final ChatRoomMemberRole newRole = targetMember.getRole(); - if (newRole != originalRole) - { - // Mode change actually caused a role change. - final ChatRoomLocalUserRoleChangeEvent event = - new ChatRoomLocalUserRoleChangeEvent(this.chatroom, - originalRole, newRole, false); - if (isMe(targetMember.getContactAddress())) - { - this.chatroom.fireLocalUserRoleChangedEvent(event); - } - else - { - this.chatroom.fireMemberRoleEvent(targetMember, - newRole); - } - } - else - { - // Mode change did not cause an immediate role change. - // Display a system message for the mode change. - final String text = - sourceMember.getName() - + (mode.isAdded() ? " gives " - + mode.getMode().name().toLowerCase() - + " to " : " removes " - + mode.getMode().name().toLowerCase() - + " from ") + targetMember.getName(); - final MessageIrcImpl message = - new MessageIrcImpl(text, - MessageIrcImpl.DEFAULT_MIME_TYPE, - MessageIrcImpl.DEFAULT_MIME_ENCODING, null); - this.chatroom - .fireMessageReceivedEvent( - message, - sourceMember, - new Date(), - ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); - } - } - - /** - * Process mode change that represents a channel limit modification. - * - * @param sourceMember the originating member - * @param mode the limit mode change - */ - private void processLimitChange( - final ChatRoomMemberIrcImpl sourceMember, final ModeEntry mode) - { - final MessageIrcImpl limitMessage; - if (mode.isAdded()) - { - try - { - limitMessage = - new MessageIrcImpl( - "channel limit set to " - + Integer.parseInt(mode.getParams()[0]) - + " by " - + (sourceMember.getContactAddress() - .length() == 0 - ? "server" - : sourceMember.getContactAddress()), - "text/plain", "UTF-8", null); - } - catch (NumberFormatException e) - { - LOGGER.warn("server sent incorrect limit: " - + "limit is not a number", e); - return; - } - } - else - { - // TODO "server" is now easily fakeable if someone - // calls himself server. There should be some other way - // to represent the server if a message comes from - // something other than a normal chat room member. - limitMessage = - new MessageIrcImpl( - "channel limit removed by " - + (sourceMember.getContactAddress().length() == 0 - ? "server" - : sourceMember.getContactAddress()), - "text/plain", "UTF-8", null); - } - this.chatroom.fireMessageReceivedEvent(limitMessage, sourceMember, - new Date(), - ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); - } - - /** - * Extract chat room member identifier from message. - * - * @param msg raw mode message - * @return returns member instance - */ - private ChatRoomMemberIrcImpl extractChatRoomMember( - final ChannelModeMessage msg) - { - ChatRoomMemberIrcImpl member; - ISource source = msg.getSource(); - if (source instanceof IRCServer) - { - // TODO Created chat room member with creepy empty contact ID. - // Interacting with this contact might screw up other sections - // of code which is not good. Is there a better way to represent - // an IRC server as a chat room member? - member = - new ChatRoomMemberIrcImpl(IrcConnection.this.provider, - this.chatroom, "", ChatRoomMemberRole.ADMINISTRATOR); - } - else if (source instanceof IRCUser) - { - String nick = ((IRCUser) source).getNick(); - member = - (ChatRoomMemberIrcImpl) this.chatroom - .getChatRoomMember(nick); - } - else - { - throw new IllegalArgumentException("Unknown source type: " - + source.getClass().getName()); - } - return member; - } - - /** - * Test whether this listener corresponds to the chat room. - * - * @param chatRoomName chat room name - * @return returns true if this listener applies, false otherwise - */ - private boolean isThisChatRoom(final String chatRoomName) - { - return this.chatroom.getIdentifier().equalsIgnoreCase(chatRoomName); - } - - /** - * Test whether the source user is this user. - * - * @param user the source user - * @return returns true if this use, or false otherwise - */ - private boolean isMe(final IRCUser user) - { - return isMe(user.getNick()); - } - - /** - * Test whether the user nick is this user. - * - * @param name nick of the user - * @return returns true if so, false otherwise - */ - private boolean isMe(final String name) - { - final String userNick = - IrcConnection.this.connectionState.getNickname(); - if (userNick == null) - { - return false; - } - return userNick.equals(name); - } - } } diff --git a/src/net/java/sip/communicator/impl/protocol/irc/IrcStack.java b/src/net/java/sip/communicator/impl/protocol/irc/IrcStack.java index 0395910ae..d98af95c7 100644 --- a/src/net/java/sip/communicator/impl/protocol/irc/IrcStack.java +++ b/src/net/java/sip/communicator/impl/protocol/irc/IrcStack.java @@ -183,10 +183,8 @@ private SSLContext getCustomSSLContext(final String hostname) SSLContext context = null; try { - CertificateService cs = - IrcActivator.getCertificateService(); - X509TrustManager tm = - cs.getTrustManager(hostname); + CertificateService cs = IrcActivator.getCertificateService(); + X509TrustManager tm = cs.getTrustManager(hostname); context = cs.getSSLContext(tm); } catch (GeneralSecurityException e) diff --git a/src/net/java/sip/communicator/impl/protocol/irc/ProtocolProviderServiceIrcImpl.java b/src/net/java/sip/communicator/impl/protocol/irc/ProtocolProviderServiceIrcImpl.java index 84a65e583..f4dcbab84 100644 --- a/src/net/java/sip/communicator/impl/protocol/irc/ProtocolProviderServiceIrcImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/irc/ProtocolProviderServiceIrcImpl.java @@ -364,6 +364,8 @@ public void shutdown() public void unregister() throws OperationFailedException { + // TODO Consider removing the leave operations, as QUIT automatically + // leaves open channels. for (ChatRoom joinedChatRoom : multiUserChat .getCurrentlyJoinedChatRooms()) { diff --git a/test/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImplTest.java b/test/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImplTest.java index a05730653..a2a6c7590 100644 --- a/test/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImplTest.java +++ b/test/net/java/sip/communicator/impl/protocol/irc/ChatRoomIrcImplTest.java @@ -16,6 +16,7 @@ public class ChatRoomIrcImplTest private ProtocolProviderServiceIrcImpl providerMock; private IrcStack stackMock; private IrcConnection connectionMock; + private ChannelManager channelMock; //@before public void setUp() throws Exception @@ -25,23 +26,25 @@ public void setUp() throws Exception EasyMock.createMock(ProtocolProviderServiceIrcImpl.class); this.stackMock = EasyMock.createMock(IrcStack.class); this.connectionMock = EasyMock.createMock(IrcConnection.class); - EasyMock.expect(this.providerMock.getIrcStack()).andReturn(stackMock); + this.channelMock = EasyMock.createMock(ChannelManager.class); + EasyMock.expect(this.providerMock.getIrcStack()).andReturn(this.stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn(this.connectionMock); - EasyMock.expect(this.connectionMock.getChannelTypes()).andReturn( + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + EasyMock.expect(this.channelMock.getChannelTypes()).andReturn( Collections.unmodifiableSet(Sets.newHashSet('#', '&'))); } //@Test public void testConstruction() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); new ChatRoomIrcImpl("#test", this.providerMock); } //@Test(expected = IllegalArgumentException.class) public void testConstructionNullIdentifier() { - EasyMock.replay(this.providerMock, this.stackMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); try { new ChatRoomIrcImpl(null, this.providerMock); @@ -69,7 +72,7 @@ public void testConstructionNullProvider() //@Test(expected = IllegalArgumentException.class) public void testEmptyName() { - EasyMock.replay(this.providerMock, this.stackMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); try { new ChatRoomIrcImpl("", this.providerMock); @@ -83,7 +86,7 @@ public void testEmptyName() //@Test(expected = IllegalArgumentException.class) public void testTooLongName() { - EasyMock.replay(this.providerMock, this.stackMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); try { new ChatRoomIrcImpl( @@ -102,7 +105,7 @@ public void testTooLongName() //@Test public void testAutoPrefixBadChannelName() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("!test", this.providerMock); Assert.assertEquals("#!test", room.getIdentifier()); } @@ -110,7 +113,7 @@ public void testAutoPrefixBadChannelName() //@Test(expected = IllegalArgumentException.class) public void testIllegalNameSpace() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); try { new ChatRoomIrcImpl("#test test", this.providerMock); @@ -124,7 +127,7 @@ public void testIllegalNameSpace() //@Test(expected = IllegalArgumentException.class) public void testIllegalNameComma() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); try { new ChatRoomIrcImpl("#test,test", this.providerMock); @@ -138,14 +141,14 @@ public void testIllegalNameComma() //@Test public void testValidName() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); } //@Test public void testCorrectConstruction() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertEquals("#my-cool-channel", room.getIdentifier()); @@ -156,7 +159,7 @@ public void testCorrectConstruction() //@Test public void testHashCodeNotFailing() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); room.hashCode(); @@ -169,12 +172,13 @@ public void testRoomIsJoined() .andReturn(this.stackMock).times(2); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock).times(2); + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock).times(2); EasyMock .expect( - this.connectionMock.isJoined(EasyMock + this.channelMock.isJoined(EasyMock .anyObject(ChatRoomIrcImpl.class))).andReturn(false) .andReturn(true); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertFalse(room.isJoined()); @@ -184,7 +188,7 @@ public void testRoomIsJoined() //@Test public void testIsPersistentRoom() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertTrue(room.isPersistent()); @@ -193,7 +197,7 @@ public void testIsPersistentRoom() //@Test public void testDestroyRoom() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertTrue(room.destroy("whatever", null)); @@ -202,7 +206,7 @@ public void testDestroyRoom() //@Test public void testSetLocalUserNull() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); try @@ -218,7 +222,7 @@ public void testSetLocalUserNull() //@Test public void testSetLocalUser() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertEquals(ChatRoomMemberRole.SILENT_MEMBER, @@ -243,7 +247,7 @@ public void testMemberCount() { ChatRoomMemberIrcImpl user = EasyMock.createMock(ChatRoomMemberIrcImpl.class); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock, user); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -262,7 +266,7 @@ public void testAddMember() { ChatRoomMemberIrcImpl user = EasyMock.createMock(ChatRoomMemberIrcImpl.class); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock, user); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -278,7 +282,7 @@ public void testRemoveMember() { ChatRoomMemberIrcImpl user = EasyMock.createMock(ChatRoomMemberIrcImpl.class); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock, user); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -293,7 +297,7 @@ public void testRemoveMember() //@Test public void testEqualsSame() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertTrue(room.equals(room)); @@ -302,7 +306,7 @@ public void testEqualsSame() //@Test public void testEqualsNull() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertFalse(room.equals(null)); @@ -311,7 +315,7 @@ public void testEqualsNull() //@Test public void testEqualsOtherClassInstance() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertFalse(room.equals(new Object())); @@ -325,9 +329,10 @@ public void testEqualsOtherProviderInstance() EasyMock.expect(providerMock2.getIrcStack()).andReturn(this.stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - EasyMock.expect(this.connectionMock.getChannelTypes()).andReturn( + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + EasyMock.expect(this.channelMock.getChannelTypes()).andReturn( Collections.unmodifiableSet(Sets.newHashSet('#', '$'))); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock, providerMock2); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -342,9 +347,10 @@ public void testEqualsOtherRoomInstance() EasyMock.expect(this.providerMock.getIrcStack()).andReturn(stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - EasyMock.expect(this.connectionMock.getChannelTypes()).andReturn( + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + EasyMock.expect(this.channelMock.getChannelTypes()).andReturn( Collections.unmodifiableSet(Sets.newHashSet('#', '$'))); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); ChatRoomIrcImpl room2 = @@ -358,9 +364,10 @@ public void testEqualsSameRoomRepresentation() EasyMock.expect(this.providerMock.getIrcStack()).andReturn(stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - EasyMock.expect(this.connectionMock.getChannelTypes()).andReturn( + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + EasyMock.expect(this.channelMock.getChannelTypes()).andReturn( Collections.unmodifiableSet(Sets.newHashSet('#', '$'))); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); ChatRoomIrcImpl room2 = @@ -371,7 +378,7 @@ public void testEqualsSameRoomRepresentation() //@Test public void testGetChatRoomSubject() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); Assert.assertEquals("", room.getSubject()); @@ -381,17 +388,19 @@ public void testGetChatRoomSubject() public void testSetChatRoomSubject() throws OperationFailedException { final String newSubject = "My test subject!"; - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall(); EasyMock.expect(this.providerMock.getIrcStack()).andReturn( this.stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall(); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -408,7 +417,8 @@ public void testSetChatRoomSubjectFailedByIndirectIOException() throws OperationFailedException { final String newSubject = "My test subject!"; - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall().andThrow( new RuntimeException("Some error", new IOException("Real cause"))); @@ -416,10 +426,11 @@ public void testSetChatRoomSubjectFailedByIndirectIOException() this.stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall(); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -440,17 +451,19 @@ public void testSetChatRoomSubjectFailedByOtherRuntimeException() throws OperationFailedException { final String newSubject = "My test subject!"; - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall().andThrow(new RuntimeException("Some error")); EasyMock.expect(this.providerMock.getIrcStack()).andReturn( this.stackMock); EasyMock.expect(this.stackMock.getConnection()).andReturn( this.connectionMock); - this.connectionMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), + EasyMock.expect(this.connectionMock.getChannelManager()).andReturn(this.channelMock); + this.channelMock.setSubject(EasyMock.anyObject(ChatRoomIrcImpl.class), EasyMock.eq(newSubject)); EasyMock.expectLastCall(); - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl room = new ChatRoomIrcImpl("#my-cool-channel", this.providerMock); @@ -473,7 +486,7 @@ public void testSetChatRoomSubjectFailedByOtherRuntimeException() // @Test public void testChatRoomWithAlternativePrefix() { - EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock); + EasyMock.replay(this.providerMock, this.stackMock, this.connectionMock, this.channelMock); ChatRoomIrcImpl alternative = new ChatRoomIrcImpl("&MyAlternative-channel-prefix", this.providerMock); @@ -487,12 +500,14 @@ public void testOnlyAlternativeChannelTypesWithDefault() EasyMock.createMock(ProtocolProviderServiceIrcImpl.class); IrcStack specialStackMock = EasyMock.createMock(IrcStack.class); IrcConnection specialConnectionMock = EasyMock.createMock(IrcConnection.class); + ChannelManager specialChannelMock = EasyMock.createMock(ChannelManager.class); EasyMock.expect(specialProviderMock.getIrcStack()).andReturn( specialStackMock); EasyMock.expect(specialStackMock.getConnection()).andReturn(specialConnectionMock); - EasyMock.expect(specialConnectionMock.getChannelTypes()).andReturn( + EasyMock.expect(specialConnectionMock.getChannelManager()).andReturn(specialChannelMock); + EasyMock.expect(specialChannelMock.getChannelTypes()).andReturn( Sets.newHashSet('&')); - EasyMock.replay(specialProviderMock, specialStackMock, specialConnectionMock); + EasyMock.replay(specialProviderMock, specialStackMock, specialConnectionMock, specialChannelMock); ChatRoomIrcImpl alternative = new ChatRoomIrcImpl("channel-name-without-prefix", specialProviderMock);