private void doReadAheadLoads() {
    List to_submit = null;
    try {
      lock_mon.enter();

      while (loading_messages.size() + queued_messages.size() < request_read_ahead
          && !requests.isEmpty()
          && !destroyed) {
        DiskManagerReadRequest dmr = (DiskManagerReadRequest) requests.removeFirst();
        loading_messages.add(dmr);
        if (to_submit == null) to_submit = new ArrayList();
        to_submit.add(dmr);
      }
    } finally {
      lock_mon.exit();
    }

    /*
    if ( peer.getIp().equals( "64.71.5.2")){

    	TimeFormatter.milliTrace( "obt read_ahead: -> " + (to_submit==null?0:to_submit.size()) +
    			" [lo=" + loading_messages.size() + ",qm=" + queued_messages.size() + ",re=" + requests.size() + ",rl=" + request_read_ahead + "]");
    }
    */

    if (to_submit != null) {
      for (int i = 0; i < to_submit.size(); i++) {
        peer.getManager()
            .getAdapter()
            .enqueueReadRequest(peer, (DiskManagerReadRequest) to_submit.get(i), read_req_listener);
      }
    }
  }
  /**
   * Remove an outstanding piece data request.
   *
   * @param piece_number
   * @param piece_offset
   * @param length
   */
  public void removePieceRequest(int piece_number, int piece_offset, int length) {
    if (destroyed) return;

    DiskManagerReadRequest dmr =
        peer.getManager().getDiskManager().createReadRequest(piece_number, piece_offset, length);

    try {
      lock_mon.enter();

      if (requests.contains(dmr)) {
        requests.remove(dmr);
        return;
      }

      if (loading_messages.contains(dmr)) {
        loading_messages.remove(dmr);
        return;
      }

      for (Iterator i = queued_messages.entrySet().iterator(); i.hasNext(); ) {
        Map.Entry entry = (Map.Entry) i.next();
        if (entry.getValue().equals(dmr)) { // it's already been queued
          BTPiece msg = (BTPiece) entry.getKey();
          if (outgoing_message_queue.removeMessage(msg, true)) {
            i.remove();
          }
          break; // do manual listener notify
        }
      }
    } finally {
      lock_mon.exit();
    }

    outgoing_message_queue.doListenerNotifications();
  }
  public Message[] removeDecodedMessages() {
    if (messages_last_read.isEmpty()) return null;

    Message[] msgs = (Message[]) messages_last_read.toArray(new Message[messages_last_read.size()]);

    messages_last_read.clear();

    return msgs;
  }
        public void readFailed(DiskManagerReadRequest request, Throwable cause) {
          try {
            lock_mon.enter();

            if (!loading_messages.contains(request) || destroyed) { // was canceled
              return;
            }
            loading_messages.remove(request);

          } finally {
            lock_mon.exit();
          }
        }
  /**
   * Get a list of piece numbers being requested
   *
   * @return list of Long values
   */
  public int[] getRequestedPieceNumbers() {
    if (destroyed) return new int[0];

    /** Cheap hack to reduce (but not remove all) the # of duplicate entries */
    int iLastNumber = -1;
    int pos = 0;
    int[] pieceNumbers;

    try {
      lock_mon.enter();

      // allocate max size needed (we'll shrink it later)
      pieceNumbers = new int[queued_messages.size() + loading_messages.size() + requests.size()];

      for (Iterator iter = queued_messages.keySet().iterator(); iter.hasNext(); ) {
        BTPiece msg = (BTPiece) iter.next();
        if (iLastNumber != msg.getPieceNumber()) {
          iLastNumber = msg.getPieceNumber();
          pieceNumbers[pos++] = iLastNumber;
        }
      }

      for (Iterator iter = loading_messages.iterator(); iter.hasNext(); ) {
        DiskManagerReadRequest dmr = (DiskManagerReadRequest) iter.next();
        if (iLastNumber != dmr.getPieceNumber()) {
          iLastNumber = dmr.getPieceNumber();
          pieceNumbers[pos++] = iLastNumber;
        }
      }

      for (Iterator iter = requests.iterator(); iter.hasNext(); ) {
        DiskManagerReadRequest dmr = (DiskManagerReadRequest) iter.next();
        if (iLastNumber != dmr.getPieceNumber()) {
          iLastNumber = dmr.getPieceNumber();
          pieceNumbers[pos++] = iLastNumber;
        }
      }

    } finally {
      lock_mon.exit();
    }

    int[] trimmed = new int[pos];
    System.arraycopy(pieceNumbers, 0, trimmed, 0, pos);

    return trimmed;
  }
  private void sendPendingHaves() {
    if (destroyed) {

      return;
    }

    try {
      pending_haves_mon.enter();

      int num_haves = pending_haves.size();

      if (num_haves == 0) {

        return;
      }

      // single have -> use BT

      if (num_haves == 1 || az_have_version < BTMessageFactory.MESSAGE_VERSION_SUPPORTS_PADDING) {

        for (int i = 0; i < num_haves; i++) {

          Integer piece_num = (Integer) pending_haves.get(i);

          outgoing_message_q.addMessage(new BTHave(piece_num.intValue(), bt_have_version), true);
        }
      } else {

        int[] piece_numbers = new int[num_haves];

        for (int i = 0; i < num_haves; i++) {

          piece_numbers[i] = ((Integer) pending_haves.get(i)).intValue();
        }

        outgoing_message_q.addMessage(new AZHave(piece_numbers, az_have_version), true);
      }

      outgoing_message_q.doListenerNotifications();

      pending_haves.clear();

    } finally {

      pending_haves_mon.exit();
    }
  }
  /** Destroy the aggregator, along with any pending messages. */
  public void destroy() {
    try {
      pending_haves_mon.enter();

      pending_haves.clear();
      destroyed = true;
    } finally {
      pending_haves_mon.exit();
    }
  }
        public void readCompleted(DiskManagerReadRequest request, DirectByteBuffer data) {
          try {
            lock_mon.enter();

            if (!loading_messages.contains(request) || destroyed) { // was canceled
              data.returnToPool();
              return;
            }
            loading_messages.remove(request);

            BTPiece msg =
                new BTPiece(request.getPieceNumber(), request.getOffset(), data, piece_version);
            queued_messages.put(msg, request);

            outgoing_message_queue.addMessage(msg, true);
          } finally {
            lock_mon.exit();
          }

          outgoing_message_queue.doListenerNotifications();
        }
  /**
   * Queue a new have message for aggregated sending.
   *
   * @param piece_number of the have message
   * @param force if true, send this and any other pending haves right away
   */
  public void queueHaveMessage(int piece_number, boolean force) {
    if (destroyed) return;

    try {
      pending_haves_mon.enter();

      pending_haves.add(new Integer(piece_number));
      if (force) {
        sendPendingHaves();
      } else {
        int pending_bytes = pending_haves.size() * 9;
        if (pending_bytes >= outgoing_message_q.getMssSize()) {
          // System.out.println("enough pending haves for a full packet!");
          // there's enough pending bytes to fill a packet payload
          sendPendingHaves();
        }
      }
    } finally {

      pending_haves_mon.exit();
    }
  }
  /** Remove all outstanding piece data requests. */
  public void removeAllPieceRequests() {
    if (destroyed) return;

    try {
      lock_mon.enter();

      // removed this trace as Alon can't remember why the trace is here anyway and as far as I can
      // see there's nothing to stop a piece being delivered to transport and removed from
      // the message queue before we're notified of this and thus it is entirely possible that
      // our view of queued messages is lagging.
      // String before_trace = outgoing_message_queue.getQueueTrace();
      /*
      int num_queued = queued_messages.size();
      int num_removed = 0;

      for( Iterator i = queued_messages.keySet().iterator(); i.hasNext(); ) {
        BTPiece msg = (BTPiece)i.next();
        if( outgoing_message_queue.removeMessage( msg, true ) ) {
          i.remove();
          num_removed++;
        }
      }

      if( num_removed < num_queued -2 ) {
        Debug.out( "num_removed[" +num_removed+ "] < num_queued[" +num_queued+ "]:\nBEFORE:\n" +before_trace+ "\nAFTER:\n" +outgoing_message_queue.getQueueTrace() );
      }
      */

      for (Iterator i = queued_messages.keySet().iterator(); i.hasNext(); ) {
        BTPiece msg = (BTPiece) i.next();
        outgoing_message_queue.removeMessage(msg, true);
      }

      queued_messages.clear(); // this replaces stuff above
      requests.clear();
      loading_messages.clear();
    } finally {
      lock_mon.exit();
    }

    outgoing_message_queue.doListenerNotifications();
  }
  private int postReadProcess() throws IOException {
    int prot_bytes_read = 0;
    int data_bytes_read = 0;

    if (!reading_length_mode && !destroyed) { // reading payload data mode
      // ensure-restore proper buffer limits
      payload_buffer.limit(SS, message_length);
      length_buffer.limit(SS, 4);

      int read = payload_buffer.position(SS) - pre_read_start_position;

      if (payload_buffer.position(SS) > 0) { // need to have read the message id first byte
        if (BTMessageFactory.getMessageType(payload_buffer) == Message.TYPE_DATA_PAYLOAD) {
          data_bytes_read += read;
        } else {
          prot_bytes_read += read;
        }
      }

      if (!payload_buffer.hasRemaining(SS) && !is_paused) { // full message received!
        payload_buffer.position(SS, 0);

        DirectByteBuffer ref_buff = payload_buffer;
        payload_buffer = null;

        if (reading_handshake_message) { // decode handshake
          reading_handshake_message = false;

          DirectByteBuffer handshake_data =
              DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG_BT_HAND, 68);
          handshake_data.putInt(SS, HANDSHAKE_FAKE_LENGTH);
          handshake_data.put(SS, ref_buff);
          handshake_data.flip(SS);

          ref_buff.returnToPool();

          try {
            Message handshake =
                MessageManager.getSingleton()
                    .createMessage(BTMessage.ID_BT_HANDSHAKE_BYTES, handshake_data, (byte) 1);
            messages_last_read.add(handshake);
          } catch (MessageException me) {
            handshake_data.returnToPool();
            throw new IOException("BT message decode failed: " + me.getMessage());
          }

          // we need to auto-pause decoding until we're told to start again externally,
          // as we don't want to accidentally read the next message on the stream if it's an
          // AZ-format handshake
          pauseDecoding();
        } else { // decode normal message
          try {
            messages_last_read.add(createMessage(ref_buff));
          } catch (Throwable e) {
            ref_buff.returnToPoolIfNotFree();

            // maintain unexpected errors as such so they get logged later

            if (e instanceof RuntimeException) {

              throw ((RuntimeException) e);
            }

            throw new IOException("BT message decode failed: " + e.getMessage());
          }
        }

        reading_length_mode = true; // see if we've already read the next message's length
        percent_complete = -1; // reset receive percentage
      } else { // only partial received so far
        percent_complete =
            (payload_buffer.position(SS) * 100) / message_length; // compute receive percentage
      }
    }

    if (reading_length_mode && !destroyed) {
      length_buffer.limit(SS, 4); // ensure proper buffer limit

      prot_bytes_read +=
          (pre_read_start_buffer == 1)
              ? length_buffer.position(SS) - pre_read_start_position
              : length_buffer.position(SS);

      if (!length_buffer.hasRemaining(SS)) { // done reading the length
        reading_length_mode = false;

        length_buffer.position(SS, 0);
        message_length = length_buffer.getInt(SS);

        length_buffer.position(SS, 0); // reset it for next length read

        if (message_length == HANDSHAKE_FAKE_LENGTH) { // handshake message
          reading_handshake_message = true;
          message_length = 64; // restore 'real' length
          payload_buffer =
              DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG_BT_HAND, message_length);
        } else if (message_length == 0) { // keep-alive message
          reading_length_mode = true;
          last_received_was_keepalive = true;

          try {
            Message keep_alive =
                MessageManager.getSingleton()
                    .createMessage(BTMessage.ID_BT_KEEP_ALIVE_BYTES, null, (byte) 1);
            messages_last_read.add(keep_alive);
          } catch (MessageException me) {
            throw new IOException("BT message decode failed: " + me.getMessage());
          }
        } else if (message_length < MIN_MESSAGE_LENGTH || message_length > MAX_MESSAGE_LENGTH) {
          throw new IOException(
              "Invalid message length given for BT message decode: " + message_length);
        } else { // normal message
          payload_buffer =
              DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG_BT_PAYLOAD, message_length);
        }
      }
    }

    protocol_bytes_last_read += prot_bytes_read;
    data_bytes_last_read += data_bytes_read;

    return prot_bytes_read + data_bytes_read;
  }
  public ByteBuffer destroy() {
    if (destroyed) {
      Debug.out("Trying to redestroy message decoder, stack trace follows: " + this);
      Debug.outStackTrace();
    }

    is_paused = true;
    destroyed = true;

    // there's a concurrency issue with the decoder whereby it can be destroyed while will being
    // messed with. Don't
    // have the energy to look into it properly atm so just try to ensure that it doesn't bork too
    // badly (parg: 29/04/2012)
    // only occasional but does have potential to generate direct buffer mem leak ;(

    int lbuff_read = 0;
    int pbuff_read = 0;
    length_buffer.limit(SS, 4);

    DirectByteBuffer plb = payload_buffer;

    if (reading_length_mode) {
      lbuff_read = length_buffer.position(SS);
    } else { // reading payload
      length_buffer.position(SS, 4);
      lbuff_read = 4;
      pbuff_read = plb == null ? 0 : plb.position(SS);
    }

    ByteBuffer unused = ByteBuffer.allocate(lbuff_read + pbuff_read); // TODO convert to direct?

    length_buffer.flip(SS);
    unused.put(length_buffer.getBuffer(SS));

    try {
      if (plb != null) {
        plb.flip(SS);
        unused.put(
            plb.getBuffer(
                SS)); // Got a buffer overflow exception here in the past - related to PEX?
      }
    } catch (RuntimeException e) {
      Debug.out("hit known threading issue");
    }

    unused.flip();

    length_buffer.returnToPool();

    if (plb != null) {
      plb.returnToPool();
      payload_buffer = null;
    }

    try {
      for (int i = 0; i < messages_last_read.size(); i++) {
        Message msg = (Message) messages_last_read.get(i);
        msg.destroy();
      }
    } catch (RuntimeException e) {
      // happens if messages modified by alt thread...
      Debug.out("hit known threading issue");
    }
    messages_last_read.clear();

    return unused;
  }
 public boolean isStalledPendingLoad() {
   return (queued_messages.size() == 0 && loading_messages.size() > 0);
 }
 public int getRequestCount() {
   return (queued_messages.size() + loading_messages.size() + requests.size());
 }
 /**
  * Are there Haves messages pending?
  *
  * @return true if there are any unsent haves, false otherwise
  */
 public boolean hasPending() {
   return !pending_haves.isEmpty();
 }