package com.esri.geoevent.solutions.transport.irc.jerklib; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; import com.esri.geoevent.solutions.transport.irc.jerklib.Session.State; import com.esri.geoevent.solutions.transport.irc.jerklib.events.IRCEvent; import com.esri.geoevent.solutions.transport.irc.jerklib.listeners.WriteRequestListener; /** * A class for reading and writing to an IRC connection. * This class will also handle PING/PONG. * * @author mohadib * */ class Connection { private Logger log = Logger.getLogger(this.getClass().getName()); /* ConnectionManager for this Connection */ private final ConnectionManager manager; /* SocketChannel this connection will use for reading/writing */ private final SocketChannel socChannel; /* A Buffer for write request */ final List<WriteRequest> writeRequests = Collections.synchronizedList(new ArrayList<WriteRequest>()); /* ByteBuffer for readinging into */ private final ByteBuffer readBuffer = ByteBuffer.allocate(2048); /* indicates if an event fragment is waiting */ private boolean gotFragment; /* buffer for event fragments */ private final StringBuffer stringBuff = new StringBuffer(); /* actual hostname connected to */ private String actualHostName; /* Session Connection belongs to */ private final Session session; /** * @param manager * @param socChannel - socket channel to read from * @param session - Session this Connection belongs to */ Connection(ConnectionManager manager, SocketChannel socChannel, Session session) { this.manager = manager; this.socChannel = socChannel; this.session = session; } /** * Get profile use for this Connection * * @return the Profile */ Profile getProfile() { return session.getRequestedConnection().getProfile(); } /** * Sets the actual host name of this Connection. * @param name */ void setHostName(String name) { actualHostName = name; } /** * Gets actual hostname for Connection. * * @return hostname */ String getHostName() { return actualHostName; } /** * Adds a listener to be notified of all data written via this Connection * * @param request */ void addWriteRequest(WriteRequest request) { writeRequests.add(request); } /** * Called to finish the Connection Process * * @return true if fincon is successfull * @throws IOException */ boolean finishConnect() throws IOException { return socChannel.finishConnect(); } /** * Reads from connection and creates default IRCEvents that * are added to the ConnectionManager for relaying * * @return bytes read */ int read() { if (!socChannel.isConnected()) { log.severe("Read call while sochan.isConnected() == false"); return -1; } readBuffer.clear(); int numRead = 0; try { numRead = socChannel.read(readBuffer); } catch (Exception e) { e.printStackTrace(); session.disconnected(); } if (numRead == -1) { session.disconnected(); } if (session.getState() == State.DISCONNECTED || numRead <= 0) { return 0; } readBuffer.flip(); String tmpStr = new String(readBuffer.array(), 0, numRead); // read did not contain a \r\n if (tmpStr.indexOf("\r\n") == -1) { // append whole thing to buffer and set fragment flag stringBuff.append(tmpStr); gotFragment = true; return numRead; } // this read had a \r\n in it if (gotFragment) { // prepend fragment to front of current message tmpStr = stringBuff.toString() + tmpStr; stringBuff.delete(0, stringBuff.length()); gotFragment = false; } String[] strSplit = tmpStr.split("\r\n"); for (int i = 0; i < (strSplit.length - 1); i++) { manager.addToEventQueue(createDefaultIRCEvent(strSplit[i])); } String last = strSplit[strSplit.length - 1]; if (!tmpStr.endsWith("\r\n")) { // since string did not end with \r\n we need to // append the last element in strSplit to a stringbuffer // for next read and set flag to indicate we have a fragment waiting stringBuff.append(last); gotFragment = true; } else { manager.addToEventQueue(createDefaultIRCEvent(last)); } return numRead; } /** * Writes all requests in queue to server * * @return number bytes written */ int doWrites() { int amount = 0; List<WriteRequest> tmpReqs = new ArrayList<WriteRequest>(); synchronized (writeRequests) { tmpReqs.addAll(writeRequests); writeRequests.clear(); } for (WriteRequest request : tmpReqs) { String data; if (request.getType() == WriteRequest.Type.CHANNEL_MSG) { data = "PRIVMSG " + request.getChannel().getName() + " :" + request.getMessage() + "\r\n"; } else if (request.getType() == WriteRequest.Type.PRIVATE_MSG) { data = "PRIVMSG " + request.getNick() + " :" + request.getMessage() + "\r\n"; } else { data = request.getMessage(); if (!data.endsWith("\r\n")) { data += "\r\n"; } } byte[] dataArray = data.getBytes(); ByteBuffer buff = ByteBuffer.allocate(dataArray.length); buff.put(dataArray); buff.flip(); try { amount += socChannel.write(buff); } catch (IOException e) { e.printStackTrace(); session.disconnected(); } if (session.getState() == State.DISCONNECTED) { return amount; } fireWriteEvent(request); } return amount; } /** * Send a ping */ void ping() { writeRequests.add(new WriteRequest("PING " + actualHostName + "\r\n", session)); session.pingSent(); } /** * Send a pong * * @param event , the Ping event */ void pong(IRCEvent event) { session.gotResponse(); String data = event.getRawEventData().substring(event.getRawEventData().lastIndexOf(":") + 1); writeRequests.add(new WriteRequest("PONG " + data + "\r\n", session)); } /** * Alert connection a pong was received */ void gotPong() { session.gotResponse(); } /** * Close connection * * @param quitMessage */ void quit(String quitMessage) { try { if (quitMessage == null) quitMessage = ""; WriteRequest request = new WriteRequest("QUIT :" + quitMessage + "\r\n", session); writeRequests.add(request); // clear out write queue doWrites(); socChannel.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Fires a write request to all write listeners * * @param request */ void fireWriteEvent(WriteRequest request) { for (WriteRequestListener listener : manager.getWriteListeners()) { listener.receiveEvent(request); } } /** * Create a default irc event * * @param rawData * @return */ private IRCEvent createDefaultIRCEvent(final String rawData) { return new IRCEvent() { public Session getSession() { return session; } public String getRawEventData() { return rawData; } public Type getType() { return IRCEvent.Type.DEFAULT; } }; } }