Commits

Chris Thunes  committed 2402a04 Merge

Merge branch 'dev'

  • Participants
  • Parent commits 0e2c677, 7499808

Comments (0)

Files changed (84)

File brewtab-irc/pom.xml

       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>

File brewtab-irc/src/main/java/com/brewtab/irc/Connection.java

+package com.brewtab.irc;
+
+import java.util.List;
+
+import org.jboss.netty.channel.ChannelFuture;
+
+import com.brewtab.irc.messages.Message;
+import com.brewtab.irc.messages.MessageListener;
+import com.brewtab.irc.messages.filter.MessageFilter;
+
+/**
+ * Represents a general IRC connection. The connection may be client to server,
+ * server to client, or server to server.
+ */
+public interface Connection {
+    /**
+     * Add a message listener to the connection. If this listener has had
+     * already been registered with a different filter, its filter will be
+     * replaced with the one provided.
+     * 
+     * @param filter
+     * @param listener
+     */
+    public void addMessageListener(MessageFilter filter, MessageListener listener);
+
+    /**
+     * Remove a previously added message listener.
+     * 
+     * @param listener
+     */
+    public void removeMessageListener(MessageListener listener);
+
+    /**
+     * Add a connection state listener.
+     * 
+     * @param listener
+     */
+    public void addConnectionStateListener(ConnectionStateListener listener);
+
+    /**
+     * Remove a connection state listener.
+     * 
+     * @param listener
+     */
+    public void removeConnectionStateListener(ConnectionStateListener listener);
+
+    /**
+     * Send a message on this connection.
+     * 
+     * @param message
+     * @return A Netty {@link ChannelFuture} object for the send operation
+     */
+    public ChannelFuture send(Message message);
+
+    /**
+     * Send a multiple messages on this connection.
+     * 
+     * @param message
+     * @return A Netty {@link ChannelFuture} object for the send operation
+     */
+    public ChannelFuture send(Message... messages);
+
+    /**
+     * Send a message and return a response.
+     * 
+     * @param match a filter to match messages which should be included in the response
+     * @param last a filter to match the last message of the response
+     * @param message the message to send
+     * @return a list of response messages matching the provided filters
+     * @throws InterruptedException
+     */
+    public List<Message> request(MessageFilter match, MessageFilter last, Message message) throws InterruptedException;
+
+    /**
+     * Send a message and return a response.
+     * 
+     * @param match a filter to match messages which should be included in the response
+     * @param last a filter to match the last message of the response
+     * @param message the message to send
+     * @return a list of response messages matching the provided filters
+     * @throws InterruptedException
+     */
+    public List<Message> request(MessageFilter match, MessageFilter last, Message... messages) throws InterruptedException;
+
+    /**
+     * Close this connection.
+     * 
+     * @return A Netty {@link ChannelFuture} for the close operation
+     */
+    public ChannelFuture close();
+
+    /**
+     * Check if the connection is connected
+     * 
+     * @return
+     */
+    public boolean isConnected();
+}

File brewtab-irc/src/main/java/com/brewtab/irc/ConnectionException.java

+package com.brewtab.irc;
+
+public class ConnectionException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public ConnectionException(String message, Throwable e) {
+        super(message, e);
+    }
+
+    public ConnectionException(Throwable e) {
+        super(e);
+    }
+
+    public ConnectionException(String message) {
+        super(message);
+    }
+}

File brewtab-irc/src/main/java/com/brewtab/irc/ConnectionStateListener.java

+package com.brewtab.irc;
+
+public interface ConnectionStateListener {
+    /**
+     * Called when a connection is connected.
+     */
+    public void onConnectionConnected();
+
+    /**
+     * Called when a connection has started being closed.
+     */
+    public void onConnectionClosing();
+
+    /**
+     * Called when a connection has been closed.
+     */
+    public void onConnectionClosed();
+}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCChannel.java

