Package com.tryge.xocotl.io

Source Code of com.tryge.xocotl.io.StreamedDuplexChannel$Writer

package com.tryge.xocotl.io;

import com.tryge.xocotl.util.internal.FutureImpl;
import com.tryge.xocotl.util.Preconditions;

import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
* The StreamedDuplexChannel sends and receives messages from a stream that
* provides an input stream and an output stream. All messages are sent
* asynchronously therefore the send methods return immediately. However, if
* you need to wait for a response you can wait using the returned future.
* If you don't care about a response you can just sendAndForget the message,
* however, keep in mind that you won't have any indication that the message
* has really been sent.
*
* @author michael.zehender@me.com
*/
class StreamedDuplexChannel implements DuplexChannel {
  static final Message POISON = new PoisonMessage();

  private final AtomicReference<ChannelListener> channelListener = new AtomicReference<ChannelListener>(new NopChannelListener());
  private final AtomicReference<MessageListener> messageListener = new AtomicReference<MessageListener>(new NopChannelListener());
  private final ThreadFactory factory;
  private final Responder responder;
  private final MessageDecoder decoder;
  private final StreamSource source;
  private final int receiveBufferSize;

  private final LinkedBlockingQueue<Message> messages = new LinkedBlockingQueue<Message>();

  private volatile Stream stream;
  private volatile Thread readerThread;
  private volatile Thread writerThread;
  private volatile boolean closing;
  private volatile boolean closed;
  private volatile boolean open;

  StreamedDuplexChannel(ThreadFactory factory, Responder responder, MessageDecoder decoder, StreamSource source, int receiveBufferSize) {
    Preconditions.notNull(factory, "thread factory mustn't be null.");
    Preconditions.notNull(factory, "responder mustn't be null");
    Preconditions.notNull(decoder, "message decoder mustn't be null");
    Preconditions.notNull(source, "stream source mustn't be null.");
    Preconditions.checkPositive(receiveBufferSize, "receive buffer size must be greater than 0");

    this.factory = factory;
    this.responder = responder;
    this.decoder = decoder;
    this.source = source;
    this.receiveBufferSize = receiveBufferSize;
  }

  @Override
  public void setChannelListener(ChannelListener listener) {
    Preconditions.notNull(listener, "channel listener mustn't be null - you could use NopListener instead.");

    this.channelListener.set(listener);
  }

  @Override
  public void setMessageListener(MessageListener listener) {
    Preconditions.notNull(listener, "message listener mustn't be null - you could use NopListener instead.");

    this.messageListener.set(listener);
  }

  @Override
  public void close() throws IOException {
    if (!open || closed) {
      // silently ignore close on closed channel
      return;
    }

    closing = true;

    readerThread.interrupt();
    writerThread.interrupt();

    try {
      stream.close();
    } finally {
      try {
        sendAndForget(POISON);
        if (Thread.currentThread() != readerThread) {
          readerThread.join();
        }
        if (Thread.currentThread() != writerThread) {
          writerThread.join();
        }
      } catch (InterruptedException ie) {
        Thread.currentThread().interrupt();
      } finally {
        stream = null;
        closed = true;
        channelListener.get().onClose(this);
        responder.destroyed(this);
      }
    }
  }

  private void closeSilently() {
    try {
      if (!closing) {
        close();
      }
    } catch (Exception e) {
      // ignore
    }
  }

  @Override
  public void open() throws IOException {
    Preconditions.checkState(stream == null, "channel is already open");
    Preconditions.checkState(!closed, "channel has already been closed");
    stream = source.open();

    readerThread = factory.newThread(reader);
    writerThread = factory.newThread(writer);

    open = true;
    readerThread.start();
    writerThread.start();
  }

  @Override
  public boolean isClosed() {
    return stream == null;
  }

  @Override
  public boolean isOpen() {
    return stream != null;
  }

  @Override
  public Future<Message> send(Message msg) {
    Preconditions.notNull(msg, "can't send null message");
    Preconditions.checkState(stream != null, "channel is closed");

    ResponderMessage message = responder.register(this, msg);
    FutureImpl<Message> future = message.future();

    if (!messages.offer(message)) {
      future.failed(new IllegalStateException("could not enqueue message"));
    }
    return future;
  }

  @Override
  public Future<Void> sendEx(Message msg) {
    Preconditions.notNull(msg, "can't send null message");
    Preconditions.checkState(stream != null, "channel is closed");

    SuccessMessage message = new SuccessMessage(msg);
    FutureImpl<Void> future = message.future();

    if(!messages.offer(message)) {
      future.failed(new IllegalStateException("could not enqueue message"));
    }
    return future;
  }

  @Override
  public void sendAndForget(Message msg) {
    Preconditions.notNull(msg, "can't send null message");
    Preconditions.checkState(stream != null, "channel is closed");

    try {
      messages.add(msg);
    } catch(Exception e) {
      // couldn't queue message but caller doesn't care.
    }
  }

  final Runnable reader = new Reader();
  class Reader implements Runnable {
    @Override
    public void run() {
      try {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(receiveBufferSize);

        while(!Thread.currentThread().isInterrupted()) {
          byte[] buffer = new byte[Math.min(128, receiveBufferSize/2)];

          try {
            int read = stream.getInputStream().read(buffer);
            if (read == -1) {
              break;
            }

            byteBuffer.put(buffer, 0, read);
            // sets the limit to the current position and then the position to zero
            byteBuffer.flip();
            byteBuffer.mark();

            for(Message msg = decoder.decode(byteBuffer); msg != null; msg = decoder.decode(byteBuffer)) {
              if (msg.isResponse()) {
                responder.responseReceived(StreamedDuplexChannel.this, msg);
              } else {
                messageListener.get().onMessage(msg);
              }
              byteBuffer.mark();
            }
            // reset to the position of the last mark (end of last message)
            byteBuffer.reset();
            if (byteBuffer.position() != 0) {
              byteBuffer.compact();
            } else {
              byteBuffer.position(byteBuffer.limit());
            }
            // increase limit to capacity
            byteBuffer.limit(byteBuffer.capacity());
          } catch (IOException e) {
            break;
          } catch (Throwable th) {
            // something went awfully wrong here
            th.printStackTrace();
            break;
          }
        }
      } finally {
        closeSilently();
      }
    }
  }

  final Runnable writer = new Writer();
  class Writer implements Runnable {
    @Override
    public void run() {
      try {
        while(!Thread.currentThread().isInterrupted()) {
          Stream stream = StreamedDuplexChannel.this.stream;
          if (stream == null) {
            return;
          }

          try {
            Message message = messages.poll(1, TimeUnit.SECONDS);
            if (message != null) {
              message.writeTo(stream.getOutputStream());
              stream.getOutputStream().flush();
            }
          } catch (InterruptedException e) {
            break;
          } catch(EOFException eof) {
            break;
          } catch (IOException e) {
            break;
          } catch (Throwable th) {
            // something went awfully wrong here
            th.printStackTrace();
            break;
          }
        }
      } finally {
        closeSilently();
      }
    }
  }
}
TOP

Related Classes of com.tryge.xocotl.io.StreamedDuplexChannel$Writer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.