@Override
  public DirectByteBuffer[] getData() {
    try {
      if (buffer == null) {
        if (msg_type == REQUEST_MESSAGE_TYPE_ID) {
          Map<Object, Object> map = new HashMap<Object, Object>();
          map.put("msg_type", msg_type);
          map.put("piece", piece);
          buffer =
              MessagingUtil.convertPayloadToBencodedByteStream(
                  map, DirectByteBuffer.AL_MSG_UT_METADATA);
        } else if (msg_type == DATA_MESSAGE_TYPE_ID) {
          Map<Object, Object> map = new HashMap<Object, Object>();
          map.put("msg_type", msg_type);
          map.put("piece", piece);
          map.put("total_size", total_size);
          byte[] mapPayload = BEncoder.encode(map);
          buffer =
              DirectByteBufferPool.getBuffer(
                  DirectByteBuffer.AL_MSG_UT_METADATA, mapPayload.length + metadata.length);
          buffer.put(DirectByteBuffer.SS_MSG, mapPayload);
          buffer.put(DirectByteBuffer.SS_MSG, metadata);
          buffer.flip(DirectByteBuffer.SS_MSG);
        } else if (msg_type == REJECT_MESSAGE_TYPE_ID) {
          Map<Object, Object> map = new HashMap<Object, Object>();
          map.put("msg_type", msg_type);
          map.put("piece", piece);
          buffer =
              MessagingUtil.convertPayloadToBencodedByteStream(
                  map, DirectByteBuffer.AL_MSG_UT_METADATA);
        } else {
          if (Logger.isEnabled()) {
            Logger.log(
                new LogEvent(
                    LOGID, LogEvent.LT_ERROR, "METADATA (UT): No valid msg_type=" + msg_type));
          }
          return null;
        }
      }

      return new DirectByteBuffer[] {buffer};
    } catch (Throwable e) {
      // what is the best way to handle this error?
      if (Logger.isEnabled()) {
        Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "METADATA (UT)", e));
      }
      return new DirectByteBuffer[0];
    }
  }
    private static byte[] getDummyPacket() throws IOException {
      DirectByteBuffer dbuffer =
          DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_MSG, OSF2FMessage.MAX_PAYLOAD_SIZE);
      while (dbuffer.hasRemaining(DirectByteBuffer.SS_NET)) {
        dbuffer.put(DirectByteBuffer.SS_NET, (byte) 0);
      }
      dbuffer.flip(DirectByteBuffer.SS_NET);

      DirectByteBuffer[] data =
          OSF2FMessageFactory.createOSF2FRawMessage(
                  new OSF2FChannelDataMsg(OSF2FMessage.CURRENT_VERSION, 0, -1, dbuffer))
              .getRawData();
      ByteBuffer buf = ByteBuffer.allocate(OSF2FMessage.MAX_MESSAGE_SIZE + 20);
      for (int i = 0; i < data.length; i++) {
        buf.put(data[i].getBuffer(DirectByteBuffer.SS_MSG));
      }
      buf.flip();
      byte[] b = new byte[buf.remaining()];
      buf.get(b);
      return b;
    }
  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;
  }
  protected void convert(int target_type) throws FMFileManagerException {

    if (type == target_type) {

      return;
    }

    if (type == FMFile.FT_PIECE_REORDER || target_type == FMFile.FT_PIECE_REORDER) {

      if (target_type == FMFile.FT_PIECE_REORDER_COMPACT
          || type == FMFile.FT_PIECE_REORDER_COMPACT) {

        // these two access modes are in fact identical at the moment

        type = target_type;

        return;
      }

      throw (new FMFileManagerException("Conversion to/from piece-reorder not supported"));
    }

    File file = owner.getLinkedFile();

    RandomAccessFile raf = null;

    boolean ok = false;

    try {
      FMFileAccess target_access;

      if (target_type == FMFile.FT_LINEAR) {

        target_access = new FMFileAccessLinear(owner);

      } else {

        target_access =
            new FMFileAccessCompact(
                owner.getOwner().getTorrentFile(),
                control_dir,
                controlFileName,
                new FMFileAccessLinear(owner));
      }

      if (file.exists()) {

        raf = new RandomAccessFile(file, FMFileImpl.WRITE_ACCESS_MODE);

        // due to the simplistic implementation of compact we only actually need to deal with
        // the last piece of the file (first piece is in the right place already)

        FMFileAccessCompact compact_access;

        if (target_type == FMFile.FT_LINEAR) {

          compact_access = (FMFileAccessCompact) file_access;

        } else {

          compact_access = (FMFileAccessCompact) target_access;
        }

        long length = file_access.getLength(raf);

        long last_piece_start = compact_access.getLastPieceStart();
        long last_piece_length = compact_access.getLastPieceLength();

        // see if we have any potential data for the last piece

        if (last_piece_length > 0 && length > last_piece_start) {

          long data_length = length - last_piece_start;

          if (data_length > last_piece_length) {

            Debug.out(
                "data length inconsistent: len=" + data_length + ",limit=" + last_piece_length);

            data_length = last_piece_length;
          }

          DirectByteBuffer buffer =
              DirectByteBufferPool.getBuffer(DirectByteBuffer.AL_FILE, (int) data_length);

          try {

            file_access.read(raf, new DirectByteBuffer[] {buffer}, last_piece_start);

            // see if we need to truncate

            if (target_type == FMFile.FT_COMPACT) {

              long first_piece_length = compact_access.getFirstPieceLength();

              long physical_length = raf.length();

              if (physical_length > first_piece_length) {

                raf.setLength(first_piece_length);
              }
            }

            buffer.flip(DirectByteBuffer.AL_FILE);

            target_access.write(raf, new DirectByteBuffer[] {buffer}, last_piece_start);

          } finally {

            buffer.returnToPool();
          }
        } else {

          // no last piece, truncate after the first piece

          if (target_type == FMFile.FT_COMPACT) {

            long first_piece_length = compact_access.getFirstPieceLength();

            long physical_length = raf.length();

            if (physical_length > first_piece_length) {

              raf.setLength(first_piece_length);
            }
          }
        }

        target_access.setLength(raf, length);

        target_access.flush();
      }

      type = target_type;
      file_access = target_access;

      ok = true;

    } catch (Throwable e) {

      Debug.printStackTrace(e);

      throw (new FMFileManagerException("convert fails", e));

    } finally {

      try {
        if (raf != null) {

          try {
            raf.close();

          } catch (Throwable e) {

            // override original exception if there isn't one

            if (ok) {

              ok = false;

              throw (new FMFileManagerException("convert fails", e));
            }
          }
        }
      } finally {

        if (!ok) {

          // conversion failed - replace with linear access, caller is responsible for
          // handling this (marking file requiring recheck)

          type = FMFile.FT_LINEAR;
          file_access = new FMFileAccessLinear(owner);
        }

        if (type == FMFile.FT_LINEAR) {

          new File(control_dir, controlFileName).delete();
        }
      }
    }
  }