private void freeSliceBuffer(DirectByteBuffer ddb) {
    if (ddb instanceof sliceDBB) {

      int slice_index = getSliceIndex(ddb.getBufferInternal().capacity());

      List my_slice_entries = slice_entries[slice_index];

      synchronized (my_slice_entries) {
        my_slice_entries.add(0, ((sliceDBB) ddb).getSliceBuffer());
      }
    }
  }
  /** Return the given buffer to the appropriate pool. */
  protected void returnBufferSupport(DirectByteBuffer ddb) {
    ByteBuffer buff = ddb.getBufferInternal();

    if (buff == null) {

      Debug.out("Returned dbb has null delegate");

      throw (new RuntimeException("Returned dbb has null delegate"));
    }

    int capacity = buff.capacity();

    bytesIn += capacity;

    if (DEBUG_TRACK_HANDEDOUT) {

      synchronized (handed_out) {
        if (handed_out.remove(buff) == null) {

          Debug.out("buffer not handed out");

          throw (new RuntimeException("Buffer not handed out"));
        }

        // System.out.println( "[" + handed_out.size() + "] <- " + buffer + ", bytesIn = " + bytesIn
        // + ", bytesOut = " + bytesOut );
      }
    }

    // remInUse( buffer.capacity() );

    if (capacity <= SLICE_END_SIZE) {

      freeSliceBuffer(ddb);

    } else {
      Integer buffSize = new Integer(capacity);

      ArrayList bufferPool = (ArrayList) buffersMap.get(buffSize);

      if (bufferPool != null) {

        // no need to sync around 'poolsLock', as adding during compaction is ok

        synchronized (bufferPool) {
          bufferPool.add(buff);
        }
      } else {

        Debug.out("Invalid buffer given; could not find proper buffer pool");
      }
    }
  }
  private int preReadProcess(int allowed) {
    if (allowed < 1) {
      Debug.out("allowed < 1");
    }

    decode_array[0] =
        payload_buffer == null
            ? null
            : payload_buffer.getBuffer(
                SS); // ensure the decode array has the latest payload pointer

    int bytes_available = 0;
    boolean shrink_remaining_buffers = false;
    int start_buff = reading_length_mode ? 1 : 0;
    boolean marked = false;

    for (int i = start_buff; i < 2; i++) { // set buffer limits according to bytes allowed
      ByteBuffer bb = decode_array[i];

      if (bb == null) {
        Debug.out("preReadProcess:: bb[" + i + "] == null, decoder destroyed=" + destroyed);

        throw (new RuntimeException("decoder destroyed"));
      }

      if (shrink_remaining_buffers) {
        bb.limit(0); // ensure no read into this next buffer is possible
      } else {
        int remaining = bb.remaining();

        if (remaining < 1) continue; // skip full buffer

        if (!marked) {
          pre_read_start_buffer = i;
          pre_read_start_position = bb.position();
          marked = true;
        }

        if (remaining > allowed) { // read only part of this buffer
          bb.limit(bb.position() + allowed); // limit current buffer
          bytes_available += bb.remaining();
          shrink_remaining_buffers = true; // shrink any tail buffers
        } else { // full buffer is allowed to be read
          bytes_available += remaining;
          allowed -= remaining; // count this buffer toward allowed and move on to the next
        }
      }
    }

    return bytes_available;
  }
        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();
        }
public class BTMessageDecoder implements MessageStreamDecoder {
  private static final int MIN_MESSAGE_LENGTH = 1; // for type id
  // private static final int MAX_MESSAGE_LENGTH = 16*1024+128;  //should never be > 16KB+9B, as we
  // never request chunks > 16KB - update, some LT extensions can be bigger
  private static final int MAX_MESSAGE_LENGTH =
      128
          * 1024; // 17/5/2013: parg: got a huge torrent with so many pieces the bitfield exceeds
                  // the above limit...
  private static final int HANDSHAKE_FAKE_LENGTH =
      323119476; // (byte)19 + "Bit" readInt() value of header

  private static final byte SS = DirectByteBuffer.SS_MSG;

