package com.aelfengard.i3;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import com.aelfengard.i3.packet.I3Packet;
public class I3Connection {
static final String DEFAULT_HOST = "us-1.i3.intermud.org";
static final int DEFAULT_PORT = 9000;
private final BlockingQueue<I3Packet> writeQueue = new LinkedBlockingQueue<I3Packet>();
private final Selector selector;
private final SelectionKey key;
private final SocketChannel channel;
private final Set<I3PacketListener> listeners = new HashSet<I3PacketListener>();
public I3Connection() throws IOException {
this(DEFAULT_HOST, DEFAULT_PORT);
}
public I3Connection(String host, int port) throws IOException {
channel = SocketChannel.open(new InetSocketAddress(host, port));
selector = Selector.open();
channel.configureBlocking(false);
key = channel.register(selector, SelectionKey.OP_READ);
new ConnectionThread().start();
}
public void send(I3Packet packet) {
writeQueue.add(packet);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
selector.wakeup();
}
private void packetReceived(I3Packet packet) {
Set<I3PacketListener> set;
synchronized(this) {
set = new HashSet<I3PacketListener>(listeners);
}
for (I3PacketListener l : set) {
l.packetReceived(this, packet);
}
}
private void unrecognizedPacket(String packet) {
Set<I3PacketListener> set;
synchronized(this) {
set = new HashSet<I3PacketListener>(listeners);
}
for (I3PacketListener l : set) {
l.unrecognizedPacket(this, packet);
}
}
private void connectionClosed() {
Set<I3PacketListener> set;
synchronized(this) {
set = new HashSet<I3PacketListener>(listeners);
}
for (I3PacketListener l : set) {
l.connectionClosed(this);
}
}
public synchronized void addPacketListener(I3PacketListener l) {
listeners.add(l);
}
public synchronized void removePacketListener(I3PacketListener l) {
listeners.remove(l);
}
private class ConnectionThread extends Thread {
private final ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
private ByteBuffer readBuffer = lengthBuffer; private ByteBuffer[] writeBuffers = null;
public ConnectionThread() {
setDaemon(true);
}
public void run() {
try {
while (true) {
selector.select();
selector.selectedKeys().clear();
if (key.isReadable()) {
while (true) {
channel.read(readBuffer);
if (readBuffer.hasRemaining()) {
break; }
if (readBuffer == lengthBuffer) {
readBuffer = ByteBuffer.allocate(readBuffer.getInt(0));
}
else {
byte[] b = readBuffer.array();
removeNonPrintables(b);
String packetString = new String(b, "ISO-8859-1");
if (System.getProperty("i3.debug") != null) {
System.err.println("I3 Packet Received: " + packetString);
}
I3Packet packet = I3Packet.forString(packetString);
if (packet == null) {
unrecognizedPacket(packetString);
}
else {
packetReceived(packet);
}
lengthBuffer.clear();
readBuffer = lengthBuffer;
}
}
}
else if (key.isWritable()) {
while (true) {
if (writeBuffers != null) {
channel.write(writeBuffers);
if (writeBuffers[0].hasRemaining() || writeBuffers[1].hasRemaining()) {
break; }
}
I3Packet packet;
try {
packet = writeQueue.remove();
String packetString = packet.toString();
if (System.getProperty("i3.debug") != null) {
System.err.println("I3 Packet Sent: " + packetString);
}
byte[] b = packetString.getBytes("ISO-8859-1");
removeNonPrintables(b);
writeBuffers = new ByteBuffer[] {
ByteBuffer.allocate(4),
ByteBuffer.wrap(b)};
writeBuffers[0].putInt(0, b.length);
}
catch (NoSuchElementException ex) {
writeBuffers = null;
key.interestOps(SelectionKey.OP_READ);
break;
}
}
}
}
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
try {
channel.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
try {
selector.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
connectionClosed();
}
}
}
private static void removeNonPrintables(byte[] b) {
for (int i = 0; i < b.length; i++) {
if (b[i] < 32 || (b[i] >= 127 && b[i] <= 159)) {
b[i] = '?';
}
}
}
}