/* * JLibs: Common Utilities for Java * Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package jlibs.nio; import java.io.IOException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.nio.channels.SelectionKey; import static java.nio.channels.SelectionKey.OP_READ; import static java.nio.channels.SelectionKey.OP_WRITE; import static jlibs.nio.Debugger.IO; import static jlibs.nio.Debugger.println; /** * @author Santhosh Kumar Tekuri */ public final class Socket implements Transport{ private static final SocketTimeoutException SOCKET_TIMEOUT_EXCEPTION = new SocketTimeoutException(); static{ SOCKET_TIMEOUT_EXCEPTION.setStackTrace(new StackTraceElement[0]); } private final NBStream channel; protected final SelectionKey selectionKey; public Socket(NBStream channel, SelectionKey selectionKey){ this.channel = channel; this.selectionKey = selectionKey; reader = channel.selectable instanceof ScatteringByteChannel ? (ScatteringByteChannel)channel.selectable : null; writer = channel.selectable instanceof GatheringByteChannel ? (GatheringByteChannel)channel.selectable : null; } @Override public NBStream channel(){ return channel; } /*-------------------------------------------------[ Source ]---------------------------------------------------*/ private Input.Listener inputListener; @Override public Input.Listener getInputListener(){ return inputListener; } @Override public void setInputListener(Input.Listener listener){ inputListener = listener; } private final ScatteringByteChannel reader; Input peekIn = this; boolean peekInInterested; private boolean eof; @Override public void addReadInterest(){ if(peekIn==this) peekInInterested = true; if(newInterests==-1){ if(IO) println(selectable()+".addInterestOps(R)"); selectionKey.interestOps(selectionKey.interestOps()|OP_READ); if(channel.getTimeout()>0) channel.reactor.startTimer(channel, channel.getTimeout()); }else newInterests |= OP_READ; } @Override public void wakeupReader(){ if(inputListener!=null){ peekInInterested = true; channel.wakeup(); } } @Override public int read(ByteBuffer dst) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; int read = reader.read(dst); eof = read==-1; return read; } @Override public long read(ByteBuffer[] dsts) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; long read = reader.read(dsts); eof = read==-1; return read; } @Override public long read(ByteBuffer[] dsts, int offset, int length) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; long read = reader.read(dsts, offset, length); eof = read==-1; return read; } @Override public long transferTo(long position, long count, FileChannel target) throws IOException{ return target.transferFrom(reader, position, count); } @Override public long available(){ return 0; } @Override public boolean eof(){ return eof; } @Override public Output detachOutput(){ return this; } /*-------------------------------------------------[ Sink ]---------------------------------------------------*/ private Output.Listener outputListener; @Override public Output.Listener getOutputListener(){ return outputListener; } @Override public void setOutputListener(Output.Listener listener){ outputListener = listener; } private final GatheringByteChannel writer; Output peekOut = this; boolean peekOutInterested; @Override public void addWriteInterest(){ if(peekOut==this) peekOutInterested = true; if(newInterests==-1){ if(IO) println(selectable()+".addInterestOps(W)"); selectionKey.interestOps(selectionKey.interestOps()|OP_WRITE); if(channel.getTimeout()>0) channel.reactor.startTimer(channel, channel.getTimeout()); }else newInterests |= OP_WRITE; } @Override public void wakeupWriter(){ if(outputListener!=null){ peekOutInterested = true; channel.wakeup(); } } @Override public int write(ByteBuffer src) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; return writer.write(src); } @Override public long write(ByteBuffer[] srcs) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; return writer.write(srcs); } @Override public long write(ByteBuffer[] srcs, int offset, int length) throws IOException{ if(timeout) throw SOCKET_TIMEOUT_EXCEPTION; return writer.write(srcs, offset, length); } @Override public long transferFrom(FileChannel src, long position, long count) throws IOException{ return src.transferTo(position, count, writer); } @Override @Trace(condition=false) public boolean flush() throws IOException{ return true; } @Override public Input detachInput(){ return this; } /*-------------------------------------------------[ Closeable ]---------------------------------------------------*/ @Override public boolean isOpen(){ return channel.selectable.isOpen(); } @Override public void close() throws IOException{ if(isOpen()){ channel.closing(); channel.selectable.close(); } } /*-------------------------------------------------[ Process ]---------------------------------------------------*/ private int newInterests = -1; private boolean timeout; void process(boolean timeout){ this.timeout = timeout; int readyOps = selectionKey.readyOps(); int socketInterests = selectionKey.interestOps(); int oldInterests = newInterests = selectionKey.interestOps()&~readyOps; try{ boolean peekSourceInterested = this.peekInInterested; boolean peekSinkInterested = this.peekOutInterested; this.peekInInterested = this.peekOutInterested = false; if(peekSourceInterested){ boolean notify = false; if(timeout) notify = true; else if((readyOps&OP_READ)!=0) notify = true; else if((readyOps&OP_WRITE)!=0 && !peekSinkInterested) notify = true; if(notify){ try{ if(inputListener !=null) inputListener.process(peekIn); }catch(Throwable thr){ channel.reactor.handleException(thr); } } } if(channel.isOpen() && peekSinkInterested){ boolean notify = false; if(timeout) notify = true; else if((readyOps&OP_WRITE)!=0) notify = true; else if((readyOps&OP_READ)!=0 && !peekSourceInterested) notify = true; if(notify){ try{ if(outputListener !=null) outputListener.process(peekOut); }catch(Throwable thr){ channel.reactor.handleException(thr); } } } }finally{ if(channel.isOpen()){ if(newInterests!=socketInterests){ if(IO) println(selectable()+".setInterestOps("+Debugger.ops(newInterests)+")"); selectionKey.interestOps(newInterests); } if(oldInterests!=newInterests && channel.getTimeout()>0) channel.reactor.startTimer(channel, channel.getTimeout()); newInterests = -1; } } } void wakeupNow(){ try{ if(peekInInterested){ peekInInterested = false; if(inputListener!=null) inputListener.process(peekIn); } }catch(Throwable thr){ channel.reactor.handleException(thr); } try{ if(peekOutInterested){ peekOutInterested = false; if(outputListener!=null) outputListener.process(peekOut); } }catch(Throwable thr){ channel.reactor.handleException(thr); } } private String selectable(){ String str = channel.toString(); return "Selectable"+str.substring(channel.getClass().getSimpleName().length()); } @Override public String toString(){ String str = channel.toString(); return "Socket"+str.substring(channel.getClass().getSimpleName().length()); } }