Package org.xlightweb

Source Code of org.xlightweb.BodyDataSink$WriteCompletionHandlerCaller

/*
*  Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
*  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.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xlightweb.AbstractHttpConnection.IMultimodeExecutor;
import org.xlightweb.HttpUtils.CompletionHandlerInfo;
import org.xsocket.DataConverter;
import org.xsocket.IDataSink;
import org.xsocket.IDestroyable;
import org.xsocket.connection.ConnectionUtils;
import org.xsocket.connection.IConnection;
import org.xsocket.connection.IWriteCompletionHandler;
import org.xsocket.connection.NonBlockingConnection;
import org.xsocket.connection.IConnection.FlushMode;





/**
* I/O resource capable of receiving the body data.
*
* @author grro@xlightweb.org
*
*/
public abstract class BodyDataSink implements IDataSink, IDestroyable, Flushable, Closeable, WritableByteChannel, GatheringByteChannel {
 
    private static final Logger LOG = Logger.getLogger(BodyDataSink.class.getName());

    private static final int TRANSFER_CHUNK_SIZE = 65536;
  private static final long DEFAULT_SEND_TIMEOUT_MILLIS = Long.valueOf(System.getProperty(NonBlockingConnection.SEND_TIMEOUT_KEY, Long.toString(NonBlockingConnection.DEFAULT_SEND_TIMEOUT_MILLIS)));
 
  private long sendTimeoutMillis = DEFAULT_SEND_TIMEOUT_MILLIS;

  private final WriteQueue writeQueue = new WriteQueue();
  private final ArrayList<IBodyCloseListener> closeListeners = new ArrayList<IBodyCloseListener>();
  private IBodyDestroyListener destroyListener = null;
 
  private final AtomicBoolean isOpenRef = new AtomicBoolean(true);
  private final AtomicBoolean isDestroyedRef = new AtomicBoolean(false);
 
 
 
  // flags
  private String encoding = null;
  private boolean isAutoflush = true;
  private FlushMode flushMode = FlushMode.SYNC;
 
 
  // attachment
  private Object attachment = null;
 
 
  // write completion handler support
  private List<WriteCompletionHandlerCaller> writeCompletionHandlerCallers = new ArrayList<WriteCompletionHandlerCaller>();
 
 
  // executor
  private final IMultimodeExecutor executor;


  // statistics
  int written = 0;
 
 
  BodyDataSink(String characterEncoding, IMultimodeExecutor executor) throws IOException {
      this.executor = executor;
     
    setFlushmode(FlushMode.SYNC);
    setEncoding(characterEncoding);
  }

 

  final void addCloseListener(IBodyCloseListener closeListener) {
    synchronized (closeListeners) {
      closeListeners.add(closeListener);
    }
  }

 
  /**
   * remove a close listener
   *
   * @param closeListener a close listener
   * @return true, if the listener is removed
   */
  final boolean removeCloseListener(IBodyCloseListener closeListener) {
    synchronized (closeListeners) {
      return closeListeners.remove(closeListener);
    }
  }
 

