package org.playorm.nio.impl.cm.basic.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.playorm.nio.api.channels.DatagramChannel;
import org.playorm.nio.api.channels.NioException;
import org.playorm.nio.api.handlers.DatagramListener;
import org.playorm.nio.api.libs.ChannelSession;
import org.playorm.nio.api.libs.FactoryCreator;
/**
*/
public class DatagramChannelImpl implements DatagramChannel
{
private static final Logger log = Logger.getLogger(DatagramChannelImpl.class.getName());
private static final FactoryCreator CREATOR = FactoryCreator.createFactory(null);
private static final DatagramListener NULL_LISTENER = new NullDatagramListener();
private ChannelSession session;
private DatagramSocket socket;
private String id;
private DatagramListener listener = NULL_LISTENER;
private ByteBuffer buffer;
private ReaderThread readerThread;
private boolean shutDownThread = false;
private String name;
public DatagramChannelImpl(String id, int bufferSize) {
this.id = "["+id+"] ";
session = CREATOR.createSession(this);
buffer = ByteBuffer.allocate(bufferSize);
}
/**
* @see org.playorm.nio.api.channels.Channel#registerForReads(org.playorm.nio.api.handlers.DataListener)
*/
public void registerForReads(DatagramListener listener) {
this.listener = listener;
}
/**
* @see org.playorm.nio.api.channels.Channel#unregisterForReads()
*/
public void unregisterForReads() {
listener = NULL_LISTENER;
}
/**
* @see org.playorm.nio.api.channels.Channel#getSession()
*/
public ChannelSession getSession() {
return session;
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#setReuseAddress(boolean)
*/
public void setReuseAddress(boolean b) {
if(socket == null)
throw new IllegalStateException(id+"Must bind socket before any operations can be called");
try {
socket.setReuseAddress(b);
} catch (SocketException e) {
throw new NioException(e);
}
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#bind(java.net.SocketAddress)
*/
public void bind(SocketAddress addr) {
try {
socket = new DatagramSocket(addr);
readerThread = new ReaderThread();
readerThread.start();
} catch(IOException e) {
throw new NioException(e);
}
}
public void setId(Object id) {
}
public Object getId() {
return id;
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#isBlocking()
*/
public boolean isBlocking() {
return true;
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#close()
*/
public void close() {
if(socket == null)
return;
//stop the thread first!!!!
if(Thread.currentThread().equals(readerThread)) {
//since we are on the reader thread, we are not stuck on a DatagramPacket.receive call, just
//close and return
shutDownThread = true;
socket.close();
return;
}
//otherwise, we must interrupt the call to receive
shutDownThread = true;
//useless on a DatagramSocket for some reason......
//readerThread.interrupt();
socket.close();
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#isClosed()
*/
public boolean isClosed() {
if(socket == null)
throw new IllegalStateException(id+"Must bind socket before any operations can be called");
return socket.isClosed();
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#isBound()
*/
public boolean isBound() {
if(socket == null)
return false;
return socket.isBound();
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#getLocalAddress()
*/
public InetSocketAddress getLocalAddress() {
if(!socket.isBound())
throw new IllegalStateException(id+"Must bind socket before any operations can be called");
log.fine("get local="+socket.getLocalPort());
return new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort());
}
public void oldWrite(SocketAddress addr, ByteBuffer b) {
try {
oldWriteImpl(addr,b);
} catch (IOException e) {
throw new NioException(e);
}
}
/**
* @throws IOException
* @see org.playorm.nio.api.channels.DatagramChannel#oldWrite(java.net.SocketAddress, java.nio.ByteBuffer)
*/
private void oldWriteImpl(SocketAddress addr, ByteBuffer b) throws IOException {
if(socket == null)
throw new IllegalStateException(id+"Must bind socket before any operations can be called");
DatagramPacket packet = new DatagramPacket(b.array(), b.position(), b.limit()-b.position(), addr);
if(log.isLoggable(Level.FINER))
log.finer("size="+(b.limit()-b.position())+" addr="+addr);
socket.send(packet);
}
private void doThreadWork() {
while(!shutDownThread) {
readPackets();
}
if(log.isLoggable(Level.FINER))
log.finer(id+"reader thread ending");
}
private void readPackets() {
InetSocketAddress fromAddr = null;
try {
buffer.clear();
DatagramPacket packet = new DatagramPacket(buffer.array(), buffer.remaining());
socket.receive(packet);
fromAddr = (InetSocketAddress)packet.getSocketAddress();
int offset = packet.getOffset();
int len = packet.getLength();
buffer.position(offset);
buffer.limit(offset+len);
fireToListener(this, fromAddr, buffer);
} catch(Throwable e) {
//ignore an SocketException when shutDownThread.
if(e instanceof SocketException && shutDownThread)
return;
log.log(Level.WARNING, id+"Exception processing packet", e);
fireFailure(fromAddr, buffer, e);
}
}
/**
* @param fromAddr
*/
private void fireToListener(DatagramChannel c, InetSocketAddress fromAddr, ByteBuffer b) {
try {
listener.incomingData(c, fromAddr, b);
if(b.remaining() > 0) {
log.warning(id+"Client="+listener+" did not read all the data from the buffer");
}
} catch(Throwable e) {
log.log(Level.WARNING, id+"Exception in client's listener", e);
}
}
/**
* @param fromAddr
* @param e
*/
private void fireFailure(InetSocketAddress fromAddr, ByteBuffer data, Throwable e) {
try {
listener.failure(this, fromAddr, data, e);
} catch(Throwable ee) {
log.log(Level.WARNING, id+"Exception notifying client of exception", ee);
}
}
private class ReaderThread extends Thread {
/**
* @see java.lang.Thread#run()
*/
@Override
public void run()
{
doThreadWork();
}
/**
*
*/
public void stopRunning()
{
shutDownThread = true;
}
}
private static class NullDatagramListener implements DatagramListener {
/**
* @see org.playorm.nio.api.handlers.DatagramListener
* #incomingData(org.playorm.nio.api.channels.DatagramChannel, java.net.InetSocketAddress, java.nio.ByteBuffer)
*/
public void incomingData(DatagramChannel channel, InetSocketAddress fromAddr, ByteBuffer b) throws IOException {
}
/**
* @see org.playorm.nio.api.handlers.DatagramListener
* #failure(org.playorm.nio.api.channels.DatagramChannel, java.net.InetSocketAddress, java.nio.ByteBuffer, java.lang.Exception)
*/
public void failure(DatagramChannel channel, InetSocketAddress fromAddr, ByteBuffer data, Throwable e) {
log.log(Level.WARNING, "Exception", e);
}
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#setName(java.lang.String)
*/
public void setName(String name)
{
this.name = name;
}
/**
* @see org.playorm.nio.api.channels.RegisterableChannel#getName()
*/
public String getName()
{
return name;
}
}