/*
* 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.listeners;
import jlibs.nio.*;
import jlibs.nio.filters.BufferInput;
import jlibs.nio.filters.ChunkedOutput;
import jlibs.nio.util.BufferAllocator;
import jlibs.nio.util.Buffers;
import jlibs.nio.util.UnpooledBufferAllocator;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;
import static jlibs.nio.Debugger.DEBUG;
import static jlibs.nio.Debugger.println;
/**
* @author Santhosh Kumar Tekuri
*/
public abstract class Task{
int firstOp;
protected IOListener listener;
protected Input in;
protected Output out;
protected Task(int firstOp){
this.firstOp = firstOp;
}
protected void init(IOListener listener, Input in, Output out){
this.listener = listener;
this.in = in;
this.out = out;
}
@Trace(condition=DEBUG, args="($1==1?'R':'W')")
protected abstract boolean process(int readyOp) throws IOException;
protected void cleanup(Throwable thr){}
@Trace(condition=DEBUG, args="$1+\", \"+$2")
protected int childTaskFinished(Task childTask, Throwable thr) throws Throwable{
if(thr!=null)
throw thr;
return firstOp;
}
/*-------------------------------------------------[ writeBuffer ]---------------------------------------------------*/
protected boolean write(ByteBuffer buffer) throws IOException{
while(buffer.hasRemaining()){
if(out.write(buffer)==0){
out.addWriteInterest();
return false;
}
}
return true;
}
protected boolean send(ByteBuffer buffer) throws IOException{
while(buffer.hasRemaining()){
if(out.write(buffer)==0){
out.addWriteInterest();
return false;
}
}
if(out.flush())
return true;
out.addWriteInterest();
return false;
}
protected boolean flush() throws IOException{
if(out.flush())
return true;
out.addWriteInterest();
return false;
}
/*-------------------------------------------------[ readBuffer ]---------------------------------------------------*/
protected boolean read(ByteBuffer buffer) throws IOException{
while(buffer.hasRemaining()){
int read = in.read(buffer);
if(read==0){
in.addReadInterest();
return false;
}else if(read==-1)
return true;
}
return true;
}
/*-------------------------------------------------[ writeBuffers ]---------------------------------------------------*/
private Buffers buffers;
private BufferAllocator allocator = Reactor.current().allocator;
protected void prepareFlush(Buffers buffers, boolean discard){
if(discard)
allocator = Reactor.current().allocator;
else{
buffers = buffers.copy();
allocator = UnpooledBufferAllocator.HEAP;
}
buffers.removeEmpty(allocator);
this.buffers = buffers;
}
protected void prepareFlush(Buffers buffers, BufferAllocator allocator){
this.allocator = allocator;
buffers.removeEmpty(allocator);
this.buffers = buffers;
}
protected boolean flushBuffers() throws IOException{
try{
while(buffers.length>0 && out.write(buffers.array, buffers.offset, buffers.length)>0)
buffers.removeEmpty(allocator);
if(buffers.length>0 || !out.flush()){
out.addWriteInterest();
return false;
}
flushBuffersDone();
return true;
}catch(Throwable thr){
flushBuffersDone();
throw thr;
}
}
private void flushBuffersDone(){
if(buffers.length>0)
allocator.free(buffers);
buffers = null;
allocator = Reactor.current().allocator;
}
/*-------------------------------------------------[ writeFile ]---------------------------------------------------*/
private FileChannel fileChannel;
private long fileOffset;
private long fileLength;
protected void prepareTransferFromFile(File file) throws IOException{
fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.READ);
fileOffset = 0;
fileLength = fileChannel.size();
if(out instanceof ChunkedOutput)
((ChunkedOutput)out).startChunk(fileLength);
}
protected boolean transferFromFile() throws IOException{
try{
while(fileLength>0){
long wrote = out.transferFrom(fileChannel, fileOffset, fileLength);
if(wrote==0){
out.addWriteInterest();
return false;
}else{
fileOffset += wrote;
fileLength -= wrote;
}
}
}catch(Throwable thr){
transferFromFileDone();
throw thr;
}
transferFromFileDone();
return true;
}
private void transferFromFileDone() throws IOException{
fileChannel.close();
fileChannel = null;
}
/*-------------------------------------------------[ readBuffers ]---------------------------------------------------*/
private ByteBuffer buffer;
protected boolean read(Buffers buffers) throws IOException{
// first-time
if(buffer==null)
buffer = allocator.allocate();
try{
while(true){
int read = in.read(buffer);
if(read==0){
in.addReadInterest();
return false;
}else if(read==-1)
break;
else if(!buffer.hasRemaining()){
buffer.flip();
buffers.append(buffer);
buffer = allocator.allocate();
}
}
}catch(Throwable thr){
try{
readBuffersDone(buffers);
}catch(Throwable suppressed){
thr.addSuppressed(suppressed);
}
throw thr;
}
readBuffersDone(buffers);
return true;
}
private void readBuffersDone(Buffers buffers) throws IOException{
if(buffer.position()==0)
allocator.free(buffer);
else{
buffer.flip();
buffers.append(buffer);
}
buffer = null;
in.close();
}
/*-------------------------------------------------[ readFixedLength ]---------------------------------------------------*/
private long readLimit;
protected void prepareReadFixedLength(long readLimit){
this.readLimit = readLimit;
}
protected boolean readFixedLength(Buffers buffers) throws IOException{
try{
while(true){
if(buffer!=null && !buffer.hasRemaining()){
buffer.flip();
buffers.append(buffer);
buffer = null;
}
if(buffer==null){
if(readLimit==0)
break;
buffer = allocator.allocate();
buffer.limit((int)Math.min(buffer.remaining(), readLimit));
readLimit -= buffer.remaining();
}
int read = in.read(buffer);
if(read==-1)
break;
else if(read==0){
in.addReadInterest();
return false;
}
}
}catch(Throwable thr){
try{
readFixedLengthDone(buffers);
}catch(Throwable suppressed){
thr.addSuppressed(suppressed);
}
throw thr;
}
readFixedLengthDone(buffers);
return true;
}
private void readFixedLengthDone(Buffers buffers){
if(buffer!=null){
if(buffer.position()==0)
allocator.free(buffer);
else{
buffer.flip();
buffers.append(buffer);
}
buffer = null;
}
}
/*-------------------------------------------------[ closeOutputs ]---------------------------------------------------*/
protected void closeInputs() throws IOException{
while(true){
if(in instanceof Transport)
return;
in.close();
in = in.detachInput();
}
}
protected boolean closeOutputs() throws IOException{
while(true){
if(out instanceof Transport){
if(out.flush())
return true;
out.addWriteInterest();
return false;
}else{
out.close();
if(out.flush())
out = out.detachOutput();
else{
out.addWriteInterest();
return false;
}
}
}
}
protected boolean closeOutput() throws IOException{
out.close();
if(out.flush())
return true;
else{
out.addWriteInterest();
return false;
}
}
protected boolean shutdown() throws IOException{
try{
closeInputs();
if(closeOutputs() && closeOutput()){
in.close();
return true;
}else
return false;
}catch(Throwable thr){
out.channel().shutdown();
in.channel().shutdown();
throw thr;
}
}
/*-------------------------------------------------[ drainInputs ]---------------------------------------------------*/
protected boolean drainInput(boolean close) throws IOException{
if(buffer==null)
buffer = allocator.allocate();
try{
while(true){
int read;
if(in instanceof BufferInput){
((BufferInput)in).drainBuffer();
read = -1;
}else
read = in.read(buffer);
if(read==0){
in.addReadInterest();
return false;
}else if(read==-1){
if(close)
in.close();
drainDone();
return true;
}else
buffer.clear();
}
}catch(Throwable thr){
drainDone();
throw thr;
}
}
protected boolean drainInputs() throws IOException{
if(buffer==null)
buffer = allocator.allocate();
try{
while(true){
if(BufferInput.getOriginal(in) instanceof Transport){
while(in instanceof BufferInput){
BufferInput bin = (BufferInput)in;
if(bin.canDetach())
in = in.detachInput();
else
break;
}
drainDone();
return true;
}
while(true){
int read;
if(in instanceof BufferInput){
((BufferInput)in).drainBuffer();
read = -1;
}else
read = in.read(buffer);
if(read==0){
in.addReadInterest();
return false;
}else if(read==-1){
in.close();
in = in.detachInput();
break;
}else
buffer.clear();
}
}
}catch(Throwable thr){
drainDone();
throw thr;
}
}
private void drainDone(){
allocator.free(buffer);
buffer = null;
}
/*-------------------------------------------------[ switch-IO ]---------------------------------------------------*/
private Input _in;
private Output _out;
protected void switchIO(Input in, Output out){
if(this.in!=in){
_in = this.in;
this.in = in;
in.setInputListener(listener);
}
if(this.out!=out){
_out = this.out;
this.out = out;
out.setOutputListener(listener);
}
}
protected void revertIO(){
if(_in!=null){
if(in.getInputListener()==listener)
in.setInputListener(null);
in = _in;
_in = null;
}
if(_out!=null){
if(out.getOutputListener()==listener)
out.setOutputListener(null);
out = _out;
_out = null;
}
}
/*-------------------------------------------------[ pumping ]---------------------------------------------------*/
private boolean flushNeeded;
protected void preparePump(Buffers backup){
buffers = backup;
buffer = allocator.allocate();
flushNeeded = false;
}
protected boolean doPump(int readyOp) throws IOException{
boolean flushing = false;
try{
if(readyOp==OP_WRITE && flushNeeded){
flushing = true;
if(!out.flush()){
out.addWriteInterest();
return false;
}
flushing = false;
flushNeeded = false;
readyOp = OP_READ;
}
while(true){
if(readyOp==OP_READ){
int read = in.read(buffer);
if(read==0){
in.addReadInterest();
if(flushNeeded){
flushing = true;
if(!out.flush()){
out.addWriteInterest();
return false;
}
flushing = false;
flushNeeded = false;
}
return false;
}else if(read==-1)
break;
// flip -------------------
if(buffers==null)
buffer.flip();
else{
buffer.limit(buffer.position());
buffer.position(buffer.limit()-read);
}
readyOp = OP_WRITE;
flushNeeded = false;
}
if(buffer.hasRemaining()){
do{
if(out.write(buffer)==0){
out.addWriteInterest();
return false;
}
}while(buffer.hasRemaining());
flushNeeded = true;
// clear -------------------
if(buffers==null)
buffer.clear();
else{
if(buffer.limit()==buffer.capacity()){
buffer.flip();
buffers.append(buffer);
buffer = allocator.allocate();
}else{
buffer.position(buffer.limit());
buffer.limit(buffer.capacity());
}
}
}
readyOp = OP_READ;
}
}catch(Throwable thr){
try{
pumpDone(flushing ? OP_READ : readyOp);
}catch(Throwable suppressed){
thr.addSuppressed(suppressed);
}
throw readyOp==OP_WRITE || flushing ? new OutputException(thr) : new InputException(thr);
}
try{
pumpDone(readyOp);
}catch(Throwable thr){
throw new InputException(thr);
}
return true;
}
private void pumpDone(int readyOp) throws IOException{
if(buffers!=null){
if(readyOp==OP_READ){
if(buffer.position()!=0){
buffer.flip();
buffers.append(buffer);
buffer = null;
}
}else{
if(buffer.limit()!=0){
buffer.position(0);
buffers.append(buffer);
buffer = null;
}
}
}
if(buffer!=null){
allocator.free(buffer);
buffer = null;
}
buffers = null;
in.close();
}
/*-------------------------------------------------[ writeToOutputStream ]---------------------------------------------------*/
private OutputStream os;
protected void prepareWriteToOutputStream(OutputStream os, Buffers backup){
this.os = os;
buffers = backup;
buffer = allocator.allocateHeap();
}
protected boolean doWriteToOutputStream() throws IOException{
try{
while(true){
int read = in.read(buffer);
if(read==0){
in.addReadInterest();
return false;
}else if(read==-1)
break;
else{
os.write(buffer.array(), 0, buffer.position());
if(buffers==null)
buffer.clear();
else if(buffer.remaining()==0){
buffer.flip();
buffers.append(buffer);
buffer = allocator.allocateHeap();
}
}
}
}catch(Throwable thr){
writeToOutputStreamDone();
throw thr;
}
writeToOutputStreamDone();
return true;
}
private void writeToOutputStreamDone() throws IOException{
if(buffers!=null){
if(buffer.position()>0 && buffers!=null){
buffer.flip();
buffers.append(buffer);
buffers = null;
buffer = null;
}
}
if(buffer!=null){
allocator.free(buffer);
buffer = null;
}
in.close();
}
/*-------------------------------------------------[ Child Task ]---------------------------------------------------*/
Task child;
Task parent;
protected void setChild(Task child){
if(this.child!=null)
throw new UnsupportedOperationException(child+"="+child.getClass().getName());
if(DEBUG)
println("child = "+child);
this.child = child;
child.parent = this;
child.init(listener, in, out);
}
protected boolean hasChild(){
return child!=null;
}
}