  /**
   * set the sendtimout
   * @param sendTimeoutMillis  the send timeout
   */
  public final void setSendTimeoutMillis(long sendTimeoutMillis) {
      this.sendTimeoutMillis = sendTimeoutMillis;
  }
 
 
  @SuppressWarnings("unchecked")
    private void callCloseListener() {
        ArrayList<IBodyCloseListener> closeListenersCopy = null;
        synchronized (closeListeners) {
            closeListenersCopy = (ArrayList<IBodyCloseListener>) closeListeners.clone();
        }
       
        for (IBodyCloseListener bodyCloseListener : closeListenersCopy) {
            removeCloseListener(bodyCloseListener);
            try {
                bodyCloseListener.onClose();
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + getId() + "] error occured by calling " + bodyCloseListener + " " + ioe.toString());
                }
            }
        }
    }

 
  final void setDestroyListener(IBodyDestroyListener destroyListener) {
      assert (this.destroyListener == null);
      this.destroyListener = destroyListener;
  }

 
  int getSizeWritten() {
      return written;
  }
 

    /**
     * returns the size of pending write data
     *  
     * @return the size of pending write data
     */
    abstract int getPendingWriteDataSize();
   

    /**
     * {@inheritDoc}
     */
    public final void flush() throws IOException {
        if (!isOpenRef.get()) {
            throw new ClosedChannelException();
        }

        internalFlush();
    }
   
  
    protected abstract boolean isNetworkendpoint();
   
 
  /**
   * {@inheritDoc}
   */
  public final void internalFlush() throws IOException {
        removeWriteMark();
        ByteBuffer[] dataToWrite = writeQueue.drain();


        // ASYNC flush mode
        if (getFlushmode() == FlushMode.ASYNC) {
            if (writeCompletionHandlerCallers.isEmpty()) {
                written += writeData(dataToWrite, null);
               
            } else {
                WriteCompletionHandlerAdapter completionHandlerAdapter = new WriteCompletionHandlerAdapter(writeCompletionHandlerCallers);
                writeCompletionHandlerCallers = new ArrayList<WriteCompletionHandlerCaller>();
               
                written += writeData(dataToWrite, completionHandlerAdapter);
            }
          
           
        // SYNC flush mode           
        } else {
            if (writeCompletionHandlerCallers.isEmpty()) {
                SyncCaller caller = new SyncCaller(dataToWrite, null);
                caller.call();
               
            } else {
                WriteCompletionHandlerAdapter completionHandlerAdapter = new WriteCompletionHandlerAdapter(writeCompletionHandlerCallers);
                writeCompletionHandlerCallers = new ArrayList<WriteCompletionHandlerCaller>();
               
                SyncCaller caller = new SyncCaller(dataToWrite, completionHandlerAdapter);
                caller.call();
            }
        }
  }
 
 
   
    private static final class WriteCompletionHandlerAdapter implements IWriteCompletionHandler, IUnsynchronized {

        private final List<WriteCompletionHandlerCaller> callers;
       
       
        public WriteCompletionHandlerAdapter(List<WriteCompletionHandlerCaller> callers) throws IOException {
            this.callers = callers;
        }

       
        public void onWritten(int written) throws IOException {
            for (WriteCompletionHandlerCaller caller : callers) {
                caller.onWritten();
            }
        }
       
        public void onException(IOException ioe) {
            for (WriteCompletionHandlerCaller caller : callers) {
                caller.onException(ioe);
            }
        }
    }

 
  
 
  private final class SyncCaller implements IWriteCompletionHandler, IUnsynchronized {

      private final Object writeGuard = new Object();
      private final WriteCompletionHandlerAdapter writeCompletionHandlerAdapter;
     
      private IOException ioe = null;
      private boolean isWritten = false;
     
      private ByteBuffer[] dataToWrite = null;
     
     
      public SyncCaller(ByteBuffer[] buffers, WriteCompletionHandlerAdapter writeCompletionHandlerAdapter) throws IOException {
          this.writeCompletionHandlerAdapter = writeCompletionHandlerAdapter;
          dataToWrite = buffers;
        }
     

      public void call() throws IOException {

          written += writeData(dataToWrite, this)
         
          synchronized (writeGuard) {

              // is not written -> wait
                if (!isWritten) {
                    long start = System.currentTimeMillis();
                    long remainingTime = sendTimeoutMillis;
                    do {
                        // wait
                        try {
                            writeGuard.wait(remainingTime);
                        } catch (InterruptedException ignore) { }
                       
                        if (ioe != null) {
                            throw ioe;
                           
                        } else if (isWritten) {
                            return;
                        }
               
                        remainingTime = (start + sendTimeoutMillis) - System.currentTimeMillis();
                    } while (remainingTime > 0);

                    String msg = "[" + getId() + "] send timeout " + DataConverter.toFormatedDuration(sendTimeoutMillis) + " reached)";
                    if (LOG.isLoggable(Level.FINE)) {
                        LOG.fine(msg);
                    }
                    throw new SocketTimeoutException(msg);
                }
          }          
      }
     
     
      public void onWritten(int written) throws IOException {
          if (writeCompletionHandlerAdapter != null) {
              writeCompletionHandlerAdapter.onWritten(written);
          }
         
          synchronized (writeGuard) {
              isWritten = true;
              writeGuard.notifyAll();
            }
      }
     
      public void onException(IOException ioe) {
         
          if (writeCompletionHandlerAdapter != null) {
              writeCompletionHandlerAdapter.onException(ioe);
          }
         
            synchronized (writeGuard) {
                this.ioe = ioe;
                isWritten = true;
                writeGuard.notifyAll();
            }         
      }
  }


    

    abstract int writeData(ByteBuffer[] dataToWrite, IWriteCompletionHandler completionHandler) throws IOException;
   

 
 
  /**
   * {@inheritDoc}
   */
  public final void close() throws IOException {

    if (isOpenRef.getAndSet(false)) {
        try {
            if (!writeQueue.isEmpty()) {
                internalFlush();
            }
            onClose();
        } catch (IOException ioe) {
          throw ioe;
         
        } catch (Exception e) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] error occured by flushing BodyDataSink " + e.toString());
            }
          throw new IOException(e.toString());
           
        } finally {
          callCloseListener();
        }
    }
  }
 
 
  /**
     * closes this connection by swallowing io exceptions
     */
    public final void closeQuitly() {
        try {
            close();
        } catch (IOException ioe) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] Error occured by closing connection " + ioe.toString());
            }
        }
    }
 
    abstract void onClose() throws IOException;

   
    private void ensureStreamIsOpenAndWritable() throws ClosedChannelException {
        if (!isOpenRef.get()) {
            throw new DetailedClosedChannelException("data sink " + getId() " is closed");
        }
    }
   

    /**
     * writes a buffer array
     *
     * @param buffers                  the buffers to write
     * @param writeCompletionHandler   the completion handler
     * @throws IOException if an exception occurs
     */
    public void write(ByteBuffer[] buffers, IWriteCompletionHandler writeCompletionHandler) throws IOException {
        if (writeCompletionHandler == null) {
            write(buffers);
            return;
        }
       
        ensureStreamIsOpenAndWritable();
       
        writeCompletionHandlerCallers.add(new WriteCompletionHandlerCaller(writeCompletionHandler, buffers));
        write(buffers);
    }


 
 
  /**
   * transfer the available data from the data source
   *
   * @param source   the data source
   * @return the transfered size
   *
   * @throws IOException if an exception occurs
   */
  public final long transferFrom(NonBlockingBodyDataSource source) throws IOException {
    return source.transferTo(this);
  }
 

 
  /**
   * transfer the available data from the data source
   *
   * @param source   the data source
   * @param length   the length to transfer
   * @return the transfered size
   *
   * @throws IOException if an exception occurs
   */
  public final long transferFrom(NonBlockingBodyDataSource source, int length) throws IOException {
    return source.transferTo(this, length);
  }

 
  /**
   * transfer all data from the data source
   *
   * @param source   the data source
   * @return the transfered size
   *
   * @throws IOException if an exception occurs
   */
  public final long transferFrom(BlockingBodyDataSource source) throws IOException {
    return source.transferTo(this);
  }
 
 
  /**
   * transfer data from the data source
   *
   * @param source   the data source
   * @param length   the length to transfer
   * @return the transfered size
   *
   * @throws IOException if an exception occurs
   */
  public final long transferFrom(BlockingBodyDataSource source, int length) throws IOException {
    return source.transferTo(this);
  }

    /**
     * transfer all data from the data source
     *
     * @param source   the data source
     * @return the transfered size
     *
     * @throws IOException if an exception occurs
     */
    public final long transferFrom(InputStream is) throws IOException {
        return transferFrom(Channels.newChannel(is));
    }


   

    /**
     * {@inheritDoc}
     */
    public final long transferFrom(ReadableByteChannel source) throws IOException, BufferOverflowException {
        return transferFrom(source, TRANSFER_CHUNK_SIZE);
    }

   

    /**
     * {@inheritDoc}
     */
    public final long transferFrom(ReadableByteChannel source, int chunkSize) throws IOException, BufferOverflowException {
        ensureStreamIsOpenAndWritable();

        long transfered = 0;

        int read = 0;
        do {
            ByteBuffer transferBuffer = ByteBuffer.allocate(chunkSize);
            read = source.read(transferBuffer);

            if (read > 0) {
                if (transferBuffer.remaining() == 0) {
                    transferBuffer.flip();
                    write(transferBuffer);

                } else {
                    transferBuffer.flip();
                    write(transferBuffer.slice());
                }

                transfered += read;
            }
        } while (read > 0);

        return transfered;
    }



    /**
     * {@inheritDoc}
     */
    public long transferFrom(FileChannel fileChannel) throws IOException, BufferOverflowException {
        ensureStreamIsOpenAndWritable();
       
        if (getFlushmode() == FlushMode.SYNC) {
            final long size = fileChannel.size();
            long remaining = size;
               
            long offset = 0;
            long length = 0;
               
            do {
                if (remaining > TRANSFER_CHUNK_SIZE) {
                    length = TRANSFER_CHUNK_SIZE;
                } else {
                    length = remaining;
                }
                   
                MappedByteBuffer buffer = fileChannel.map(MapMode.READ_ONLY, offset, length);
                long written = write(buffer);
                   
                offset += written;
                remaining -= written;
            } while (remaining > 0);
                   
            return size;
           
        } else {
            return transferFrom((ReadableByteChannel) fileChannel);
        }
    }
   


  /**
   * {@inheritDoc}
   */
  public final int write(byte b) throws IOException, BufferOverflowException {
      return write(new byte[] {b});
  }


  /**
   * {@inheritDoc}
   */
  public final int write(byte... bytes) throws IOException, BufferOverflowException {
      return write(ByteBuffer.wrap(bytes));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(byte[] bytes, int offset, int length) throws IOException, BufferOverflowException {
      return write(DataConverter.toByteBuffer(bytes, offset, length));
  }


  /**
   * {@inheritDoc}
   */
  public int write(ByteBuffer buffer) throws IOException, BufferOverflowException {
      ensureStreamIsOpenAndWritable();
     
      int written = writeQueue.append(buffer);
     
      if (isAutoflush) {
          flush();
      }
         
      return written;
  }


  /**
   * {@inheritDoc}
   */
  public long write(ByteBuffer[] buffers) throws IOException, BufferOverflowException {
      ensureStreamIsOpenAndWritable();
     
      long written = writeQueue.append(buffers);
     
      if (isAutoflush) {
          flush();
      }
             
      return written;
  }


  /**
   * {@inheritDoc}
   */
  public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
    return write(DataConverter.toByteBuffers(srcs, offset, length));
  }


  /**
   * {@inheritDoc}
   */
  public final long write(List<ByteBuffer> buffers) throws IOException, BufferOverflowException {
    return write(buffers.toArray(new ByteBuffer[buffers.size()]));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(int i) throws IOException, BufferOverflowException {
    return write(DataConverter.toByteBuffer(i));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(short s) throws IOException, BufferOverflowException {
      return write(DataConverter.toByteBuffer(s));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(long l) throws IOException, BufferOverflowException {
        return write(DataConverter.toByteBuffer(l));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(double d) throws IOException, BufferOverflowException {
        return write(DataConverter.toByteBuffer(d));
  }


  /**
   * {@inheritDoc}
   */
  public final int write(String message) throws IOException, BufferOverflowException {
        return write(DataConverter.toByteBuffer(message, getEncoding()));
  }


  /**
   * sets the default encoding
   *
   * @param defaultEncoding  the default encoding
   */
  public final void setEncoding(String defaultEncoding) {
    this.encoding = defaultEncoding;
  }


  /**
   * gets the default encoding
   *
   * @return  the default encoding
   */
  public final String getEncoding() {
    return encoding;
  }


  /**
   * see {@link IConnection#setFlushmode(FlushMode)}
   */
  public void setFlushmode(FlushMode flushMode) {
      this.flushMode = flushMode;
  }


  /**
   * see {@link IConnection#getFlushmode()}
   */
  public final FlushMode getFlushmode() {
    return flushMode;
  }

 
  /**
   * set autoflush. If autoflush is activated, each write call
   * will cause a flush. <br><br>
   *
   * @param autoflush true if autoflush should be activated
   */
  public final void setAutoflush(boolean autoflush) {
      this.isAutoflush = autoflush;
  }

 
  /**
   * get autoflush
   *
   * @return true, if autoflush is activated
   */
  public final boolean isAutoflush() {
    return isAutoflush;
  }
 
 
 
  /**
   * Marks the write position in the connection.
   */
  public final void markWritePosition() {
      writeQueue.markWritePosition();
  }


  /**
   * Resets to the marked write position. If the connection has been marked,
   * then attempt to reposition it at the mark.
   *
   * @return true, if reset was successful
   */
  public final boolean resetToWriteMark() {
    return writeQueue.resetToWriteMark();
  }


 
  /**
   * remove the write mark
   */
  public final void removeWriteMark() {
      writeQueue.removeWriteMark();
  }

 
  /**
   * Attaches the given object to this connection
   *
   * @param obj The object to be attached; may be null
   * @return The previously-attached object, if any, otherwise null
   */
  public final void setAttachment(Object obj) {
      this.attachment = obj;
  }


  /**
   * Retrieves the current attachment.
   *
   * @return The object currently attached to this key, or null if there is no attachment
   */
  public final Object getAttachment() {
    return attachment;
  }

 
 
  /**
   * returns true if the data sink is open
   *
   * @return true if the data sink is open
   */
  public boolean isOpen() {
    return isOpenRef.get();
  }
 

 
  /**
   * call back if the underlying connection is closed
   */
  void onUnderlyingHttpConnectionClosed() {
    if (isOpenRef.get()) {
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine("[" + getId() + "] underlying connection is closed. closing data source");
      }
      isOpenRef.set(false);
   
      callCloseListener();
    }
  }
 
 
 
    
    protected String getId() {
        return Integer.toString(hashCode());
    }
   
    /**
     * destroys this data sink 
     */
    public void destroy() {
        destroy("user initiated");
    }

 
  /**
   * destroys this data sink 
   */
  final void destroy(String reason) {
     
    isOpenRef.set(false);
    if (!isDestroyedRef.getAndSet(true)) {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("[" + getId() + "] destroying data sink");
            }

        onDestroy(reason);
       
        if (destroyListener != null) {
            try {
                destroyListener.onDestroyed();
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("[" + getId() + "] error occured by calling destroy listener " + destroyListener + " " + ioe.toString());
                }
            }
        }
    }
  }
 
 
  abstract void onDestroy(String reason);
 
 
   
 
  /**
   * {@inheritDoc}
   */
  public String toString() {
    return writeQueue.toString();
  }   
 
 
    private final class WriteQueue implements Cloneable {

        private final Queue queue = new Queue();
       
        // mark support
        private RewriteableBuffer writeMarkBuffer = null;
        private boolean isWriteMarked = false;
       
       
       
        /**
         * returns true, if empty
         *
         * @return true, if empty
         */
        public boolean isEmpty() {
            return queue.isEmpty() && (writeMarkBuffer == null);
        }
       

        /**
         * return the current size
         *
         * @return  the current size
         */
        public int getSize() {

            int size = queue.getSize();
                   
            if (writeMarkBuffer != null) {
                size += writeMarkBuffer.size();
            }
                       
            return size;
        }
       
       

        /**
         * drain the queue
         *
         * @return the queue content
         */
        public ByteBuffer[] drain() {
            return queue.drain();
        }

           
       
        /**
         * append a byte buffer to this queue.
         *
         * @param data the ByteBuffer to append
         */
        public int append(ByteBuffer data) {
           
            if (data == null) {
                return 0;
            }
           
            int size = data.remaining();
                   
            if (isWriteMarked) {
                writeMarkBuffer.append(data);

            } else {
                queue.append(data);
            }
           
            return size;
        }
       
       
       
       
        /**
         * append a list of byte buffer to this queue. By adding a list,
         * the list becomes part of to the buffer, and should not be modified outside the buffer
         * to avoid side effects
         *
         * @param bufs  the list of ByteBuffer
         */
        public long append(ByteBuffer[] bufs) {
           
            if (bufs == null) {
                return 0;
            }
           
            if (bufs.length < 1) {
                return 0;
            }
           
            int size = 0;

            if (isWriteMarked) {
                for (ByteBuffer buffer : bufs) {
                    size += buffer.remaining();
                    writeMarkBuffer.append(buffer);
                }
               
            } else {
                for (ByteBuffer buffer : bufs) {
                    size += buffer.remaining();
                }
               
                queue.append(bufs);
            }
           
            return size;
        }
       
       


       
        /**
         * mark the current write position 
         */
        public void markWritePosition() {
            removeWriteMark();

            isWriteMarked = true;
            writeMarkBuffer = new RewriteableBuffer();
        }



        /**
         * remove write mark
         */
        public void removeWriteMark() {
            if (isWriteMarked) {
                isWriteMarked = false;
               
                append(writeMarkBuffer.drain());
                writeMarkBuffer = null;
            }
        }
       
       
        /**
         * reset the write position the the saved mark
         *
         * @return true, if the write position has been marked 
         */
        public boolean resetToWriteMark() {
            if (isWriteMarked) {
                writeMarkBuffer.resetWritePosition();
                return true;

            } else {
                return false;
            }
        }

       
       
        @Override
        protected Object clone() throws CloneNotSupportedException {
            WriteQueue copy = new WriteQueue();
            copy.queue.append(this.queue.copyContent());
       
            if (this.writeMarkBuffer != null) {
                copy.writeMarkBuffer = (RewriteableBuffer) this.writeMarkBuffer.clone();
            }
           
            return copy;
        }

       
        /**
         * {@inheritDoc}
         */
        public String asString(String encoding) {
            return queue.asString(encoding);
        }
       
       
       
       
       
        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return queue.toString();
        }
    }
 
 
  private static final class Queue {

        private ByteBuffer[] buffers;


        /**
         * returns true, if empty
         *
         * @return true, if empty
         */
        public synchronized boolean isEmpty() {
            return empty();
        }
       

        private boolean empty() {
            return (buffers == null);
        }
           

        /**
         * return the current size
         *
         * @return  the current size
         */
        public synchronized int getSize() {
            if (empty()) {
                return 0;
               
            } else {
                int size = 0;
                if (buffers != null) {
                    for (int i = 0; i < buffers.length; i++) {
                        if (buffers[i] != null) {
                            size += buffers[i].remaining();
                        }
                    }
                }
               
                return size;
            }
        }
       
       
        public synchronized void append(ByteBuffer data) {         
            if (buffers == null) {
                buffers = new ByteBuffer[1];
                buffers[0] = data;
                                   
            } else {
                ByteBuffer[] newBuffers = new ByteBuffer[buffers.length + 1];
                System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
                newBuffers[buffers.length] = data;
                buffers = newBuffers;
            }
        }
       
       
        /**
         * append a list of byte buffer to this queue. By adding a list,
         * the list becomes part of to the buffer, and should not be modified outside the buffer
         * to avoid side effects
         *
         * @param bufs  the list of ByteBuffer
         */
        public synchronized void append(ByteBuffer[] bufs) {
            if (buffers == null) {
                buffers = bufs;
                                       
            else {
                ByteBuffer[] newBuffers = new ByteBuffer[buffers.length + bufs.length];
                System.arraycopy(buffers, 0, newBuffers, 0, buffers.length);
                System.arraycopy(bufs, 0, newBuffers, buffers.length, bufs.length);
                buffers = newBuffers;
            }
        }
       


        /**
         * drain the queue
         *
         * @return the queue content
         */
        public synchronized ByteBuffer[] drain() {
            ByteBuffer[] result = buffers;
            buffers = null;
                       
            return result;
        }
       
       
       
        public synchronized ByteBuffer[] copyContent()  {
            return ConnectionUtils.copy(buffers);
        }
       
       
       
        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return asString("US-ASCII");
        }
       
       
        /**
         * {@inheritDoc}
         */
        public synchronized String asString(String encoding) {
            StringBuilder sb = new StringBuilder();
            if (buffers != null) {
                ByteBuffer[] copy = new ByteBuffer[buffers.length];
                try {
                    for (int i = 0; i < copy.length; i++) {
                        if (buffers[i] != null) {
                            copy[i] = buffers[i].duplicate();
                        }
                    }
                    sb.append(DataConverter.toString(copy, encoding, Integer.MAX_VALUE));
                   
                } catch (UnsupportedEncodingException use) {
                    sb.append(DataConverter.toHexString(copy, Integer.MAX_VALUE));
                }
            }

            return sb.toString();
        }
  }
 
 
    private static final class RewriteableBuffer implements Cloneable {
        private ArrayList<ByteBuffer> bufs = new ArrayList<ByteBuffer>();
        private int writePosition = 0;
       
       
        public void append(ByteBuffer buffer) {
           
            if (buffer.remaining() < 1) {
                return;
            }
           
            if (writePosition == bufs.size()) {
                bufs.add(buffer);
                writePosition++;
               
            } else {
                ByteBuffer currentBuffer = bufs.remove(writePosition);
               
                if (currentBuffer.remaining() == buffer.remaining()) {
                    bufs.add(writePosition, buffer);
                    writePosition++;
                   
                } else if (currentBuffer.remaining() > buffer.remaining()) {
                    currentBuffer.position(currentBuffer.position() + buffer.remaining());
                    bufs.add(writePosition, currentBuffer);
                    bufs.add(writePosition, buffer);
                    writePosition++;
                   
                } else { // currentBuffer.remaining() < buffer.remaining()
                    bufs.add(writePosition, buffer);
                    writePosition++;
                   
                    int bytesToRemove = buffer.remaining() - currentBuffer.remaining();
                    while (bytesToRemove > 0) {
                        // does tailing buffers exits?
                        if (writePosition < bufs.size()) {
                           
                            ByteBuffer buf = bufs.remove(writePosition);
                            if (buf.remaining() > bytesToRemove) {
                                buf.position(buf.position() + bytesToRemove);
                                bufs.add(writePosition, buf);
                            } else {
                                bytesToRemove -= buf.remaining();
                            }
                           
                        // ...no
                        } else {
                            bytesToRemove = 0;
                        }
                    }
                }
               
            }  
        }
   
        public void resetWritePosition() {
            writePosition = 0;
        }
       
   
        public ByteBuffer[] drain() {
            ByteBuffer[] result = bufs.toArray(new ByteBuffer[bufs.size()]);
            bufs.clear();
            writePosition = 0;
           
            return result;
        }
       
       
        public int size() {
            int size = 0;
            for (ByteBuffer buffer : bufs) {
                size += buffer.remaining();
            }
           
            return size;
        }
       
        @Override
        protected Object clone() throws CloneNotSupportedException {
            RewriteableBuffer copy = (RewriteableBuffer) super.clone();
           
            copy.bufs = new ArrayList<ByteBuffer>();
            for (ByteBuffer buffer : this.bufs) {
                copy.bufs.add(buffer.duplicate());
            }

            return copy;
        }
    }    
   
   
   
    private final class WriteCompletionHandlerCaller {
       
        private final IWriteCompletionHandler writeCompletionHandler;
        private final CompletionHandlerInfo writeCompletionHandlerInfo;
        private final int size;
       
       
        public WriteCompletionHandlerCaller(IWriteCompletionHandler writeCompletionHandler, ByteBuffer[] buffers) {
            this.writeCompletionHandler = writeCompletionHandler;
            writeCompletionHandlerInfo = HttpUtils.getCompletionHandlerInfo(writeCompletionHandler);
           
            size = HttpUtils.computeRemaining(buffers);
        }
       
       
        void onWritten(){
           
            if (writeCompletionHandlerInfo.isUnsynchronized()) {
                performCompletionHandler();
               
            } else {
                Runnable task = new Runnable() {
                    public void run() {
                        performCompletionHandler();
                    }
                };
               
                if (writeCompletionHandlerInfo.isOnWrittenMultithreaded()) {
                    executor.processMultithreaded(task);
                } else {
                    executor.processNonthreaded(task);
                }
            }
        }
       
       
        private void performCompletionHandler() {
            try {
                writeCompletionHandler.onWritten(size);
            } catch (IOException ioe) {
                if (LOG.isLoggable(Level.FINE)) {
                    LOG.fine("error occured by perforing onWritten of " + writeCompletionHandler + " " + ioe.toString());
                }
                destroy();
            }
        }
       
       
       
        void onException(final IOException ioe) {
           
            if (writeCompletionHandlerInfo.isUnsynchronized()) {
                writeCompletionHandler.onException(ioe);
               
            } else {
                Runnable task = new Runnable() {
                    public void run() {
                        writeCompletionHandler.onException(ioe);
                    }
                };
               
                if (writeCompletionHandlerInfo.isOnExceptionMutlithreaded()) {
                    executor.processMultithreaded(task);
                } else {
                    executor.processNonthreaded(task);
                }
            }
        }
    }
}
     
TOP

Related Classes of org.xlightweb.BodyDataSink$WriteCompletionHandlerCaller

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.