package de.tum.in.www1.jReto.niotools;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.InterruptibleChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Method overview:
*
* dispatch - runs a runnable in the run loop of the dispatcher (ie. in the dispatcher's thread). Can be called from any thread.
*
* These methods need to be called from the dispatcher's thread.
*
* start - starts the run loop, does not return until stop is called (either by one of the registered callbacks or by a dispatched runnable)
* stop - stops the run loop
* register* - register a callback for a certain event of a SocketChannel.
* unregister* - unregisters a callback for a certain event of a SocketChannel.
* */
public class Dispatcher {
public static interface AcceptHandler<T> {
public void onAcceptable(T socket);
}
public static interface ConnectHandler<T> {
public void onConnectable(T socket);
}
public static interface WriteHandler<T> {
public void onWriteable(T socket);
}
public static interface ReadHandler<T> {
public void onReadable(T socket);
}
public static interface Handler<T> extends AcceptHandler<T>, ConnectHandler<T>, WriteHandler<T>, ReadHandler<T> {}
private final static int[] ALL_OPERATIONS = {SelectionKey.OP_ACCEPT, SelectionKey.OP_CONNECT, SelectionKey.OP_READ, SelectionKey.OP_WRITE};
private final Selector selector;
private final ConcurrentHashMap<Integer, HashMap<SelectableChannel, HandlerDispatcher<?>>> handlersByChannelByOperation;
private final Executor executor;
private final LinkedBlockingQueue<Runnable> runnables;
private boolean isCurrentlyRunning = false;
public Dispatcher(Executor executor) throws IOException {
this.selector = Selector.open();
this.executor = executor;
this.runnables = new LinkedBlockingQueue<Runnable>();
this.handlersByChannelByOperation = new ConcurrentHashMap<Integer, HashMap<SelectableChannel,HandlerDispatcher<?>>>();
for (int operation : ALL_OPERATIONS) this.handlersByChannelByOperation.put(operation, new HashMap<SelectableChannel, HandlerDispatcher<?>>());
}
private void dispatch(Runnable runnable) {
this.runnables.add(runnable);
this.selector.wakeup();
}
/**
* Call these methods only from the Dispatcher's thread.
* If you want to perform one of these from another thread, use dispatch to do it.
* */
public <T extends SelectableChannel> void registerHandler(Handler<T> handler, T channel) {
this.registerAcceptHandler(handler, channel);
this.registerConnectHandler(handler, channel);
this.registerReadHandler(handler, channel);
this.registerWriteHandler(handler, channel);
}
public <T extends SelectableChannel> void registerAcceptHandler(AcceptHandler<T> handler, T channel) {
register(channel, SelectionKey.OP_ACCEPT, new HandlerAcceptDispatcher<T>(handler, channel));
}
public <T extends SelectableChannel> void registerConnectHandler(ConnectHandler<T> handler, T channel) {
register(channel, SelectionKey.OP_CONNECT, new HandlerConnectDispatcher<T>(handler, channel));
}
public <T extends SelectableChannel> void registerReadHandler(ReadHandler<T> handler, T channel) {
register(channel, SelectionKey.OP_READ, new HandlerReadDispatcher<T>(handler, channel));
}
public <T extends SelectableChannel> void registerWriteHandler(WriteHandler<T> handler, T channel) {
register(channel, SelectionKey.OP_WRITE, new HandlerWriteDispatcher<T>(handler, channel));
}
public void unregister(SelectableChannel channel) {
unregisterAccept(channel);
unregisterConnect(channel);
unregisterRead(channel);
unregisterWrite(channel);
}
public void unregisterAccept(SelectableChannel channel) {
unregister(channel, SelectionKey.OP_ACCEPT);
}
public void unregisterConnect(SelectableChannel channel) {
unregister(channel, SelectionKey.OP_CONNECT);
}
public void unregisterRead(SelectableChannel channel) {
unregister(channel, SelectionKey.OP_READ);
}
public void unregisterWrite(SelectableChannel channel) {
unregister(channel, SelectionKey.OP_WRITE);
}
/**
* Registration has to happen in the thread that does select. So the dispatcher has it's own little action queue so we can
* do the registration in that thread.
* */
private <T extends SelectableChannel> void register(final T channel, final int operation, final HandlerDispatcher<?> handlerDispatcher) {
if (channel == null) throw new IllegalArgumentException("channel may not be null");
if (!channel.isOpen()) throw new IllegalArgumentException("channel may not be closed");
this.dispatch(new Runnable() {
@Override
public void run() {
try {
Dispatcher.this.handlersByChannelByOperation.get(operation).put(channel, handlerDispatcher);
int interestOps = 0;
SelectionKey key = channel.keyFor(selector);
if (key != null) {
interestOps = key.interestOps();
}
final int registeredInterestOps = interestOps;
channel.register(selector, operation | registeredInterestOps);
} catch (ClosedChannelException e) {
System.err.println("Could not register operation with channel because the channel is closed.");
Dispatcher.this.handlersByChannelByOperation.get(operation).remove(channel);
e.printStackTrace();
}
}
});
}
private void unregister(final SelectableChannel channel, final int operation) {
this.dispatch(new Runnable() {
@Override
public void run() {
SelectionKey key = channel.keyFor(selector);
if (key != null && key.isValid()) {
int ops = key.interestOps();
ops &= ~operation;
key.interestOps(ops);
}
Dispatcher.this.handlersByChannelByOperation.get(operation).remove(channel);
}
});
}
public void start() {
if (this.isCurrentlyRunning) {
System.err.println("Attempted to start a dispatcher that is already running.");
return;
}
this.isCurrentlyRunning = true;
new Thread(new Runnable() {
@Override
public void run() {
Dispatcher.this.loop();
}
}).start();
}
public void stop() {
this.isCurrentlyRunning = false;
this.selector.wakeup();
}
public void loop() {
while (this.isCurrentlyRunning) {
int readyChannels = 0;
try {
readyChannels = selector.select();
while (!this.runnables.isEmpty()) {
this.runnables.take().run();
}
} catch (IOException e) {
this.isCurrentlyRunning = true;
System.err.println("Dispatcher stopped due to IO Exception.");
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (readyChannels == 0) continue;
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while(keyIterator.hasNext()) {
final SelectionKey key = keyIterator.next();
for (final int operation : ALL_OPERATIONS) {
if(key.isValid() && (key.readyOps() & operation) != 0) {
this.executor.execute(new Runnable() {
@Override
public void run() {
HandlerDispatcher<?> dispatcher = Dispatcher.this.handlersByChannelByOperation.get(operation).get(key.channel());
if (dispatcher != null) {
dispatcher.dispatch();
}
}
});
}
if (!this.isCurrentlyRunning) break;
}
keyIterator.remove();
}
}
}
private static abstract class HandlerDispatcher<T extends InterruptibleChannel> {
T socket;
public HandlerDispatcher(T socket) {
if (socket == null) throw new IllegalArgumentException("socket may not be null");
this.socket = socket;
}
public abstract void dispatch();
}
private static class HandlerAcceptDispatcher<T extends InterruptibleChannel> extends HandlerDispatcher<T> {
AcceptHandler<T> handler;
public HandlerAcceptDispatcher(AcceptHandler<T> handler, T socket) {
super(socket);
if (handler == null) throw new IllegalArgumentException("handler may not be null");
this.handler = handler;
}
public void dispatch() {
this.handler.onAcceptable(this.socket);
}
}
private static class HandlerConnectDispatcher<T extends InterruptibleChannel> extends HandlerDispatcher<T> {
ConnectHandler<T> handler;
public HandlerConnectDispatcher(ConnectHandler<T> handler, T socket) {
super(socket);
if (handler == null) throw new IllegalArgumentException("handler may not be null");
this.handler = handler;
}
public void dispatch() {
this.handler.onConnectable(this.socket);
}
}
private static class HandlerReadDispatcher<T extends InterruptibleChannel> extends HandlerDispatcher<T> {
ReadHandler<T> handler;
public HandlerReadDispatcher(ReadHandler<T> handler, T socket) {
super(socket);
if (handler == null) throw new IllegalArgumentException("handler may not be null");
this.handler = handler;
}
public void dispatch() {
this.handler.onReadable(this.socket);
}
}
private static class HandlerWriteDispatcher<T extends InterruptibleChannel> extends HandlerDispatcher<T> {
WriteHandler<T> handler;
public HandlerWriteDispatcher(WriteHandler<T> handler, T socket) {
super(socket);
if (handler == null) throw new IllegalArgumentException("handler may not be null");
this.handler = handler;
}
public void dispatch() {
this.handler.onWriteable(this.socket);
}
}
}