/**
*
*/
package com.trendrr.oss.networking;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.trendrr.oss.concurrent.TrendrrLock;
import com.trendrr.oss.exceptions.TrendrrDisconnectedException;
import com.trendrr.oss.exceptions.TrendrrException;
import com.trendrr.oss.exceptions.TrendrrNoCallbackException;
import com.trendrr.oss.exceptions.TrendrrOverflowException;
/**
* @author Dustin Norlander
* @created Mar 11, 2011
*
*/
public class SocketChannelWrapper {
protected static Log log = LogFactory.getLog(SocketChannelWrapper.class);
protected SocketChannel channel = null;
protected AsynchBuffer buffer = null;
protected SelectorThread thread = null;
protected TrendrrLock threadInit = new TrendrrLock();
protected ConcurrentLinkedQueue<ByteBuffer> writes = new ConcurrentLinkedQueue<ByteBuffer>();
protected boolean closed = false;
protected ByteReadCallback closeListener = null;
AtomicInteger numQueued = new AtomicInteger(0);
public SocketChannelWrapper(SocketChannel channel) {
this.channel = channel;
this.buffer = new AsynchBuffer();
}
public void readUntil(String delimiter, Charset charset, boolean stripDelimiter, StringReadCallback callback) {
this.buffer.readUntil(delimiter, charset, stripDelimiter, callback);
this.buffer.process();//attempt to read directly from the already buffered bytes
this.notifyChange();
}
/**
* sets a callback that will get called on disconnect. the remaining bytes will be sent, or a 0 length array if no unread bytes remain.
* No exception will be sent though.
* @param closeListener
*/
public void setCloseListener(ByteReadCallback closeListener) {
this.closeListener = closeListener;
}
/**
* reads until the requested string is found.
* @param delimiter
* @param charset
* @return
* @throws TrendrrException
*/
public String readUntil(String delimiter, Charset charset, boolean stripDelimiter) throws TrendrrException{
SynchronousReadCallback callback = new SynchronousReadCallback();
this.readUntil(delimiter, charset, stripDelimiter, callback);
callback.awaitResponse();
if (callback.exception != null) {
throw callback.exception;
}
return callback.stringResult;
}
public void readBytes(int numBytes, ByteReadCallback callback) {
this.buffer.readBytes(numBytes, callback);
this.buffer.process();//attempt to read from the already buffered bytes
this.notifyChange();
}
public byte[] readBytes(int numBytes) throws TrendrrException{
SynchronousReadCallback callback = new SynchronousReadCallback();
this.readBytes(numBytes, callback);
callback.awaitResponse();
if (callback.exception != null) {
throw callback.exception;
}
return callback.byteResult;
}
/**
* reads bytes until the end of the stream.
* @return
* @throws TrendrrException
*/
public byte[] readFully() throws TrendrrException{
SynchronousReadCallback callback = new SynchronousReadCallback();
this.readFully(callback);
callback.awaitResponse();
if (callback.exception != null) {
throw callback.exception;
}
return callback.byteResult;
}
/**
* reads until the end of the stream
* @param callback
*/
public void readFully(ByteReadCallback callback) {
this.readBytes(ByteReadFullyCallback.NUMBYTES, new ByteReadFullyCallback(this, callback));
}
/**
* writes to the socketchannel, throws an exception if there are > maxQueued writes waiting to be written.
* @param buf
* @param maxQueued
* @throws TrendrrOverflowException
*/
public void write(ByteBuffer buf, int maxQueued) throws TrendrrOverflowException {
if (this.numQueued.get() > maxQueued) {
throw new TrendrrOverflowException("More then " + maxQueued + " messages waiting to be written");
}
this.write(buf);
}
/**
* writes to the socketchannel, throws an exception if there are > maxQueued writes waiting to be written.
* @param buf
* @param maxQueued
* @throws TrendrrOverflowException
*/
public void write(byte[] bytes, int maxQueued) throws TrendrrOverflowException {
this.write(ByteBuffer.wrap(bytes), maxQueued);
}
public void write(ByteBuffer buf) {
this.writes.add(buf);
this.notifyChange();
this.numQueued.incrementAndGet();
}
public void write(byte[] bytes) {
this.write(ByteBuffer.wrap(bytes));
}
private void notifyChange() {
//alerts the selector that we want to read or write
try {
this.startThreadIfNeeded();
} catch (IOException e) {
log.error("Caught", e);
}
this.thread.registerChange(this);
}
/**
* returns true if there are any writes waiting.
* @return
*/
public boolean hasWrites() {
return !this.writes.isEmpty();
}
/**
* returns true if any reads are waiting.
* @return
*/
public boolean hasReads() {
if (this.buffer == null)
return false;
return this.buffer.hasCallbacksWaiting();
}
public SocketChannel getChannel() {
return this.channel;
}
Queue<ByteBuffer> getWrites() {
return this.writes;
}
public int getWriteQueueSize() {
return this.numQueued.get();
}
/**
* Attempts to read from the network, and process any callbacks.
* does nothing if no callbacks have been registered.
*
* @throws TrendrrNoCallbackException
* @throws TrendrrDisconnectedException
* @throws TrendrrException
*/
public void doRead() throws TrendrrNoCallbackException, TrendrrDisconnectedException, TrendrrException {
int numRead = 1;
while (numRead > 0 && this.buffer.hasCallbacksWaiting()) {
numRead = this.buffer.read(this.channel);
this.buffer.process();
}
}
/**
* attempts to process the remaining callbacks, then closes the channel and
* cleans up any resources.
*/
public synchronized void close() {
if (closed) {
log.warn("Already closed!");
return;
}
try { this.buffer.process(); } catch (Exception x) {
log.debug("Caught", x);
}
try { this.channel.close();} catch (Exception x) {
log.debug("Caught", x);
}
try { this.buffer.close();} catch (Exception x) {
log.debug("Caught", x);
}
if (this.thread != null) {
this.thread.unregister(this);
}
this.buffer = null;
this.thread = null;
this.closed = true;
if (!this.writes.isEmpty()) {
log.warn("clearing " + this.numQueued.get() + " writes");
}
this.writes.clear();
this.numQueued.set(0);
}
public boolean isClosed() {
return this.closed;
}
private void startThreadIfNeeded() throws IOException {
if (threadInit.lockOnce()) {
try {
this.thread = SelectorThread.registerChannel(this);
} finally {
threadInit.unlock();
}
}
}
}