-package com.brewtab.irc;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
-
-import com.brewtab.irc.messages.IRCMessage;
-import com.brewtab.irc.messages.IRCMessageType;
-import com.brewtab.irc.messages.filter.IRCMessageFilter;
-import com.brewtab.irc.messages.filter.IRCMessageFilters;
-import com.brewtab.irc.messages.filter.IRCMessageOrFilter;
-
-/**
- * An IRC channel. Represents a connection to an IRC channel.
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public class IRCChannel implements IRCMessageHandler {
-    /* Channel name */
-    private String name;
-
-    /* Associated IRCClient object */
-    private IRCClient client;
-
-    /* Set once the channel has been joined */
-    private CountDownLatch joined;
-
-    /* IRCChanneListners for this channel */
-    private LinkedList<IRCChannelListener> listeners;
-
-    /* List of nicks in the channel */
-    private ArrayList<String> names;
-
-    /**
-     * Message handler that receives responses from NAMES requests for this
-     * channel and creates a list of names. NamesRequestHandler#getNames can be
-     * called to wait for the list to be fully populated at which time all names
-     * are returned as an array.
-     * 
-     * @author Christopher Thunes <cthunes@brewtab.com>
-     */
-    private class NamesRequestHandler implements IRCMessageHandler, IRCMessageFilter {
-        private ArrayList<String> names;
-        private CountDownLatch done;
-
-        /**
-         * Create a new NamesRequestHandler
-         */
-        public NamesRequestHandler() {
-            this.names = new ArrayList<String>();
-            this.done = new CountDownLatch(1);
-        }
-
-        @Override
-        public void handleMessage(IRCMessage message) {
-            switch (message.getType()) {
-            case RPL_NAMREPLY:
-                String[] args = message.getArgs();
-                for (String name : args[args.length - 1].split(" ")) {
-                    names.add(name.replaceFirst("^[@+]", ""));
-                }
-                break;
-
-            case RPL_ENDOFNAMES:
-                this.done.countDown();
-                break;
-
-            default:
-                break;
-            }
-        }
-
-        @Override
-        public boolean check(IRCMessage message) {
-            String channelName = IRCChannel.this.getName();
-            String[] args = message.getArgs();
-
-            switch (message.getType()) {
-            case RPL_NAMREPLY:
-                if (args.length > 2 && args[2].equals(channelName)) {
-                    return true;
-                }
-                break;
-
-            case RPL_ENDOFNAMES:
-                if (args.length > 1 && args[1].equals(channelName)) {
-                    return true;
-                }
-                break;
-
-            default:
-                break;
-            }
-
-            return false;
-        }
-
-        /**
-         * Wait for a full request to be replied to and then return the list of
-         * names
-         * 
-         * @return the list of names in the channel
-         */
-        public ArrayList<String> getNames() {
-            try {
-                this.done.await();
-            } catch (InterruptedException e) {
-                // TODO: Log and return whatever we have
-            }
-
-            return this.names;
-        }
-    }
-
-    /**
-     * Instantiate a new IRCChannel object associated with the given IRCClient
-     * 
-     * @param client
-     *            The client to operate with
-     * @param name
-     *            The channel to join
-     */
-    public IRCChannel(IRCClient client, String name) {
-        this.client = client;
-        this.name = name;
-        this.joined = new CountDownLatch(1);
-        this.listeners = new LinkedList<IRCChannelListener>();
-        this.names = new ArrayList<String>();
-
-        /*
-         * Build a compound filter collecting messages sent regarding this
-         * channel and include all QUIT messages. Some QUIT messages will
-         * concern this channel if it's a user in this channel QUITing
-         */
-        IRCMessageFilter channelFilter = IRCMessageFilters.newChannelFilter(name);
-        IRCMessageFilter quitFilter = IRCMessageFilters.newTypeFilter(IRCMessageType.QUIT);
-        IRCMessageFilter nickFilter = IRCMessageFilters.newTypeFilter(IRCMessageType.NICK);
-        IRCMessageFilter filter = new IRCMessageOrFilter(channelFilter, quitFilter, nickFilter);
-
-        this.client.addHandler(filter, this);
-    }
-
-    /**
-     * Get the channel name
-     * 
-     * @return the channel name
-     */
-    public String getName() {
-        return this.name;
-    }
-
-    /**
-     * Get the associated IRCClient
-     * 
-     * @return the associated IRCClient
-     */
-    public IRCClient getClient() {
-        return this.client;
-    }
-
-    /**
-     * Perform the join to the channel. Returns once the join operation is
-     * complete
-     * 
-     * @return true if joined successfully, false otherwise
-     */
-    public boolean doJoin() {
-        IRCMessage joinMessage = new IRCMessage(IRCMessageType.JOIN, this.name);
-        NamesRequestHandler namesHandler = new NamesRequestHandler();
-
-        this.client.addHandler(namesHandler, namesHandler);
-        this.client.getConnection().sendMessage(joinMessage);
-
-        try {
-            this.joined.await();
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            return false;
-        }
-
-        this.names = namesHandler.getNames();
-        this.client.removeHandler(namesHandler);
-
-        return true;
-    }
-
-    /**
-     * Part the channel
-     * 
-     * @param reason
-     *            The reason sent with the PART message
-     */
-    public void part(String reason) {
-        IRCMessage partMessage = new IRCMessage(IRCMessageType.PART, this.name, reason);
-        this.client.getConnection().sendMessage(partMessage);
-        this.joined = new CountDownLatch(1);
-    }
-
-    /**
-     * Write a message to the channel
-     * 
-     * @param text
-     *            The message to send
-     */
-    public void write(String text) {
-        IRCMessage privMessage = new IRCMessage(IRCMessageType.PRIVMSG, this.name, text);
-        this.client.getConnection().sendMessage(privMessage);
-    }
-
-    /**
-     * Write multiple messages efficiently
-     * 
-     * @param strings
-     *            The messages to send
-     */
-    public void writeMultiple(String... strings) {
-        IRCMessage[] privMessages = new IRCMessage[strings.length];
-        for (int i = 0; i < strings.length; i++) {
-            privMessages[i] = new IRCMessage(IRCMessageType.PRIVMSG, this.name, strings[i]);
-        }
-        this.client.getConnection().sendMessages(privMessages);
-    }
-
-    /**
-     * Retrieve the list of users in the channel. This call returns a cached
-     * copy. To request a refresh of the listing from the server call
-     * IRChannel#refreshNames.
-     * 
-     * @return the list of nicks of users in the channel
-     */
-    public String[] getNames() {
-        String[] tempNames = new String[this.names.size()];
-        tempNames = this.names.toArray(tempNames);
-        return tempNames;
-    }
-
-    /**
-     * Makes a NAMES request to the server for this channel. Store the result
-     * replacing any existing names list. The list can be retrieved with
-     * IRCChannel#getNames
-     */
-    public void refreshNames() {
-        IRCMessage message = new IRCMessage(IRCMessageType.NAMES, this.name);
-        NamesRequestHandler handler = new NamesRequestHandler();
-
-        this.client.addHandler(handler, handler);
-        this.client.getConnection().sendMessage(message);
-        this.names = handler.getNames();
-        this.client.removeHandler(handler);
-    }
-
-    /**
-     * Add an IRCChannelListener to this channel.
-     * 
-     * @param listener
-     *            the listener to add
-     */
-    public void addListener(IRCChannelListener listener) {
-        this.listeners.add(listener);
-    }
-
-    @Override
-    public void handleMessage(IRCMessage message) {
-        IRCUser user = IRCUser.fromString(message.getPrefix());
-
-        switch (message.getType()) {
-        case JOIN:
-            /* Must have valid user prefix */
-            if (user == null) {
-                break;
-            }
-
-            /*
-             * Once we've received a join message from the server we ourselves
-             * have actually joined
-             */
-            this.joined.countDown();
-
-            /* Add user to names list */
-            if (!this.names.contains(user.getNick())) {
-                this.names.add(user.getNick());
-            }
-
-            /* Call listeners */
-            for (IRCChannelListener listener : this.listeners) {
-                listener.onJoin(this, user);
-            }
-            break;
-
-        case PART:
-            /* Must have valid user prefix */
-            if (user == null) {
-                break;
-            }
-
-            /* Remove nick from names list */
-            if (this.names.contains(user.getNick())) {
-                this.names.remove(user.getNick());
-            }
-
-            /* Call listeners */
-            for (IRCChannelListener listener : this.listeners) {
-                listener.onPart(this, user);
-            }
-            break;
-
-        case PRIVMSG:
-            /* Must have valid user prefix */
-            if (user == null) {
-                break;
-            }
-
-            /* Call listeners */
-            for (IRCChannelListener listener : this.listeners) {
-                listener.onPrivateMessage(this, message.getArgs()[1], user);
-            }
-            break;
-
-        case QUIT:
-            /* Must have valid user prefix */
-            if (user == null) {
-                break;
-            }
-
-            /* Remove nick from names list */
-            if (this.names.contains(user.getNick())) {
-                this.names.remove(user.getNick());
-            }
-
-            /* Call listeners */
-            for (IRCChannelListener listener : this.listeners) {
-                listener.onQuit(this, user);
-            }
-            break;
-
-        case NICK:
-            /* Must have valid user prefix */
-            if (user == null) {
-                break;
-            }
-
-            /* Replace nick in names list */
-            if (this.names.contains(user.getNick())) {
-                this.names.remove(user.getNick());
-                this.names.add(message.getArgs()[0]);
-            }
-            break;
-
-        default:
-            break;
-        }
-    }
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCChannelListener.java

