private void invokeOnComplete(final IoCallback callback) {
    inCall = true;
    try {
      callback.onComplete(exchange, this);
    } finally {
      inCall = false;
    }
    while (next != null || pendingFile != null) {
      ByteBuffer[] next = this.next;
      IoCallback queuedCallback = this.queuedCallback;
      FileChannel file = this.pendingFile;
      this.next = null;
      this.queuedCallback = null;
      this.pendingFile = null;

      if (next != null) {
        for (ByteBuffer buffer : next) {
          writeBuffer(buffer, queuedCallback);
        }
      } else if (file != null) {
        performTransfer(file, queuedCallback);
      }
      inCall = true;
      try {
        queuedCallback.onComplete(exchange, this);
      } finally {
        inCall = false;
      }
    }
  }
 private boolean writeBuffer(final ByteBuffer[] buffers, final IoCallback callback) {
   if (outputStream instanceof BufferWritableOutputStream) {
     // fast path, if the stream can take a buffer directly just write to it
     try {
       ((BufferWritableOutputStream) outputStream).write(buffers);
       return true;
     } catch (IOException e) {
       callback.onException(exchange, this, e);
       return false;
     }
   }
   for (ByteBuffer buffer : buffers) {
     if (buffer.hasArray()) {
       try {
         outputStream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
       } catch (IOException e) {
         callback.onException(exchange, this, e);
         return false;
       }
     } else {
       byte[] b = new byte[BUFFER_SIZE];
       while (buffer.hasRemaining()) {
         int toRead = Math.min(buffer.remaining(), BUFFER_SIZE);
         buffer.get(b, 0, toRead);
         try {
           outputStream.write(b, 0, toRead);
         } catch (IOException e) {
           callback.onException(exchange, this, e);
           return false;
         }
       }
     }
   }
   return true;
 }
  private void performTransfer(FileChannel source, IoCallback callback) {
    if (outputStream instanceof BufferWritableOutputStream) {
      try {
        ((BufferWritableOutputStream) outputStream).transferFrom(source);
      } catch (IOException e) {
        callback.onException(exchange, this, e);
      }
    } else {
      ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
      try {
        long pos = source.position();
        long size = source.size();
        while (size - pos > 0) {
          int ret = source.read(buffer);
          if (ret <= 0) {
            break;
          }
          pos += ret;
          outputStream.write(buffer.array(), buffer.arrayOffset(), ret);
          buffer.clear();
        }

        if (pos != size) {
          throw new EOFException("Unexpected EOF reading file");
        }

      } catch (IOException e) {
        callback.onException(exchange, this, e);
      }
    }
  }
 @Override
 public void close(final IoCallback callback) {
   try {
     outputStream.close();
     invokeOnComplete(callback);
   } catch (IOException e) {
     callback.onException(exchange, this, e);
   }
 }
 @Override
 public void send(final String data, final IoCallback callback) {
   if (inCall) {
     queue(new ByteBuffer[] {ByteBuffer.wrap(data.getBytes(utf8))}, callback);
     return;
   }
   try {
     outputStream.write(data.getBytes(utf8));
     invokeOnComplete(callback);
   } catch (IOException e) {
     callback.onException(exchange, this, e);
   }
 }