  private DirectByteBuffer payload_buffer = null;
  private final DirectByteBuffer length_buffer =
      DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG, 4);
  private final ByteBuffer[] decode_array = new ByteBuffer[] {null, length_buffer.getBuffer(SS)};

  private boolean reading_length_mode = true;
  private boolean reading_handshake_message = false;

  private int message_length;
  private int pre_read_start_buffer;
  private int pre_read_start_position;

  private boolean last_received_was_keepalive = false;

  private volatile boolean destroyed = false;
  private volatile boolean is_paused = false;

  private ArrayList messages_last_read = new ArrayList();
  private int protocol_bytes_last_read = 0;
  private int data_bytes_last_read = 0;
  private int percent_complete = -1;

  public BTMessageDecoder() {
    /* nothing */
  }

  public int performStreamDecode(Transport transport, int max_bytes) throws IOException {
    try {
      protocol_bytes_last_read = 0;
      data_bytes_last_read = 0;

      int bytes_remaining = max_bytes;

      while (bytes_remaining > 0) {

        if (destroyed) {

          // destruction currently isn't thread safe so one thread can destroy the decoder (e.g.
          // when closing a connection)
          // while the read-controller is still actively processing the us
          // throw( new IOException( "BTMessageDecoder already destroyed" ));
          break;
        }

        if (is_paused) {
          break;
        }

        int bytes_possible = preReadProcess(bytes_remaining);

        if (bytes_possible < 1) {
          Debug.out("ERROR BT: bytes_possible < 1");
          break;
        }

        if (reading_length_mode) {
          transport.read(decode_array, 1, 1); // only read into length buffer
        } else {
          transport.read(
              decode_array, 0, 2); // read into payload buffer, and possibly next message length
        }

        int bytes_read = postReadProcess();

        bytes_remaining -= bytes_read;

        if (bytes_read < bytes_possible) {
          break;
        }

        if (reading_length_mode && last_received_was_keepalive) {
          // hack to stop a 0-byte-read after receiving a keep-alive message
          // otherwise we won't realize there's nothing left on the line until trying to read again
          last_received_was_keepalive = false;
          break;
        }
      }

      return max_bytes - bytes_remaining;

    } catch (NullPointerException e) {

      // due to lack of synchronization here the buffers can be nullified by a concurrent 'destroy'
      // turn this into something less scarey

      throw (new IOException("Decoder has most likely been destroyed"));
    }
  }

  public int getPercentDoneOfCurrentMessage() {
    return percent_complete;
  }

  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 int getProtocolBytesDecoded() {
    return protocol_bytes_last_read;
  }

  public int getDataBytesDecoded() {
    return data_bytes_last_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;
  }

  private int preReadProcess(int allowed) {
    if (allowed < 1) {
      Debug.out("allowed < 1");
    }

    decode_array[0] =
        payload_buffer == null
            ? null
            : payload_buffer.getBuffer(
                SS); // ensure the decode array has the latest payload pointer

    int bytes_available = 0;
    boolean shrink_remaining_buffers = false;
    int start_buff = reading_length_mode ? 1 : 0;
    boolean marked = false;

    for (int i = start_buff; i < 2; i++) { // set buffer limits according to bytes allowed
      ByteBuffer bb = decode_array[i];

      if (bb == null) {
        Debug.out("preReadProcess:: bb[" + i + "] == null, decoder destroyed=" + destroyed);

        throw (new RuntimeException("decoder destroyed"));
      }

      if (shrink_remaining_buffers) {
        bb.limit(0); // ensure no read into this next buffer is possible
      } else {
        int remaining = bb.remaining();

        if (remaining < 1) continue; // skip full buffer

        if (!marked) {
          pre_read_start_buffer = i;
          pre_read_start_position = bb.position();
          marked = true;
        }

        if (remaining > allowed) { // read only part of this buffer
          bb.limit(bb.position() + allowed); // limit current buffer
          bytes_available += bb.remaining();
          shrink_remaining_buffers = true; // shrink any tail buffers
        } else { // full buffer is allowed to be read
          bytes_available += remaining;
          allowed -= remaining; // count this buffer toward allowed and move on to the next
        }
      }
    }

    return bytes_available;
  }

  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 void pauseDecoding() {
    is_paused = true;
  }

  public void resumeDecoding() {
    is_paused = false;
  }

  // Overridden by LTMessageDecoder.
  protected Message createMessage(DirectByteBuffer ref_buff) throws MessageException {
    try {
      return BTMessageFactory.createBTMessage(ref_buff);
    } catch (MessageException me) {
      /*if (identifier != null && me.getMessage() != null && me.getMessage().startsWith("Unknown BT message id")) {
       System.out.println(identifier + " " + me.getMessage());
      }*/
      throw me;
    }
  }
}
  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;
  }
  private void printInUse(boolean verbose) {
    if (DEBUG_PRINT_MEM) {

      System.out.print(
          "DIRECT: given="
              + bytesOut / 1024 / 1024
              + "MB, returned="
              + bytesIn / 1024 / 1024
              + "MB, ");

      long in_use = bytesOut - bytesIn;
      if (in_use < 1024 * 1024) System.out.print("in use=" + in_use + "B, ");
      else System.out.print("in use=" + in_use / 1024 / 1024 + "MB, ");

      long free = bytesFree();
      if (free < 1024 * 1024) System.out.print("free=" + free + "B");
      else System.out.print("free=" + free / 1024 / 1024 + "MB");

      System.out.println();

      CacheFileManager cm = null;

      try {
        cm = CacheFileManagerFactory.getSingleton();

      } catch (Throwable e) {

        Debug.printStackTrace(e);
      }

      synchronized (handed_out) {
        Iterator it = handed_out.values().iterator();

        Map cap_map = new TreeMap();
        Map alloc_map = new TreeMap();

        while (it.hasNext()) {

          DirectByteBuffer db = (DirectByteBuffer) it.next();

          if (verbose) {
            String trace = db.getTraceString();

            if (trace != null) {

              System.out.println(trace);
            }
          }

          Integer cap = new Integer(db.getBufferInternal().capacity());
          Byte alloc = new Byte(db.getAllocator());

          myInteger c = (myInteger) cap_map.get(cap);

          if (c == null) {

            c = new myInteger();

            cap_map.put(cap, c);
          }

          c.value++;

          myInteger a = (myInteger) alloc_map.get(alloc);

          if (a == null) {

            a = new myInteger();

            alloc_map.put(alloc, a);
          }

          a.value++;
        }

        it = cap_map.keySet().iterator();

        while (it.hasNext()) {

          Integer key = (Integer) it.next();
          myInteger count = (myInteger) cap_map.get(key);

          if (key.intValue() < 1024) {

            System.out.print("[" + key.intValue() + " x " + count.value + "] ");

          } else {

            System.out.print("[" + key.intValue() / 1024 + "K x " + count.value + "] ");
          }
        }

        System.out.println();

        it = alloc_map.keySet().iterator();

        while (it.hasNext()) {

          Byte key = (Byte) it.next();
          myInteger count = (myInteger) alloc_map.get(key);

          System.out.print(
              "[" + DirectByteBuffer.AL_DESCS[key.intValue()] + " x " + count.value + "] ");
        }

        if (cm != null) {

          CacheFileManagerStats stats = cm.getStats();

          System.out.print(" - Cache: ");

          System.out.print("sz=" + stats.getSize());
          System.out.print(",us=" + stats.getUsedSize());
          System.out.print(",cw=" + stats.getBytesWrittenToCache());
          System.out.print(",cr=" + stats.getBytesReadFromCache());
          System.out.print(",fw=" + stats.getBytesWrittenToFile());
          System.out.print(",fr=" + stats.getBytesReadFromFile());
        }

        System.out.println();

        if (DEBUG_HANDOUT_SIZES) {
          it = size_counts.entrySet().iterator();

          String str = "";

          while (it.hasNext()) {

            Map.Entry entry = (Map.Entry) it.next();

            str += (str.length() == 0 ? "" : ",") + entry.getKey() + "=" + entry.getValue();
          }

          System.out.println(str);
        }

        String str = "";

        for (int i = 0; i < slice_entries.length; i++) {

          boolean[] allocs = slice_allocs[i];
          int alloc_count = 0;
          for (int j = 0; j < allocs.length; j++) {
            if (allocs[j]) {
              alloc_count++;
            }
          }
          str +=
              (i == 0 ? "" : ",")
                  + "["
                  + SLICE_ENTRY_SIZES[i]
                  + "]f="
                  + slice_entries[i].size()
                  + ",a="
                  + (alloc_count * SLICE_ENTRY_ALLOC_SIZES[i])
                  + ",u="
                  + slice_use_count[i];
        }

        System.out.println("slices: " + str);
      }

      if (DEBUG_FREE_SIZES) {
        System.out.print("free block sizes: ");

        synchronized (poolsLock) {
          Iterator it = buffersMap.keySet().iterator();
          while (it.hasNext()) {
            Integer keyVal = (Integer) it.next();
            ArrayList bufferPool = (ArrayList) buffersMap.get(keyVal);

            int blocksize = keyVal.intValue();
            int blockfootprint = keyVal.intValue() * bufferPool.size();
            if (blockfootprint == 0) continue;
            String blocksuffix = "";
            if (blocksize > 1024) {
              blocksize /= 1024;
              blocksuffix = "k";
            }
            if (blocksize > 1024) {
              blocksize /= 1024;
              blocksuffix = "M";
            }
            String footsuffix = "";
            if (blockfootprint > 1024) {
              blockfootprint /= 1024;
              footsuffix = "k";
            }
            if (blockfootprint > 1024) {
              blockfootprint /= 1024;
              footsuffix = "M";
            }

            System.out.print(
                "[" + blocksize + blocksuffix + ":" + blockfootprint + footsuffix + "] ");
          }
        }

        System.out.println();
      }

      long free_mem = Runtime.getRuntime().freeMemory() / 1024 / 1024;
      long max_mem = Runtime.getRuntime().maxMemory() / 1024 / 1024;
      long total_mem = Runtime.getRuntime().totalMemory() / 1024 / 1024;
      System.out.println(
          "HEAP: max=" + max_mem + "MB, total=" + total_mem + "MB, free=" + free_mem + "MB");
      System.out.println();
    }
  }
  /**
   * Retrieve an appropriate buffer from the free pool, or create a new one if the pool is empty.
   */
  private DirectByteBuffer getBufferHelper(byte _allocator, int _length) {
    DirectByteBuffer res;

    if (_length <= SLICE_END_SIZE) {

      res = getSliceBuffer(_allocator, _length);

    } else {

      ByteBuffer buff = null;

      Integer reqVal = new Integer(_length);

      // loop through the buffer pools to find a buffer big enough

      Iterator it = buffersMap.keySet().iterator();

      while (it.hasNext()) {

        Integer keyVal = (Integer) it.next();

        // check if the buffers in this pool are big enough

        if (reqVal.compareTo(keyVal) <= 0) {

          ArrayList bufferPool = (ArrayList) buffersMap.get(keyVal);

          while (true) {

            synchronized (poolsLock) {

              // make sure we don't remove a buffer when running compaction
              // if there are no free buffers in the pool, create a new one.
              // otherwise use one from the pool

              if (bufferPool.isEmpty()) {

                buff = allocateNewBuffer(keyVal.intValue());

                if (buff == null) {

                  Debug.out("allocateNewBuffer for " + _length + " returned null");
                }

                break;

              } else {

                synchronized (bufferPool) {
                  buff = (ByteBuffer) bufferPool.remove(bufferPool.size() - 1);
                }

                if (buff == null) {

                  Debug.out("buffer pool for " + _length + " contained null entry");

                } else {

                  break;
                }
              }
            }
          }

          break;
        }
      }

      if (buff == null) {

        String str = "Unable to find an appropriate buffer pool for " + _length;

        Debug.out(str);

        throw (new RuntimeException(str));
      }

      res = new DirectByteBuffer(_allocator, buff, this);
    }

    // clear doesn't actually zero the data, it just sets pos to 0 etc.

    ByteBuffer buff = res.getBufferInternal();

    buff.clear(); // scrub the buffer

    buff.limit(_length);

    bytesOut += buff.capacity();

    if (DEBUG_PRINT_MEM || DEBUG_TRACK_HANDEDOUT) {

      synchronized (handed_out) {
        if (DEBUG_HANDOUT_SIZES) {

          int trim_size;

          if (_length < 32) {

            trim_size = 4;
          } else {

            trim_size = 16;
          }

          int trim = ((_length + trim_size - 1) / trim_size) * trim_size;

          Long count = (Long) size_counts.get(new Integer(trim));

          if (count == null) {

            size_counts.put(new Integer(trim), new Long(1));

          } else {

            size_counts.put(new Integer(trim), new Long(count.longValue() + 1));
          }
        }

        if (handed_out.put(buff, res) != null) {

          Debug.out("buffer handed out twice!!!!");

          throw (new RuntimeException("Buffer handed out twice"));
        }

        // System.out.println( "[" + handed_out.size() + "] -> " + buff + ", bytesIn = " + bytesIn +
        // ", bytesOut = " + bytesOut );
      }
    }

    // addInUse( dbb.capacity() );

    return (res);
  }