-package com.brewtab.irc;
-
-/**
- * Implementing class can listen for IRC channel related events
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public interface IRCChannelListener {
-    /**
-     * Called when a user joins the channel
-     * 
-     * @param channel
-     *            The channel that was joined
-     * @param user
-     *            The user that joined
-     */
-    public void onJoin(IRCChannel channel, IRCUser user);
-
-    /**
-     * Called when a user parts the channel
-     * 
-     * @param channel
-     *            The channel that was parted from
-     * @param user
-     *            The user that parted
-     */
-    public void onPart(IRCChannel channel, IRCUser user);
-
-    /**
-     * Called when a user quits the channel
-     * 
-     * @param channel
-     *            The channel that was quit from
-     * @param user
-     *            The user that parted
-     */
-    public void onQuit(IRCChannel channel, IRCUser user);
-
-    /**
-     * Called whenever a message is sent to the channel
-     * 
-     * @param channel
-     *            The channel the message was received on
-     * @param message
-     *            The message
-     * @param from
-     *            The user that sent the message
-     */
-    public void onPrivateMessage(IRCChannel channel, String message, IRCUser from);
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCClient.java

-package com.brewtab.irc;
-
-import java.net.InetSocketAddress;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import org.jboss.netty.bootstrap.ClientBootstrap;
-import org.jboss.netty.channel.ChannelFactory;
-import org.jboss.netty.channel.ChannelFuture;
-import org.jboss.netty.channel.ChannelFutureListener;
-import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.brewtab.irc.messages.IRCMessage;
-import com.brewtab.irc.messages.IRCMessageType;
-import com.brewtab.irc.messages.filter.IRCMessageFilter;
-import com.brewtab.irc.protocol.IRCChannelHandler;
-import com.brewtab.irc.protocol.IRCChannelPipelineFactory;
-
-public class IRCClient implements IRCConnectionManager {
-    private static final Logger log = LoggerFactory.getLogger(IRCClient.class);
-
-    /* Netty objects */
-    private ChannelFactory channelFactory;
-    private ClientBootstrap bootstrap;
-    private IRCChannelHandler channelHandler;
-    private IRCChannelPipelineFactory pipelineFactory;
-
-    /* The address of the server */
-    private InetSocketAddress address;
-
-    /* Cached copy of the server's name */
-    private String servername;
-
-    /* Connection information */
-    private String pass;
-    private String nick;
-    private String username;
-    private String hostname;
-    private String realname;
-
-    /* User object, constructed from nick, username, and hostname */
-    private IRCUser user;
-
-    /* Designate connection status */
-    private CountDownLatch connected;
-    private boolean connectedSuccessfully;
-
-    /* Thread pool from which message handlers are dispatched */
-    private ExecutorService threadPool;
-
-    /* Filter -> Handler mapping */
-    private HashMap<IRCMessageFilter, IRCMessageHandler> handlers;
-    private HashSet<IRCMessageFilter> handlersKeys;
-
-    /**
-     * Wraps a message handler so it can be passed to the thread pool
-     * 
-     * @author Christopher Thunes <cthunes@brewtab.com>
-     */
-    private class MessageHandlerRunnable implements Runnable {
-        private IRCMessageHandler handler;
-        private IRCMessage message;
-
-        /**
-         * Create a new wrapper for the given handler and the given message
-         * 
-         * @param handler
-         *            The handler to call
-         * @param message
-         *            The message to pass
-         */
-        public MessageHandlerRunnable(IRCMessageHandler handler, IRCMessage message) {
-            this.handler = handler;
-            this.message = message;
-        }
-
-        @Override
-        public void run() {
-            try {
-                this.handler.handleMessage(this.message);
-            } catch (Exception e) {
-                log.error("Caught exception from message handler", e);
-            }
-        }
-    }
-
-    /**
-     * Construct a new IRCClient connected to the given address
-     * 
-     * @param address
-     *            The address to connect to
-     */
-    public IRCClient(InetSocketAddress address) {
-        this.channelFactory = new NioClientSocketChannelFactory(
-            Executors.newCachedThreadPool(),
-            Executors.newCachedThreadPool());
-
-        this.bootstrap = new ClientBootstrap(this.channelFactory);
-        this.channelHandler = null;
-        this.pipelineFactory = new IRCChannelPipelineFactory(this);
-
-        this.bootstrap.setPipelineFactory(this.pipelineFactory);
-        this.bootstrap.setOption("tcpNoDelay", true);
-
-        this.address = address;
-        this.servername = this.address.getHostName();
-
-        this.connected = new CountDownLatch(1);
-        this.connectedSuccessfully = false;
-
-        this.handlers = new HashMap<IRCMessageFilter, IRCMessageHandler>();
-        this.handlersKeys = new HashSet<IRCMessageFilter>();
-
-        this.threadPool = Executors.newCachedThreadPool();
-
-        this.pass = null;
-        this.nick = null;
-        this.username = null;
-        this.hostname = null;
-        this.realname = null;
-
-        this.user = null;
-    }
-
-    /**
-     * Get the user information give by the IRCClient#connect method as an
-     * IRCUser object
-     * 
-     * @return the IRCUser object or null if the client is not connected yet
-     */
-    public IRCUser getUser() {
-        return this.user;
-    }
-
-    /**
-     * Connect without a password and with the given information
-     * 
-     * @param nick
-     *            The nick to connection with
-     * @param username
-     *            The user name to connect with
-     * @param hostname
-     *            The host name to connect with
-     * @param realname
-     *            The real name to connect with
-     * @return true if the connection was successfully made, false otherwise
-     */
-    public boolean connect(String nick, String username, String hostname, String realname) {
-        return this.connect(null, nick, username, hostname, realname);
-    }
-
-    /**
-     * Connect with a password and with the given information
-     * 
-     * @param pass
-     *            The password to authenticate with
-     * @param nick
-     *            The nick to connection with
-     * @param username
-     *            The user name to connect with
-     * @param hostname
-     *            The host name to connect with
-     * @param realname
-     *            The real name to connect with
-     * @return true if the connection was successfully made, false otherwise
-     */
-    public boolean connect(String pass, String nick, String username, String hostname, String realname) {
-        /* Save connection information */
-        this.pass = pass;
-        this.nick = nick;
-        this.username = username;
-        this.hostname = hostname;
-        this.realname = realname;
-
-        this.user = new IRCUser(nick, username, hostname);
-
-        /* Perform connection */
-        ChannelFuture future = this.bootstrap.connect(this.address);
-
-        /* Wait for underly socket connection to be made */
-        future.awaitUninterruptibly();
-        if (!future.isSuccess()) {
-            /* Could not connection to remote host */
-            return false;
-        }
-
-        /* Now wait for IRC connection */
-        try {
-            this.connected.await();
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            return false;
-        }
-
-        return this.connectedSuccessfully;
-    }
-
-    /**
-     * Quit from the server and close all network connections
-     * 
-     * @param reason
-     *            The reason for quitting as given in the QUIT message
-     */
-    public void quit(String reason) {
-        IRCMessage quit = new IRCMessage(IRCMessageType.QUIT, reason);
-        ChannelFuture future = this.channelHandler.sendMessage(quit);
-
-        /* Wait for message to be sent and then close the underlying connection */
-        future.addListener(new ChannelFutureListener() {
-            @Override
-            public void operationComplete(ChannelFuture future) throws Exception {
-                IRCClient.this.channelHandler.closeChannel();
-            }
-        });
-
-    }
-
-    /**
-     * Join the given channel. Create a new channel object for this connection,
-     * join the channel and return the IRCChannel object.
-     * 
-     * @param channelName
-     *            The channel to join
-     * @return the new IRCChannel object or null if the channel could not be
-     *         joined
-     */
-    public IRCChannel join(String channelName) {
-        IRCChannel channel = new IRCChannel(this, channelName);
-
-        /* Attempt to join */
-        if (channel.doJoin()) {
-            return channel;
-        }
-
-        /* Error while joining */
-        return null;
-    }
-
-    public IRCPrivateChat getPrivateChat(String nick) {
-        return new IRCPrivateChat(this, nick);
-    }
-
-    @Override
-    public void onConnect(IRCChannelHandler connection) {
-        this.channelHandler = connection;
-
-        IRCMessage nickMessage = new IRCMessage(IRCMessageType.NICK, this.nick);
-        IRCMessage userMessage = new IRCMessage(IRCMessageType.USER, this.username, this.hostname, this.servername,
-                this.realname);
-
-        if (this.pass != null) {
-            /* Send a PASS message first */
-            IRCMessage passMessage = null;
-            if (this.pass != null) {
-                passMessage = new IRCMessage(IRCMessageType.PASS, this.pass);
-            }
-
-            connection.sendMessages(passMessage, nickMessage, userMessage);
-        } else {
-            connection.sendMessages(nickMessage, userMessage);
-        }
-    }
-
-    /**
-     * Add a message handler for this connection. Messages matching the given
-     * filter are passed onto the given handler
-     * 
-     * @param filter
-     *            The filter to match messages with
-     * @param handler
-     *            The handler to call with matching message
-     */
-    public void addHandler(IRCMessageFilter filter, IRCMessageHandler handler) {
-        synchronized (this.handlers) {
-            this.handlers.put(filter, handler);
-            this.handlersKeys.add(filter);
-        }
-    }
-
-    /**
-     * Remove the handler associated with the given filter
-     * 
-     * @param filter
-     *            The filter to remove
-     * @return true if successful, false otherwise
-     */
-    public boolean removeHandler(IRCMessageFilter filter) {
-        synchronized (this.handlers) {
-            if (this.handlers.containsKey(filter)) {
-                this.handlers.remove(filter);
-                this.handlersKeys.remove(filter);
-                return true;
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * Return the IRCChannelHandler for this client connection
-     * 
-     * @return the IRCChannelHandler for this client connection
-     */
-    public IRCChannelHandler getConnection() {
-        return this.channelHandler;
-    }
-
-    @Override
-    public void onClose(IRCChannelHandler connection) {
-        /*
-         * With the underly connection closed we can safely shutdown the thread
-         * pool
-         */
-        log.debug("on close");
-        synchronized (this.handlers) {
-            this.threadPool.shutdown();
-        }
-    }
-
-    @Override
-    public void onShutdown() {
-        log.debug("on shutdown");
-        this.bootstrap.releaseExternalResources();
-    }
-
-    @Override
-    public void receiveMessage(IRCChannelHandler connection, IRCMessage message) {
-        switch (message.getType()) {
-
-        /*
-         * At least one of these should be received once a good connection is
-         * established
-         */
-        case RPL_ENDOFMOTD:
-        case RPL_LUSERME:
-        case RPL_LUSERCHANNELS:
-        case RPL_LUSERCLIENT:
-        case RPL_LUSEROP:
-        case RPL_LUSERUNKNOWN:
-            synchronized (this.connected) {
-                this.connectedSuccessfully = true;
-                this.connected.countDown();
-            }
-            break;
-
-        /* Error while connecting */
-        case ERR_NICKNAMEINUSE:
-            synchronized (this.connected) {
-                this.connectedSuccessfully = false;
-                this.connected.countDown();
-            }
-            break;
-
-        case PING:
-            IRCMessage pong = new IRCMessage(IRCMessageType.PONG, this.servername);
-            connection.sendMessage(pong);
-            break;
-
-        default:
-            break;
-        }
-
-        synchronized (this.handlers) {
-            if (this.threadPool.isShutdown()) {
-                return;
-            }
-
-            for (IRCMessageFilter filter : this.handlersKeys) {
-                if (filter.check(message)) {
-                    IRCMessageHandler handler = this.handlers.get(filter);
-                    MessageHandlerRunnable runnableHandler = new MessageHandlerRunnable(handler, message);
-                    this.threadPool.submit(runnableHandler);
-                }
-            }
-        }
-    }
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCConnectionManager.java

-package com.brewtab.irc;
-
-import com.brewtab.irc.messages.IRCMessage;
-import com.brewtab.irc.protocol.IRCChannelHandler;
-
-/**
- * A class implementing this interface can be used to manage a IRC connection
- * through an IRCChannelHandler object. Typically, a class implementing this
- * interface will be passed to a IRCChannelPipelineFactory as either an instance
- * or through an IRCConnectionManagerFactroy to actually manage a connection.
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public interface IRCConnectionManager {
-    /**
-     * Called by the underlying IRChannelHandler once the connection has been
-     * established
-     * 
-     * @param connection
-     *            The associated IRCChannelHandler
-     */
-    public void onConnect(IRCChannelHandler connection);
-
-    /**
-     * Called by the underlying IRCChannelHandler once a close request has been
-     * made on the channel
-     * 
-     * @param connection
-     */
-    public void onClose(IRCChannelHandler connection);
-
-    /**
-     * Called by the underlying IRCChannelHandler once the IRCChannelHandler is
-     * shut down
-     */
-    public void onShutdown();
-
-    /**
-     * Called by the underlying IRCChannelHandler whenever it receive a message
-     * 
-     * @param connection
-     *            The calling IRCChannelHandler
-     * @param message
-     *            The received message
-     */
-    public void receiveMessage(IRCChannelHandler connection, IRCMessage message);
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCConnectionManagerFactory.java

-package com.brewtab.irc;
-
-/**
- * A factory for IRCConnectionManager objects. An IRCConnectionManagerFactory
- * can be used to provide separate IRCConnectionManager objects per pipeline.
- * Typical usage case would be to provide separate managers to each client
- * connection for a server.
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public interface IRCConnectionManagerFactory {
-    /**
-     * Return a new IRCConnectionManager
-     * 
-     * @return the new connection manager
-     */
-    public IRCConnectionManager getConnectionManager();
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCMessageHandler.java

-package com.brewtab.irc;
-
-import com.brewtab.irc.messages.IRCMessage;
-
-/**
- * Implementing class are capable of being registered with other objects to
- * receive upstream messages
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public interface IRCMessageHandler {
-    /**
-     * Handle a message
-     * 
-     * @param message
-     *            The message to process
-     */
-    public void handleMessage(IRCMessage message);
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCPrivateChat.java

-package com.brewtab.irc;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.brewtab.irc.messages.IRCMessage;
-import com.brewtab.irc.messages.IRCMessageType;
-import com.brewtab.irc.messages.filter.IRCMessageFilter;
-import com.brewtab.irc.messages.filter.IRCMessageSimpleFilter;
-import com.brewtab.irc.messages.filter.IRCUserPrefixFilter;
-
-/**
- * Have a private conversation
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public class IRCPrivateChat implements IRCMessageHandler {
-    private final static Logger log = LoggerFactory.getLogger(IRCPrivateChat.class);
-
-    /* Nick of the user receiving the messages */
-    private String receiver;
-
-    /* Associated IRCClient object */
-    private IRCClient client;
-
-    public IRCPrivateChat(IRCClient client, String nick) {
-        this.client = client;
-        this.receiver = nick;
-
-        IRCMessageFilter filter = new IRCMessageSimpleFilter(IRCMessageType.PRIVMSG, new IRCUserPrefixFilter(nick));
-        this.client.addHandler(filter, this);
-    }
-
-    public String getReceiver() {
-        return this.receiver;
-    }
-
-    /**
-     * Get the associated IRCClient
-     * 
-     * @return the associated IRCClient
-     */
-    public IRCClient getClient() {
-        return this.client;
-    }
-
-    /**
-     * Write a message to the channel
-     * 
-     * @param text
-     *            The message to send
-     */
-    public void write(String text) {
-        IRCMessage privMessage = new IRCMessage(IRCMessageType.PRIVMSG, this.receiver, text);
-        this.client.getConnection().sendMessage(privMessage);
-    }
-
-    /**
-     * Write multiple messages efficiently
-     * 
-     * @param strings
-     *            The messages to send
-     */
-    public void writeMultiple(String... strings) {
-        IRCMessage[] privMessages = new IRCMessage[strings.length];
-        for (int i = 0; i < strings.length; i++) {
-            privMessages[i] = new IRCMessage(IRCMessageType.PRIVMSG, this.receiver, strings[i]);
-        }
-        this.client.getConnection().sendMessages(privMessages);
-    }
-
-    @Override
-    public void handleMessage(IRCMessage message) {
-        IRCUser user = IRCUser.fromString(message.getPrefix());
-        String messageText = message.getArgs()[message.getArgs().length - 1];
-
-        log.debug("PM from {}: {}", user.getNick(), messageText);
-    }
-}

File brewtab-irc/src/main/java/com/brewtab/irc/IRCUser.java

-package com.brewtab.irc;
-
-/**
- * Simple object encapsulating a user's nick, username, and hostname
- * 
- * @author Christopher Thunes <cthunes@brewtab.com>
- */
-public class IRCUser {
-    /** The nick */
-    private String nick;
-
-    /** The user */
-    private String user;
-
-    /** The host */
-    private String host;
-
-    /**
-     * Construct a new IRCUser with the given parameters
-     * 
-     * @param nick
-     *            The user's nick
-     * @param user
-     *            The user's username
-     * @param host
-     *            The user's hostname
-     */
-    public IRCUser(String nick, String user, String host) {
-        this.nick = nick;
-        this.user = user;
-        this.host = host;
-    }
-
-    /**
-     * Get the nick
-     * 
-     * @return the nick
-     */
-    public String getNick() {
-        return this.nick;
-    }
-
-    /**
-     * Return the user name
-     * 
-     * @return the user name
-     */
-    public String getUser() {
-        return this.user;
-    }
-
-    /**
-     * Return the host name
-     * 
-     * @return the host name
-     */
-    public String getHost() {
-        return this.host;
-    }
-
-    /**
-     * Construct a new IRCUser from the give String. The String should be in the
-     * format {@literal <nick>!<user>@<host>}. This format is the same as is
-     * used in IRC message prefixes
-     * 
-     * @param prefix
-     *            The prefix to extract the information from
-     * @return a new IRCUser object or null if the prefix could not be parsed
-     */
-    public static IRCUser fromString(String prefix) {
-        if (prefix == null) {
-            return null;
-        }
-
-        int endNick = prefix.indexOf('!');
-        int endUser = prefix.indexOf('@');
-
-        if (endNick == -1 || endUser == -1 || endUser < endNick) {
-            return null;
-        }
-
-        String nick = prefix.substring(0, endNick);
-        String user = prefix.substring(endNick + 1, endUser);
-        String host = prefix.substring(endUser + 1);
-
-        return new IRCUser(nick, user, host);
-    }
-}

File brewtab-irc/src/main/java/com/brewtab/irc/NotConnectedException.java

+package com.brewtab.irc;
+
+/**
+ * Thrown to indicate that an operation was request that could be not be
+ * completed because a connection has not been established
+ * 
+ * @author Christopher Thunes <cthunes@brewtab.com>
+ */
+public class NotConnectedException extends IllegalStateException {
+    private static final long serialVersionUID = 4861994587138843189L;
+
+    /**
+     * Construct a new IRCNotConnectedException with the default message
+     */
+    public NotConnectedException() {
+        super("Not connected");
+    }
+
+    /**
+     * Construct a new IRCNotConnectedException with the give message
+     * 
+     * @param reason
+     *            The message to store along with the exception
+     */
+    public NotConnectedException(String reason) {
+        super(reason);
+    }
+}

File brewtab-irc/src/main/java/com/brewtab/irc/User.java

+package com.brewtab.irc;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Simple object encapsulating a user's nick, username, and hostname
+ * 
+ * @author Christopher Thunes <cthunes@brewtab.com>
+ */
+public class User {
+    private static final Pattern userPrefixPattern;
+
+    static {
+        final String specialNickCharset = Pattern.quote("[]\\`_^{}|");
+        final String nickFirstCharacter = "[a-zA-Z" + specialNickCharset + "]";
+        final String nickRest = "[a-zA-Z0-9\\-" + specialNickCharset + "]*";
+
+        final String nick = nickFirstCharacter + nickRest;
+        final String user = "[^@]+";
+        final String host = ".+";
+
+        userPrefixPattern = Pattern.compile("^(" + nick + ")((!(" + user + "))?@(" + host + "))?$");
+    }
+
+    /** The nick */
+    private String nick;
+
+    /** The user */
+    private String user;
+
+    /** The host */
+    private String host;
+
+    /**
+     * Construct a new IRCUser with the given parameters. Only a nick is
+     * required, but if a user is provided a host must also be provided.
+     * 
+     * @param nick The user's nick
+     * @param user The user's username, or null for none
+     * @param host The user's hostname, or null for none
+     */
+    public User(String nick, String user, String host) {
+        if (nick == null) {
+            throw new IllegalArgumentException("nick can not be null");
+        }
+
+        if (user != null && host == null) {
+            throw new IllegalArgumentException("can not provide username without hostname");
+        }
+
+        this.nick = nick;
+        this.user = user;
+        this.host = host;
+    }
+
+    public User(String nick) {
+        this(nick, null, null);
+    }
+
+    public User(String nick, String host) {
+        this(nick, null, host);
+    }
+
+    /**
+     * Get the nick
+     * 
+     * @return the nick
+     */
+    public String getNick() {
+        return this.nick;
+    }
+
+    /**
+     * Return the user name
+     * 
+     * @return the user name
+     */
+    public String getUser() {
+        return this.user;
+    }
+
+    /**
+     * Return the host name
+     * 
+     * @return the host name
+     */
+    public String getHost() {
+        return this.host;
+    }
+
+    /**
+     * Construct a new IRCUser from the give String. The String should be in the
+     * format {@literal <nick>!<user>@<host>}. This format is the same as is
+     * used in IRC message prefixes
+     * 
+     * @param prefix The prefix to extract the information from
+     * @return a new IRCUser object or null if the prefix could not be parsed
+     */
+    public static User fromPrefix(String prefix) {
+        Matcher matcher = userPrefixPattern.matcher(prefix);
+
+        if (matcher.find()) {
+            String nick = matcher.group(1);
+            String user = matcher.group(4);
+            String host = matcher.group(5);
+
+            return new User(nick, user, host);
+        } else {
+            return null;
+        }
+    }
+
+    public String toPrefix() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(nick);
+
+        if (host != null) {
+            if (user != null) {
+                sb.append('!');
+                sb.append(user);
+            }
+
+            sb.append('@');
+            sb.append(host);
+        }
+
+        return sb.toString();
+    }
+}

File brewtab-irc/src/main/java/com/brewtab/irc/client/Channel.java

+package com.brewtab.irc.client;
+
+import java.util.List;
+
+public interface Channel {
+
+    /**
+     * Get the channel name
+     * 
+     * @return the channel name
+     */
+    public String getName();
+
+    /**
+     * Get the associated IRCClient
+     * 
+     * @return the associated IRCClient
+     */
+    public Client getClient();
+
+    /**
+     * Part the channel
+     * 
+     * @param reason The reason sent with the PART message
+     */
+    public void part(String reason);
+
+    /**
+     * Write a message to the channel
+     * 
+     * @param text The message to send
+     */
+    public void write(String text);
+
+    /**
+     * Write multiple messages efficiently
+     * 
+     * @param strings The messages to send
+     */
+    public void writeMultiple(String... strings);
+
+    /**
+     * Retrieve the list of users in the channel. This call returns a cached
+     * copy. To request a refresh of the listing from the server call
+     * IRChannel#refreshNames.
+     * 
+     * @return the list of nicks of users in the channel
+     */
+    public List<String> getNames();
+
+    /**
+     * Add an IRCChannelListener to this channel.
+     * 
+     * @param listener the listener to add
+     */
+    public void addListener(ChannelListener listener);
+
+}

File brewtab-irc/src/main/java/com/brewtab/irc/client/ChannelListener.java

+package com.brewtab.irc.client;
+
+import com.brewtab.irc.User;
+
+/**
+ * Implementing class can listen for IRC channel related events
+ * 
+ * @author Christopher Thunes <cthunes@brewtab.com>
+ */
+public interface ChannelListener {
+    /**
+     * Called when a user joins the channel
+     * 
+     * @param channel The channel that was joined
+     * @param user The user that joined
+     */
+    public void onJoin(Channel channel, User user);
+
+    /**
+     * Called when a user parts the channel
+     * 
+     * @param channel The channel that was parted from
+     * @param user The user that parted
+     */
+    public void onPart(Channel channel, User user);
+
+    /**
+     * Called when a user quits the channel
+     * 
+     * @param channel The channel that was quit from
+     * @param user The user that parted
+     */
+    public void onQuit(Channel channel, User user);
+
+    /**
+     * Called whenever a message is sent to the channel
+     * 
+     * @param channel The channel the message was received on
+     * @param from The user that sent the message
+     * @param message The message
+     */
+    public void onMessage(Channel channel, User from, String message);
+}

File brewtab-irc/src/main/java/com/brewtab/irc/client/Client.java

+package com.brewtab.irc.client;
+
+import com.brewtab.irc.Connection;
+import com.brewtab.irc.User;
+
+public interface Client {
+
+    public String getUsername();
+
+    public String getHostname();
+
+    public String getRealName();
+
+    public void setNick(String nick);
+
+    public String getNick();
+
+    /**
+     * Quit and disconnect from the server
+     * 
+     * @param message
+     */
+    public void quit(String message);
+
+    /**
+     * Quit and disconnect from the server using a default message.
+     */
+    public void quit();
+
+    /**
+     * Join a channel
+     * 
+     * @param channelName
+     * @return
+     */
+    public Channel join(String channelName);
+
+    /**
+     * Send a message to a user
+     * 
+     * @param nick
+     * @param message
+     */
+    public void sendMessage(User user, String message);
+
+    /**
+     * Get the connection used by the client object
+     * 
+     * @return
+     */
+    public Connection getConnection();
+}

File brewtab-irc/src/main/java/com/brewtab/irc/client/ClientFactory.java

+package com.brewtab.irc.client;
+
+import com.brewtab.irc.impl.ClientFactoryImpl;
+
+public abstract class ClientFactory {
+    public static final int DEFAULT_PORT = 6667;
+    public static final int DEFAULT_SSL_PORT = 6697;
+
+    public static final String DEFAULT_USERNAME = "ircclient";
+    public static final String DEFAULT_HOSTNAME = "localhost";
+    public static final String DEFAULT_REALNAME = "Brewtab IRC Client";
+
+    public abstract void setUsername(String username);
+
+    public abstract void setHostname(String hostname);
+
+    public abstract void setRealName(String realName);
+
+    public abstract void setNick(String nick);
+
+    public abstract Client connect(String uri);
+
+    public abstract Client connect(String uri, String password);
+
+    public static ClientFactory newInstance() {
+        return ClientFactoryImpl.newInstance();
+    }
+}

File brewtab-irc/src/main/java/com/brewtab/irc/client/NickNameInUseException.java

+package com.brewtab.irc.client;
+
+public class NickNameInUseException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+}

File brewtab-irc/src/main/java/com/brewtab/irc/impl/ChannelImpl.java

+package com.brewtab.irc.impl;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import com.brewtab.irc.Connection;
+import com.brewtab.irc.User;
+import com.brewtab.irc.client.Channel;
+import com.brewtab.irc.client.ChannelListener;
+import com.brewtab.irc.client.Client;
+import com.brewtab.irc.messages.Message;
+import com.brewtab.irc.messages.MessageListener;
+import com.brewtab.irc.messages.MessageType;
+import com.brewtab.irc.messages.filter.MessageFilters;
+
+/**
+ * An IRC channel. Represents a connection to an IRC channel.
+ * 
+ * @author Christopher Thunes <cthunes@brewtab.com>
+ */
+class ChannelImpl implements MessageListener, Channel {
+    /* Channel name */
+    private String channelName;
+
+    /* IRC Client */
+    private Client client;
+
+    /* Associated IRCConnection object */
+    private Connection connection;
+
+    /* Set once the channel has been joined */
+    private CountDownLatch joined;
+
+    /* IRCChanneListners for this channel */
+    private List<ChannelListener> listeners;
+
+    /* List of nicks in the channel */
+    private List<String> names;
+
+    /**
+     * Instantiate a new IRCChannel object associated with the given IRCClient
+     * 
+     * @param client The client to operate with
+     * @param channelName The channel to join
+     */
+    public ChannelImpl(Client client, String channelName) {
+        this.client = client;
+        this.connection = client.getConnection();
+        this.channelName = channelName;
+        this.joined = new CountDownLatch(1);
+        this.listeners = new LinkedList<ChannelListener>();
+        this.names = new ArrayList<String>();
+
+        /*
+         * Build a compound filter collecting messages sent regarding this
+         * channel and include all QUIT messages. Some QUIT messages will
+         * concern this channel if it's a user in this channel QUITing
+         */
+
+        connection.addMessageListener(
+            MessageFilters.any(
+                // Messages targeted to the channel
+                MessageFilters.message(MessageType.PRIVMSG, channelName),
+                MessageFilters.message(MessageType.JOIN, channelName),
+                MessageFilters.message(MessageType.PART, channelName),
+                MessageFilters.message(MessageType.RPL_TOPIC, channelName),
+                MessageFilters.message(MessageType.RPL_NOTOPIC, channelName),
+                MessageFilters.message(MessageType.RPL_NAMREPLY, null, "=", channelName),
+                MessageFilters.message(MessageType.RPL_ENDOFNAMES, null, channelName),
+
+                // Messages which may be relevant to our channel
+                MessageFilters.message(MessageType.QUIT),
+                MessageFilters.message(MessageType.NICK)),
+            this);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#getName()
+     */
+    @Override
+    public String getName() {
+        return this.channelName;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#getClient()
+     */
+    @Override
+    public Client getClient() {
+        return this.client;
+    }
+
+    /**
+     * Perform the join to the channel. Returns once the join operation is
+     * complete
+     * 
+     * @return true if joined successfully, false otherwise
+     */
+    public boolean join() {
+        Message joinMessage = new Message(MessageType.JOIN, this.channelName);
+        List<Message> response;
+
+        try {
+            response = connection.request(
+                MessageFilters.message(MessageType.RPL_NAMREPLY, null, "=", channelName),
+                MessageFilters.message(MessageType.RPL_ENDOFNAMES, null, channelName),
+                joinMessage);
+        } catch (InterruptedException e) {
+            return false;
+        }
+
+        List<String> names = new ArrayList<String>();
+
+        for (Message message : response) {
+            if (message.getType() == MessageType.RPL_NAMREPLY) {
+                String[] args = message.getArgs();
+
+                for (String name : args[args.length - 1].split(" ")) {
+                    names.add(name.replaceFirst("^[@+]", ""));
+                }
+            }
+        }
+
+        this.names = names;
+
+        return true;
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#part(java.lang.String)
+     */
+    @Override
+    public void part(String reason) {
+        Message partMessage = new Message(MessageType.PART, this.channelName, reason);
+        this.client.getConnection().send(partMessage);
+        this.joined = new CountDownLatch(1);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#write(java.lang.String)
+     */
+    @Override
+    public void write(String text) {
+        Message privMessage = new Message(MessageType.PRIVMSG, this.channelName, text);
+        this.client.getConnection().send(privMessage);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#writeMultiple(java.lang.String)
+     */
+    @Override
+    public void writeMultiple(String... strings) {
+        Message[] privMessages = new Message[strings.length];
+        for (int i = 0; i < strings.length; i++) {
+            privMessages[i] = new Message(MessageType.PRIVMSG, this.channelName, strings[i]);
+        }
+        this.client.getConnection().send(privMessages);
+    }
+
+    /*
+     * (non-Javadoc)
+     * @see com.brewtab.irc.impl.IRCChannel#getNames()