+ * If there's not enough content left, {@link IndexOutOfBoundsException} is
+ * raised. The default value of newly allocated, wrapped or copied buffer's
+ * {@link #readerIndex() readerIndex} is {@code 0}.
+ *
+ *
+ * If there's not enough writable bytes left, {@link IndexOutOfBoundsException}
+ * is raised. The default value of newly allocated buffer's
+ * {@link #writerIndex() writerIndex} is {@code 0}. The default value of
+ * wrapped or copied buffer's {@link #writerIndex() writerIndex} is the
+ * {@link #capacity() capacity} of the buffer.
+ *
+ *
+ * In case a completely fresh copy of an existing buffer is required, please
+ * call {@link #copy()} method instead.
+ *
+ *
{
+
+ /**
+ * Returns the number of bytes (octets) this buffer can contain.
+ */
+ public abstract int capacity();
+
+ /**
+ * Adjusts the capacity of this buffer. If the {@code newCapacity} is less than the current
+ * capacity, the content of this buffer is truncated. If the {@code newCapacity} is greater
+ * than the current capacity, the buffer is appended with unspecified data whose length is
+ * {@code (newCapacity - currentCapacity)}.
+ */
+ public abstract ByteBuf capacity(int newCapacity);
+
+ /**
+ * Returns the maximum allowed capacity of this buffer. If a user attempts to increase the
+ * capacity of this buffer beyond the maximum capacity using {@link #capacity(int)} or
+ * {@link #ensureWritable(int)}, those methods will raise an
+ * {@link IllegalArgumentException}.
+ */
+ public abstract int maxCapacity();
+
+ /**
+ * Returns the {@link ByteBufAllocator} which created this buffer.
+ */
+ public abstract ByteBufAllocator alloc();
+
+ /**
+ * Returns the endianness
+ * of this buffer.
+ */
+ public abstract ByteOrder order();
+
+ /**
+ * Returns a buffer with the specified {@code endianness} which shares the whole region,
+ * indexes, and marks of this buffer. Modifying the content, the indexes, or the marks of the
+ * returned buffer or this buffer affects each other's content, indexes, and marks. If the
+ * specified {@code endianness} is identical to this buffer's byte order, this method can
+ * return {@code this}. This method does not modify {@code readerIndex} or {@code writerIndex}
+ * of this buffer.
+ */
+ public abstract ByteBuf order(ByteOrder endianness);
+
+ /**
+ * Return the underlying buffer instance if this buffer is a wrapper of another buffer.
+ *
+ * @return {@code null} if this buffer is not a wrapper
+ */
+ public abstract ByteBuf unwrap();
+
+ /**
+ * Returns {@code true} if and only if this buffer is backed by an
+ * NIO direct buffer.
+ */
+ public abstract boolean isDirect();
+
+ /**
+ * Returns the {@code readerIndex} of this buffer.
+ */
+ public abstract int readerIndex();
+
+ /**
+ * Sets the {@code readerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code readerIndex} is
+ * less than {@code 0} or
+ * greater than {@code this.writerIndex}
+ */
+ public abstract ByteBuf readerIndex(int readerIndex);
+
+ /**
+ * Returns the {@code writerIndex} of this buffer.
+ */
+ public abstract int writerIndex();
+
+ /**
+ * Sets the {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code writerIndex} is
+ * less than {@code this.readerIndex} or
+ * greater than {@code this.capacity}
+ */
+ public abstract ByteBuf writerIndex(int writerIndex);
+
+ /**
+ * Sets the {@code readerIndex} and {@code writerIndex} of this buffer
+ * in one shot. This method is useful when you have to worry about the
+ * invocation order of {@link #readerIndex(int)} and {@link #writerIndex(int)}
+ * methods. For example, the following code will fail:
+ *
+ *
+ * // Create a buffer whose readerIndex, writerIndex and capacity are
+ * // 0, 0 and 8 respectively.
+ * {@link ByteBuf} buf = {@link Unpooled}.buffer(8);
+ *
+ * // IndexOutOfBoundsException is thrown because the specified
+ * // readerIndex (2) cannot be greater than the current writerIndex (0).
+ * buf.readerIndex(2);
+ * buf.writerIndex(4);
+ *
+ *
+ * The following code will also fail:
+ *
+ *
+ * // Create a buffer whose readerIndex, writerIndex and capacity are
+ * // 0, 8 and 8 respectively.
+ * {@link ByteBuf} buf = {@link Unpooled}.wrappedBuffer(new byte[8]);
+ *
+ * // readerIndex becomes 8.
+ * buf.readLong();
+ *
+ * // IndexOutOfBoundsException is thrown because the specified
+ * // writerIndex (4) cannot be less than the current readerIndex (8).
+ * buf.writerIndex(4);
+ * buf.readerIndex(2);
+ *
+ *
+ * By contrast, this method guarantees that it never
+ * throws an {@link IndexOutOfBoundsException} as long as the specified
+ * indexes meet basic constraints, regardless what the current index
+ * values of the buffer are:
+ *
+ *
+ * // No matter what the current state of the buffer is, the following
+ * // call always succeeds as long as the capacity of the buffer is not
+ * // less than 4.
+ * buf.setIndex(2, 4);
+ *
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code readerIndex} is less than 0,
+ * if the specified {@code writerIndex} is less than the specified
+ * {@code readerIndex} or if the specified {@code writerIndex} is
+ * greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setIndex(int readerIndex, int writerIndex);
+
+ /**
+ * Returns the number of readable bytes which is equal to
+ * {@code (this.writerIndex - this.readerIndex)}.
+ */
+ public abstract int readableBytes();
+
+ /**
+ * Returns the number of writable bytes which is equal to
+ * {@code (this.capacity - this.writerIndex)}.
+ */
+ public abstract int writableBytes();
+
+ /**
+ * Returns the maximum possible number of writable bytes, which is equal to
+ * {@code (this.maxCapacity - this.writerIndex)}.
+ */
+ public abstract int maxWritableBytes();
+
+ /**
+ * Returns {@code true}
+ * if and only if {@code (this.writerIndex - this.readerIndex)} is greater
+ * than {@code 0}.
+ */
+ public abstract boolean isReadable();
+
+ /**
+ * Returns {@code true} if and only if this buffer contains equal to or more than the specified number of elements.
+ */
+ public abstract boolean isReadable(int size);
+
+ /**
+ * Returns {@code true}
+ * if and only if {@code (this.capacity - this.writerIndex)} is greater
+ * than {@code 0}.
+ */
+ public abstract boolean isWritable();
+
+ /**
+ * Returns {@code true} if and only if this buffer has enough room to allow writing the specified number of
+ * elements.
+ */
+ public abstract boolean isWritable(int size);
+
+ /**
+ * Sets the {@code readerIndex} and {@code writerIndex} of this buffer to
+ * {@code 0}.
+ * This method is identical to {@link #setIndex(int, int) setIndex(0, 0)}.
+ *
+ * Please note that the behavior of this method is different
+ * from that of NIO buffer, which sets the {@code limit} to
+ * the {@code capacity} of the buffer.
+ */
+ public abstract ByteBuf clear();
+
+ /**
+ * Marks the current {@code readerIndex} in this buffer. You can
+ * reposition the current {@code readerIndex} to the marked
+ * {@code readerIndex} by calling {@link #resetReaderIndex()}.
+ * The initial value of the marked {@code readerIndex} is {@code 0}.
+ */
+ public abstract ByteBuf markReaderIndex();
+
+ /**
+ * Repositions the current {@code readerIndex} to the marked
+ * {@code readerIndex} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the current {@code writerIndex} is less than the marked
+ * {@code readerIndex}
+ */
+ public abstract ByteBuf resetReaderIndex();
+
+ /**
+ * Marks the current {@code writerIndex} in this buffer. You can
+ * reposition the current {@code writerIndex} to the marked
+ * {@code writerIndex} by calling {@link #resetWriterIndex()}.
+ * The initial value of the marked {@code writerIndex} is {@code 0}.
+ */
+ public abstract ByteBuf markWriterIndex();
+
+ /**
+ * Repositions the current {@code writerIndex} to the marked
+ * {@code writerIndex} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the current {@code readerIndex} is greater than the marked
+ * {@code writerIndex}
+ */
+ public abstract ByteBuf resetWriterIndex();
+
+ /**
+ * Discards the bytes between the 0th index and {@code readerIndex}.
+ * It moves the bytes between {@code readerIndex} and {@code writerIndex}
+ * to the 0th index, and sets {@code readerIndex} and {@code writerIndex}
+ * to {@code 0} and {@code oldWriterIndex - oldReaderIndex} respectively.
+ *
+ * Please refer to the class documentation for more detailed explanation.
+ */
+ public abstract ByteBuf discardReadBytes();
+
+ /**
+ * Similar to {@link ByteBuf#discardReadBytes()} except that this method might discard
+ * some, all, or none of read bytes depending on its internal implementation to reduce
+ * overall memory bandwidth consumption at the cost of potentially additional memory
+ * consumption.
+ */
+ public abstract ByteBuf discardSomeReadBytes();
+
+ /**
+ * Makes sure the number of {@linkplain #writableBytes() the writable bytes}
+ * is equal to or greater than the specified value. If there is enough
+ * writable bytes in this buffer, this method returns with no side effect.
+ * Otherwise, it raises an {@link IllegalArgumentException}.
+ *
+ * @param minWritableBytes
+ * the expected minimum number of writable bytes
+ * @throws IndexOutOfBoundsException
+ * if {@link #writerIndex()} + {@code minWritableBytes} > {@link #maxCapacity()}
+ */
+ public abstract ByteBuf ensureWritable(int minWritableBytes);
+
+ /**
+ * Tries to make sure the number of {@linkplain #writableBytes() the writable bytes}
+ * is equal to or greater than the specified value. Unlike {@link #ensureWritable(int)},
+ * this method does not raise an exception but returns a code.
+ *
+ * @param minWritableBytes
+ * the expected minimum number of writable bytes
+ * @param force
+ * When {@link #writerIndex()} + {@code minWritableBytes} > {@link #maxCapacity()}:
+ *
+ * {@code true} - the capacity of the buffer is expanded to {@link #maxCapacity()}
+ * {@code false} - the capacity of the buffer is unchanged
+ *
+ * @return {@code 0} if the buffer has enough writable bytes, and its capacity is unchanged.
+ * {@code 1} if the buffer does not have enough bytes, and its capacity is unchanged.
+ * {@code 2} if the buffer has enough writable bytes, and its capacity has been increased.
+ * {@code 3} if the buffer does not have enough bytes, but its capacity has been
+ * increased to its maximum.
+ */
+ public abstract int ensureWritable(int minWritableBytes, boolean force);
+
+ /**
+ * Gets a boolean at the specified absolute (@code index) in this buffer.
+ * This method does not modify the {@code readerIndex} or {@code writerIndex}
+ * of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 1} is greater than {@code this.capacity}
+ */
+ public abstract boolean getBoolean(int index);
+
+ /**
+ * Gets a byte at the specified absolute {@code index} in this buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 1} is greater than {@code this.capacity}
+ */
+ public abstract byte getByte(int index);
+
+ /**
+ * Gets an unsigned byte at the specified absolute {@code index} in this
+ * buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 1} is greater than {@code this.capacity}
+ */
+ public abstract short getUnsignedByte(int index);
+
+ /**
+ * Gets a 16-bit short integer at the specified absolute {@code index} in
+ * this buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 2} is greater than {@code this.capacity}
+ */
+ public abstract short getShort(int index);
+
+ /**
+ * Gets an unsigned 16-bit short integer at the specified absolute
+ * {@code index} in this buffer. This method does not modify
+ * {@code readerIndex} or {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 2} is greater than {@code this.capacity}
+ */
+ public abstract int getUnsignedShort(int index);
+
+ /**
+ * Gets a 24-bit medium integer at the specified absolute {@code index} in
+ * this buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 3} is greater than {@code this.capacity}
+ */
+ public abstract int getMedium(int index);
+
+ /**
+ * Gets an unsigned 24-bit medium integer at the specified absolute
+ * {@code index} in this buffer. This method does not modify
+ * {@code readerIndex} or {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 3} is greater than {@code this.capacity}
+ */
+ public abstract int getUnsignedMedium(int index);
+
+ /**
+ * Gets a 32-bit integer at the specified absolute {@code index} in
+ * this buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 4} is greater than {@code this.capacity}
+ */
+ public abstract int getInt(int index);
+
+ /**
+ * Gets an unsigned 32-bit integer at the specified absolute {@code index}
+ * in this buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 4} is greater than {@code this.capacity}
+ */
+ public abstract long getUnsignedInt(int index);
+
+ /**
+ * Gets a 64-bit long integer at the specified absolute {@code index} in
+ * this buffer. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 8} is greater than {@code this.capacity}
+ */
+ public abstract long getLong(int index);
+
+ /**
+ * Gets a 2-byte UTF-16 character at the specified absolute
+ * {@code index} in this buffer. This method does not modify
+ * {@code readerIndex} or {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 2} is greater than {@code this.capacity}
+ */
+ public abstract char getChar(int index);
+
+ /**
+ * Gets a 32-bit floating point number at the specified absolute
+ * {@code index} in this buffer. This method does not modify
+ * {@code readerIndex} or {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 4} is greater than {@code this.capacity}
+ */
+ public abstract float getFloat(int index);
+
+ /**
+ * Gets a 64-bit floating point number at the specified absolute
+ * {@code index} in this buffer. This method does not modify
+ * {@code readerIndex} or {@code writerIndex} of this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 8} is greater than {@code this.capacity}
+ */
+ public abstract double getDouble(int index);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index} until the destination becomes
+ * non-writable. This method is basically same with
+ * {@link #getBytes(int, ByteBuf, int, int)}, except that this
+ * method increases the {@code writerIndex} of the destination by the
+ * number of the transferred bytes while
+ * {@link #getBytes(int, ByteBuf, int, int)} does not.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * the source buffer (i.e. {@code this}).
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + dst.writableBytes} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf getBytes(int index, ByteBuf dst);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index}. This method is basically same
+ * with {@link #getBytes(int, ByteBuf, int, int)}, except that this
+ * method increases the {@code writerIndex} of the destination by the
+ * number of the transferred bytes while
+ * {@link #getBytes(int, ByteBuf, int, int)} does not.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * the source buffer (i.e. {@code this}).
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code length} is greater than {@code dst.writableBytes}
+ */
+ public abstract ByteBuf getBytes(int index, ByteBuf dst, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex}
+ * of both the source (i.e. {@code this}) and the destination.
+ *
+ * @param dstIndex the first index of the destination
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if the specified {@code dstIndex} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code dstIndex + length} is greater than
+ * {@code dst.capacity}
+ */
+ public abstract ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + dst.length} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf getBytes(int index, byte[] dst);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex}
+ * of this buffer.
+ *
+ * @param dstIndex the first index of the destination
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if the specified {@code dstIndex} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code dstIndex + length} is greater than
+ * {@code dst.length}
+ */
+ public abstract ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the specified absolute {@code index} until the destination's position
+ * reaches its limit.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer while the destination's {@code position} will be increased.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + dst.remaining()} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf getBytes(int index, ByteBuffer dst);
+
+ /**
+ * Transfers this buffer's data to the specified stream starting at the
+ * specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + length} is greater than
+ * {@code this.capacity}
+ * @throws IOException
+ * if the specified stream threw an exception during I/O
+ */
+ public abstract ByteBuf getBytes(int index, OutputStream out, int length) throws IOException;
+
+ /**
+ * Transfers this buffer's data to the specified channel starting at the
+ * specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @param length the maximum number of bytes to transfer
+ *
+ * @return the actual number of bytes written out to the specified channel
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + length} is greater than
+ * {@code this.capacity}
+ * @throws IOException
+ * if the specified channel threw an exception during I/O
+ */
+ public abstract int getBytes(int index, GatheringByteChannel out, int length) throws IOException;
+
+ /**
+ * Sets the specified boolean at the specified absolute {@code index} in this
+ * buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 1} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setBoolean(int index, boolean value);
+
+ /**
+ * Sets the specified byte at the specified absolute {@code index} in this
+ * buffer. The 24 high-order bits of the specified value are ignored.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 1} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setByte(int index, int value);
+
+ /**
+ * Sets the specified 16-bit short integer at the specified absolute
+ * {@code index} in this buffer. The 16 high-order bits of the specified
+ * value are ignored.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 2} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setShort(int index, int value);
+
+ /**
+ * Sets the specified 24-bit medium integer at the specified absolute
+ * {@code index} in this buffer. Please note that the most significant
+ * byte is ignored in the specified value.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 3} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setMedium(int index, int value);
+
+ /**
+ * Sets the specified 32-bit integer at the specified absolute
+ * {@code index} in this buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 4} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setInt(int index, int value);
+
+ /**
+ * Sets the specified 64-bit long integer at the specified absolute
+ * {@code index} in this buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 8} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setLong(int index, long value);
+
+ /**
+ * Sets the specified 2-byte UTF-16 character at the specified absolute
+ * {@code index} in this buffer.
+ * The 16 high-order bits of the specified value are ignored.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 2} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setChar(int index, int value);
+
+ /**
+ * Sets the specified 32-bit floating-point number at the specified
+ * absolute {@code index} in this buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 4} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setFloat(int index, float value);
+
+ /**
+ * Sets the specified 64-bit floating-point number at the specified
+ * absolute {@code index} in this buffer.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * {@code index + 8} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setDouble(int index, double value);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the specified absolute {@code index} until the source buffer becomes
+ * unreadable. This method is basically same with
+ * {@link #setBytes(int, ByteBuf, int, int)}, except that this
+ * method increases the {@code readerIndex} of the source buffer by
+ * the number of the transferred bytes while
+ * {@link #setBytes(int, ByteBuf, int, int)} does not.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * the source buffer (i.e. {@code this}).
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + src.readableBytes} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf setBytes(int index, ByteBuf src);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the specified absolute {@code index}. This method is basically same
+ * with {@link #setBytes(int, ByteBuf, int, int)}, except that this
+ * method increases the {@code readerIndex} of the source buffer by
+ * the number of the transferred bytes while
+ * {@link #setBytes(int, ByteBuf, int, int)} does not.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * the source buffer (i.e. {@code this}).
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code length} is greater than {@code src.readableBytes}
+ */
+ public abstract ByteBuf setBytes(int index, ByteBuf src, int length);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex}
+ * of both the source (i.e. {@code this}) and the destination.
+ *
+ * @param srcIndex the first index of the source
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if the specified {@code srcIndex} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code srcIndex + length} is greater than
+ * {@code src.capacity}
+ */
+ public abstract ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length);
+
+ /**
+ * Transfers the specified source array's data to this buffer starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + src.length} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf setBytes(int index, byte[] src);
+
+ /**
+ * Transfers the specified source array's data to this buffer starting at
+ * the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0},
+ * if the specified {@code srcIndex} is less than {@code 0},
+ * if {@code index + length} is greater than
+ * {@code this.capacity}, or
+ * if {@code srcIndex + length} is greater than {@code src.length}
+ */
+ public abstract ByteBuf setBytes(int index, byte[] src, int srcIndex, int length);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the specified absolute {@code index} until the source buffer's position
+ * reaches its limit.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + src.remaining()} is greater than
+ * {@code this.capacity}
+ */
+ public abstract ByteBuf setBytes(int index, ByteBuffer src);
+
+ /**
+ * Transfers the content of the specified source stream to this buffer
+ * starting at the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @return the actual number of bytes read in from the specified channel.
+ * {@code -1} if the specified channel is closed.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + length} is greater than {@code this.capacity}
+ * @throws IOException
+ * if the specified stream threw an exception during I/O
+ */
+ public abstract int setBytes(int index, InputStream in, int length) throws IOException;
+
+ /**
+ * Transfers the content of the specified source channel to this buffer
+ * starting at the specified absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @param length the maximum number of bytes to transfer
+ *
+ * @return the actual number of bytes read in from the specified channel.
+ * {@code -1} if the specified channel is closed.
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + length} is greater than {@code this.capacity}
+ * @throws IOException
+ * if the specified channel threw an exception during I/O
+ */
+ public abstract int setBytes(int index, ScatteringByteChannel in, int length) throws IOException;
+
+ /**
+ * Fills this buffer with NUL (0x00) starting at the specified
+ * absolute {@code index}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @param length the number of NUL s to write to the buffer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code index} is less than {@code 0} or
+ * if {@code index + length} is greater than {@code this.capacity}
+ */
+ public abstract ByteBuf setZero(int index, int length);
+
+ /**
+ * Gets a boolean at the current {@code readerIndex} and increases
+ * the {@code readerIndex} by {@code 1} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 1}
+ */
+ public abstract boolean readBoolean();
+
+ /**
+ * Gets a byte at the current {@code readerIndex} and increases
+ * the {@code readerIndex} by {@code 1} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 1}
+ */
+ public abstract byte readByte();
+
+ /**
+ * Gets an unsigned byte at the current {@code readerIndex} and increases
+ * the {@code readerIndex} by {@code 1} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 1}
+ */
+ public abstract short readUnsignedByte();
+
+ /**
+ * Gets a 16-bit short integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
+ */
+ public abstract short readShort();
+
+ /**
+ * Gets an unsigned 16-bit short integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
+ */
+ public abstract int readUnsignedShort();
+
+ /**
+ * Gets a 24-bit medium integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 3} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 3}
+ */
+ public abstract int readMedium();
+
+ /**
+ * Gets an unsigned 24-bit medium integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 3} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 3}
+ */
+ public abstract int readUnsignedMedium();
+
+ /**
+ * Gets a 32-bit integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 4} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
+ */
+ public abstract int readInt();
+
+ /**
+ * Gets an unsigned 32-bit integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 4} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
+ */
+ public abstract long readUnsignedInt();
+
+ /**
+ * Gets a 64-bit integer at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 8} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
+ */
+ public abstract long readLong();
+
+ /**
+ * Gets a 2-byte UTF-16 character at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 2} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 2}
+ */
+ public abstract char readChar();
+
+ /**
+ * Gets a 32-bit floating point number at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 4} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 4}
+ */
+ public abstract float readFloat();
+
+ /**
+ * Gets a 64-bit floating point number at the current {@code readerIndex}
+ * and increases the {@code readerIndex} by {@code 8} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.readableBytes} is less than {@code 8}
+ */
+ public abstract double readDouble();
+
+ /**
+ * Transfers this buffer's data to a newly created buffer starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code length}).
+ * The returned buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0} and {@code length} respectively.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @return the newly created buffer which contains the transferred bytes
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ */
+ public abstract ByteBuf readBytes(int length);
+
+ /**
+ * Returns a new slice of this buffer's sub-region starting at the current
+ * {@code readerIndex} and increases the {@code readerIndex} by the size
+ * of the new slice (= {@code length}).
+ *
+ * @param length the size of the new slice
+ *
+ * @return the newly created slice
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ */
+ public abstract ByteBuf readSlice(int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} until the destination becomes
+ * non-writable, and increases the {@code readerIndex} by the number of the
+ * transferred bytes. This method is basically same with
+ * {@link #readBytes(ByteBuf, int, int)}, except that this method
+ * increases the {@code writerIndex} of the destination by the number of
+ * the transferred bytes while {@link #readBytes(ByteBuf, int, int)}
+ * does not.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code dst.writableBytes} is greater than
+ * {@code this.readableBytes}
+ */
+ public abstract ByteBuf readBytes(ByteBuf dst);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code length}). This method
+ * is basically same with {@link #readBytes(ByteBuf, int, int)},
+ * except that this method increases the {@code writerIndex} of the
+ * destination by the number of the transferred bytes (= {@code length})
+ * while {@link #readBytes(ByteBuf, int, int)} does not.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes} or
+ * if {@code length} is greater than {@code dst.writableBytes}
+ */
+ public abstract ByteBuf readBytes(ByteBuf dst, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code length}).
+ *
+ * @param dstIndex the first index of the destination
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code dstIndex} is less than {@code 0},
+ * if {@code length} is greater than {@code this.readableBytes}, or
+ * if {@code dstIndex + length} is greater than
+ * {@code dst.capacity}
+ */
+ public abstract ByteBuf readBytes(ByteBuf dst, int dstIndex, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code dst.length}).
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code dst.length} is greater than {@code this.readableBytes}
+ */
+ public abstract ByteBuf readBytes(byte[] dst);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} and increases the {@code readerIndex}
+ * by the number of the transferred bytes (= {@code length}).
+ *
+ * @param dstIndex the first index of the destination
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code dstIndex} is less than {@code 0},
+ * if {@code length} is greater than {@code this.readableBytes}, or
+ * if {@code dstIndex + length} is greater than {@code dst.length}
+ */
+ public abstract ByteBuf readBytes(byte[] dst, int dstIndex, int length);
+
+ /**
+ * Transfers this buffer's data to the specified destination starting at
+ * the current {@code readerIndex} until the destination's position
+ * reaches its limit, and increases the {@code readerIndex} by the
+ * number of the transferred bytes.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code dst.remaining()} is greater than
+ * {@code this.readableBytes}
+ */
+ public abstract ByteBuf readBytes(ByteBuffer dst);
+
+ /**
+ * Transfers this buffer's data to the specified stream starting at the
+ * current {@code readerIndex}.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ * @throws IOException
+ * if the specified stream threw an exception during I/O
+ */
+ public abstract ByteBuf readBytes(OutputStream out, int length) throws IOException;
+
+ /**
+ * Transfers this buffer's data to the specified stream starting at the
+ * current {@code readerIndex}.
+ *
+ * @param length the maximum number of bytes to transfer
+ *
+ * @return the actual number of bytes written out to the specified channel
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ * @throws IOException
+ * if the specified channel threw an exception during I/O
+ */
+ public abstract int readBytes(GatheringByteChannel out, int length) throws IOException;
+
+ /**
+ * Increases the current {@code readerIndex} by the specified
+ * {@code length} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ */
+ public abstract ByteBuf skipBytes(int length);
+
+ /**
+ * Sets the specified boolean at the current {@code writerIndex}
+ * and increases the {@code writerIndex} by {@code 1} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 1}
+ */
+ public abstract ByteBuf writeBoolean(boolean value);
+
+ /**
+ * Sets the specified byte at the current {@code writerIndex}
+ * and increases the {@code writerIndex} by {@code 1} in this buffer.
+ * The 24 high-order bits of the specified value are ignored.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 1}
+ */
+ public abstract ByteBuf writeByte(int value);
+
+ /**
+ * Sets the specified 16-bit short integer at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 2}
+ * in this buffer. The 16 high-order bits of the specified value are ignored.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 2}
+ */
+ public abstract ByteBuf writeShort(int value);
+
+ /**
+ * Sets the specified 24-bit medium integer at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 3}
+ * in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 3}
+ */
+ public abstract ByteBuf writeMedium(int value);
+
+ /**
+ * Sets the specified 32-bit integer at the current {@code writerIndex}
+ * and increases the {@code writerIndex} by {@code 4} in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 4}
+ */
+ public abstract ByteBuf writeInt(int value);
+
+ /**
+ * Sets the specified 64-bit long integer at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 8}
+ * in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 8}
+ */
+ public abstract ByteBuf writeLong(long value);
+
+ /**
+ * Sets the specified 2-byte UTF-16 character at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 2}
+ * in this buffer. The 16 high-order bits of the specified value are ignored.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 2}
+ */
+ public abstract ByteBuf writeChar(int value);
+
+ /**
+ * Sets the specified 32-bit floating point number at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 4}
+ * in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 4}
+ */
+ public abstract ByteBuf writeFloat(float value);
+
+ /**
+ * Sets the specified 64-bit floating point number at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by {@code 8}
+ * in this buffer.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code this.writableBytes} is less than {@code 8}
+ */
+ public abstract ByteBuf writeDouble(double value);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the current {@code writerIndex} until the source buffer becomes
+ * unreadable, and increases the {@code writerIndex} by the number of
+ * the transferred bytes. This method is basically same with
+ * {@link #writeBytes(ByteBuf, int, int)}, except that this method
+ * increases the {@code readerIndex} of the source buffer by the number of
+ * the transferred bytes while {@link #writeBytes(ByteBuf, int, int)}
+ * does not.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code src.readableBytes} is greater than
+ * {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeBytes(ByteBuf src);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the current {@code writerIndex} and increases the {@code writerIndex}
+ * by the number of the transferred bytes (= {@code length}). This method
+ * is basically same with {@link #writeBytes(ByteBuf, int, int)},
+ * except that this method increases the {@code readerIndex} of the source
+ * buffer by the number of the transferred bytes (= {@code length}) while
+ * {@link #writeBytes(ByteBuf, int, int)} does not.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.writableBytes} or
+ * if {@code length} is greater then {@code src.readableBytes}
+ */
+ public abstract ByteBuf writeBytes(ByteBuf src, int length);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the current {@code writerIndex} and increases the {@code writerIndex}
+ * by the number of the transferred bytes (= {@code length}).
+ *
+ * @param srcIndex the first index of the source
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code srcIndex} is less than {@code 0},
+ * if {@code srcIndex + length} is greater than
+ * {@code src.capacity}, or
+ * if {@code length} is greater than {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeBytes(ByteBuf src, int srcIndex, int length);
+
+ /**
+ * Transfers the specified source array's data to this buffer starting at
+ * the current {@code writerIndex} and increases the {@code writerIndex}
+ * by the number of the transferred bytes (= {@code src.length}).
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code src.length} is greater than {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeBytes(byte[] src);
+
+ /**
+ * Transfers the specified source array's data to this buffer starting at
+ * the current {@code writerIndex} and increases the {@code writerIndex}
+ * by the number of the transferred bytes (= {@code length}).
+ *
+ * @param srcIndex the first index of the source
+ * @param length the number of bytes to transfer
+ *
+ * @throws IndexOutOfBoundsException
+ * if the specified {@code srcIndex} is less than {@code 0},
+ * if {@code srcIndex + length} is greater than
+ * {@code src.length}, or
+ * if {@code length} is greater than {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeBytes(byte[] src, int srcIndex, int length);
+
+ /**
+ * Transfers the specified source buffer's data to this buffer starting at
+ * the current {@code writerIndex} until the source buffer's position
+ * reaches its limit, and increases the {@code writerIndex} by the
+ * number of the transferred bytes.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code src.remaining()} is greater than
+ * {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeBytes(ByteBuffer src);
+
+ /**
+ * Transfers the content of the specified stream to this buffer
+ * starting at the current {@code writerIndex} and increases the
+ * {@code writerIndex} by the number of the transferred bytes.
+ *
+ * @param length the number of bytes to transfer
+ *
+ * @return the actual number of bytes read in from the specified stream
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.writableBytes}
+ * @throws IOException
+ * if the specified stream threw an exception during I/O
+ */
+ public abstract int writeBytes(InputStream in, int length) throws IOException;
+
+ /**
+ * Transfers the content of the specified channel to this buffer
+ * starting at the current {@code writerIndex} and increases the
+ * {@code writerIndex} by the number of the transferred bytes.
+ *
+ * @param length the maximum number of bytes to transfer
+ *
+ * @return the actual number of bytes read in from the specified channel
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.writableBytes}
+ * @throws IOException
+ * if the specified channel threw an exception during I/O
+ */
+ public abstract int writeBytes(ScatteringByteChannel in, int length) throws IOException;
+
+ /**
+ * Fills this buffer with NUL (0x00) starting at the current
+ * {@code writerIndex} and increases the {@code writerIndex} by the
+ * specified {@code length}.
+ *
+ * @param length the number of NUL s to write to the buffer
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.writableBytes}
+ */
+ public abstract ByteBuf writeZero(int length);
+
+ /**
+ * Locates the first occurrence of the specified {@code value} in this
+ * buffer. The search takes place from the specified {@code fromIndex}
+ * (inclusive) to the specified {@code toIndex} (exclusive).
+ *
+ * If {@code fromIndex} is greater than {@code toIndex}, the search is
+ * performed in a reversed order.
+ *
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @return the absolute index of the first occurrence if found.
+ * {@code -1} otherwise.
+ */
+ public abstract int indexOf(int fromIndex, int toIndex, byte value);
+
+ /**
+ * Locates the first occurrence of the specified {@code value} in this
+ * buffer. The search takes place from the current {@code readerIndex}
+ * (inclusive) to the current {@code writerIndex} (exclusive).
+ *
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @return the number of bytes between the current {@code readerIndex}
+ * and the first occurrence if found. {@code -1} otherwise.
+ */
+ public abstract int bytesBefore(byte value);
+
+ /**
+ * Locates the first occurrence of the specified {@code value} in this
+ * buffer. The search starts from the current {@code readerIndex}
+ * (inclusive) and lasts for the specified {@code length}.
+ *
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @return the number of bytes between the current {@code readerIndex}
+ * and the first occurrence if found. {@code -1} otherwise.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code length} is greater than {@code this.readableBytes}
+ */
+ public abstract int bytesBefore(int length, byte value);
+
+ /**
+ * Locates the first occurrence of the specified {@code value} in this
+ * buffer. The search starts from the specified {@code index} (inclusive)
+ * and lasts for the specified {@code length}.
+ *
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @return the number of bytes between the specified {@code index}
+ * and the first occurrence if found. {@code -1} otherwise.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code index + length} is greater than {@code this.capacity}
+ */
+ public abstract int bytesBefore(int index, int length, byte value);
+
+ /**
+ * Iterates over the readable bytes of this buffer with the specified {@code processor} in ascending order.
+ *
+ * @return {@code -1} if the processor iterated to or beyond the end of the readable bytes.
+ * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}.
+ */
+ public abstract int forEachByte(ByteBufProcessor processor);
+
+ /**
+ * Iterates over the specified area of this buffer with the specified {@code processor} in ascending order.
+ * (i.e. {@code index}, {@code (index + 1)}, .. {@code (index + length - 1)})
+ *
+ * @return {@code -1} if the processor iterated to or beyond the end of the specified area.
+ * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}.
+ */
+ public abstract int forEachByte(int index, int length, ByteBufProcessor processor);
+
+ /**
+ * Iterates over the readable bytes of this buffer with the specified {@code processor} in descending order.
+ *
+ * @return {@code -1} if the processor iterated to or beyond the beginning of the readable bytes.
+ * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}.
+ */
+ public abstract int forEachByteDesc(ByteBufProcessor processor);
+
+ /**
+ * Iterates over the specified area of this buffer with the specified {@code processor} in descending order.
+ * (i.e. {@code (index + length - 1)}, {@code (index + length - 2)}, ... {@code index})
+ *
+ *
+ * @return {@code -1} if the processor iterated to or beyond the beginning of the specified area.
+ * The last-visited index If the {@link ByteBufProcessor#process(byte)} returned {@code false}.
+ */
+ public abstract int forEachByteDesc(int index, int length, ByteBufProcessor processor);
+
+ /**
+ * Returns a copy of this buffer's readable bytes. Modifying the content
+ * of the returned buffer or this buffer does not affect each other at all.
+ * This method is identical to {@code buf.copy(buf.readerIndex(), buf.readableBytes())}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ */
+ public abstract ByteBuf copy();
+
+ /**
+ * Returns a copy of this buffer's sub-region. Modifying the content of
+ * the returned buffer or this buffer does not affect each other at all.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ */
+ public abstract ByteBuf copy(int index, int length);
+
+ /**
+ * Returns a slice of this buffer's readable bytes. Modifying the content
+ * of the returned buffer or this buffer affects each other's content
+ * while they maintain separate indexes and marks. This method is
+ * identical to {@code buf.slice(buf.readerIndex(), buf.readableBytes())}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ */
+ public abstract ByteBuf slice();
+
+ /**
+ * Returns a slice of this buffer's sub-region. Modifying the content of
+ * the returned buffer or this buffer affects each other's content while
+ * they maintain separate indexes and marks.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ */
+ public abstract ByteBuf slice(int index, int length);
+
+ /**
+ * Returns a buffer which shares the whole region of this buffer.
+ * Modifying the content of the returned buffer or this buffer affects
+ * each other's content while they maintain separate indexes and marks.
+ * This method is identical to {@code buf.slice(0, buf.capacity())}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ */
+ public abstract ByteBuf duplicate();
+
+ /**
+ * Returns the maximum number of NIO {@link ByteBuffer}s that consist this buffer. Note that {@link #nioBuffers()}
+ * or {@link #nioBuffers(int, int)} might return a less number of {@link ByteBuffer}s.
+ *
+ * @return {@code -1} if this buffer has no underlying {@link ByteBuffer}.
+ * the number of the underlying {@link ByteBuffer}s if this buffer has at least one underlying
+ * {@link ByteBuffer}. Note that this method does not return {@code 0} to avoid confusion.
+ *
+ * @see #nioBuffer()
+ * @see #nioBuffer(int, int)
+ * @see #nioBuffers()
+ * @see #nioBuffers(int, int)
+ */
+ public abstract int nioBufferCount();
+
+ /**
+ * Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}. The returned buffer
+ * shares the content with this buffer, while changing the position and limit of the returned
+ * NIO buffer does not affect the indexes and marks of this buffer. This method is identical
+ * to {@code buf.nioBuffer(buf.readerIndex(), buf.readableBytes())}. This method does not
+ * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the
+ * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic
+ * buffer and it adjusted its capacity.
+ *
+ * @throws UnsupportedOperationException
+ * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself
+ *
+ * @see #nioBufferCount()
+ * @see #nioBuffers()
+ * @see #nioBuffers(int, int)
+ */
+ public abstract ByteBuffer nioBuffer();
+
+ /**
+ * Exposes this buffer's sub-region as an NIO {@link ByteBuffer}. The returned buffer
+ * shares the content with this buffer, while changing the position and limit of the returned
+ * NIO buffer does not affect the indexes and marks of this buffer. This method does not
+ * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the
+ * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic
+ * buffer and it adjusted its capacity.
+ *
+ * @throws UnsupportedOperationException
+ * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself
+ *
+ * @see #nioBufferCount()
+ * @see #nioBuffers()
+ * @see #nioBuffers(int, int)
+ */
+ public abstract ByteBuffer nioBuffer(int index, int length);
+
+ /**
+ * Internal use only: Exposes the internal NIO buffer.
+ */
+ public abstract ByteBuffer internalNioBuffer(int index, int length);
+
+ /**
+ * Exposes this buffer's readable bytes as an NIO {@link ByteBuffer}'s. The returned buffer
+ * shares the content with this buffer, while changing the position and limit of the returned
+ * NIO buffer does not affect the indexes and marks of this buffer. This method does not
+ * modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the
+ * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic
+ * buffer and it adjusted its capacity.
+ *
+ *
+ * @throws UnsupportedOperationException
+ * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself
+ *
+ * @see #nioBufferCount()
+ * @see #nioBuffer()
+ * @see #nioBuffer(int, int)
+ */
+ public abstract ByteBuffer[] nioBuffers();
+
+ /**
+ * Exposes this buffer's bytes as an NIO {@link ByteBuffer}'s for the specified index and length
+ * The returned buffer shares the content with this buffer, while changing the position and limit
+ * of the returned NIO buffer does not affect the indexes and marks of this buffer. This method does
+ * not modify {@code readerIndex} or {@code writerIndex} of this buffer. Please note that the
+ * returned NIO buffer will not see the changes of this buffer if this buffer is a dynamic
+ * buffer and it adjusted its capacity.
+ *
+ * @throws UnsupportedOperationException
+ * if this buffer cannot create a {@link ByteBuffer} that shares the content with itself
+ *
+ * @see #nioBufferCount()
+ * @see #nioBuffer()
+ * @see #nioBuffer(int, int)
+ */
+ public abstract ByteBuffer[] nioBuffers(int index, int length);
+
+ /**
+ * Returns {@code true} if and only if this buffer has a backing byte array.
+ * If this method returns true, you can safely call {@link #array()} and
+ * {@link #arrayOffset()}.
+ */
+ public abstract boolean hasArray();
+
+ /**
+ * Returns the backing byte array of this buffer.
+ *
+ * @throws UnsupportedOperationException
+ * if there no accessible backing byte array
+ */
+ public abstract byte[] array();
+
+ /**
+ * Returns the offset of the first byte within the backing byte array of
+ * this buffer.
+ *
+ * @throws UnsupportedOperationException
+ * if there no accessible backing byte array
+ */
+ public abstract int arrayOffset();
+
+ /**
+ * Returns {@code true} if and only if this buffer has a reference to the low-level memory address that points
+ * to the backing data.
+ */
+ public abstract boolean hasMemoryAddress();
+
+ /**
+ * Returns the low-level memory address that point to the first byte of ths backing data.
+ *
+ * @throws UnsupportedOperationException
+ * if this buffer does not support accessing the low-level memory address
+ */
+ public abstract long memoryAddress();
+
+ /**
+ * Decodes this buffer's readable bytes into a string with the specified
+ * character set name. This method is identical to
+ * {@code buf.toString(buf.readerIndex(), buf.readableBytes(), charsetName)}.
+ * This method does not modify {@code readerIndex} or {@code writerIndex} of
+ * this buffer.
+ *
+ * @throws UnsupportedCharsetException
+ * if the specified character set name is not supported by the
+ * current VM
+ */
+ public abstract String toString(Charset charset);
+
+ /**
+ * Decodes this buffer's sub-region into a string with the specified
+ * character set. This method does not modify {@code readerIndex} or
+ * {@code writerIndex} of this buffer.
+ */
+ public abstract String toString(int index, int length, Charset charset);
+
+ /**
+ * Returns a hash code which was calculated from the content of this
+ * buffer. If there's a byte array which is
+ * {@linkplain #equals(Object) equal to} this array, both arrays should
+ * return the same value.
+ */
+ @Override
+ public abstract int hashCode();
+
+ /**
+ * Determines if the content of the specified buffer is identical to the
+ * content of this array. 'Identical' here means:
+ *
+ * the size of the contents of the two buffers are same and
+ * every single byte of the content of the two buffers are same.
+ *
+ * Please note that it does not compare {@link #readerIndex()} nor
+ * {@link #writerIndex()}. This method also returns {@code false} for
+ * {@code null} and an object which is not an instance of
+ * {@link ByteBuf} type.
+ */
+ @Override
+ public abstract boolean equals(Object obj);
+
+ /**
+ * Compares the content of the specified buffer to the content of this
+ * buffer. Comparison is performed in the same manner with the string
+ * comparison functions of various languages such as {@code strcmp},
+ * {@code memcmp} and {@link String#compareTo(String)}.
+ */
+ @Override
+ public abstract int compareTo(ByteBuf buffer);
+
+ /**
+ * Returns the string representation of this buffer. This method does not
+ * necessarily return the whole content of the buffer but returns
+ * the values of the key properties such as {@link #readerIndex()},
+ * {@link #writerIndex()} and {@link #capacity()}.
+ */
+ @Override
+ public abstract String toString();
+
+ @Override
+ public abstract ByteBuf retain(int increment);
+
+ @Override
+ public abstract ByteBuf retain();
+}
diff --git a/common/src/common/net/buffer/ByteBufAllocator.java b/common/src/common/net/buffer/ByteBufAllocator.java
new file mode 100644
index 0000000..045f669
--- /dev/null
+++ b/common/src/common/net/buffer/ByteBufAllocator.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+/**
+ * Implementations are responsible to allocate buffers. Implementations of this interface are expected to be
+ * thread-safe.
+ */
+public interface ByteBufAllocator {
+
+ ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;
+
+ /**
+ * Allocate a {@link ByteBuf}. If it is a direct or heap buffer
+ * depends on the actual implementation.
+ */
+ ByteBuf buffer();
+
+ /**
+ * Allocate a {@link ByteBuf} with the given initial capacity.
+ * If it is a direct or heap buffer depends on the actual implementation.
+ */
+ ByteBuf buffer(int initialCapacity);
+
+ /**
+ * Allocate a {@link ByteBuf} with the given initial capacity and the given
+ * maximal capacity. If it is a direct or heap buffer depends on the actual
+ * implementation.
+ */
+ ByteBuf buffer(int initialCapacity, int maxCapacity);
+
+ /**
+ * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
+ */
+ ByteBuf ioBuffer();
+
+ /**
+ * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
+ */
+ ByteBuf ioBuffer(int initialCapacity);
+
+ /**
+ * Allocate a {@link ByteBuf}, preferably a direct buffer which is suitable for I/O.
+ */
+ ByteBuf ioBuffer(int initialCapacity, int maxCapacity);
+
+ /**
+ * Allocate a heap {@link ByteBuf}.
+ */
+ ByteBuf heapBuffer();
+
+ /**
+ * Allocate a heap {@link ByteBuf} with the given initial capacity.
+ */
+ ByteBuf heapBuffer(int initialCapacity);
+
+ /**
+ * Allocate a heap {@link ByteBuf} with the given initial capacity and the given
+ * maximal capacity.
+ */
+ ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
+
+ /**
+ * Allocate a direct {@link ByteBuf}.
+ */
+ ByteBuf directBuffer();
+
+ /**
+ * Allocate a direct {@link ByteBuf} with the given initial capacity.
+ */
+ ByteBuf directBuffer(int initialCapacity);
+
+ /**
+ * Allocate a direct {@link ByteBuf} with the given initial capacity and the given
+ * maximal capacity.
+ */
+ ByteBuf directBuffer(int initialCapacity, int maxCapacity);
+
+ /**
+ * Allocate a {@link CompositeByteBuf}.
+ * If it is a direct or heap buffer depends on the actual implementation.
+ */
+ CompositeByteBuf compositeBuffer();
+
+ /**
+ * Allocate a {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
+ * If it is a direct or heap buffer depends on the actual implementation.
+ */
+ CompositeByteBuf compositeBuffer(int maxNumComponents);
+
+ /**
+ * Allocate a heap {@link CompositeByteBuf}.
+ */
+ CompositeByteBuf compositeHeapBuffer();
+
+ /**
+ * Allocate a heap {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
+ */
+ CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
+
+ /**
+ * Allocate a direct {@link CompositeByteBuf}.
+ */
+ CompositeByteBuf compositeDirectBuffer();
+
+ /**
+ * Allocate a direct {@link CompositeByteBuf} with the given maximum number of components that can be stored in it.
+ */
+ CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
+
+ /**
+ * Returns {@code true} if direct {@link ByteBuf}'s are pooled
+ */
+ boolean isDirectBufferPooled();
+}
diff --git a/common/src/common/net/buffer/ByteBufInputStream.java b/common/src/common/net/buffer/ByteBufInputStream.java
new file mode 100644
index 0000000..70a0925
--- /dev/null
+++ b/common/src/common/net/buffer/ByteBufInputStream.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An {@link InputStream} which reads data from a {@link ByteBuf}.
+ *
+ * A read operation against this stream will occur at the {@code readerIndex}
+ * of its underlying buffer and the {@code readerIndex} will increase during
+ * the read operation. Please note that it only reads up to the number of
+ * readable bytes determined at the moment of construction. Therefore,
+ * updating {@link ByteBuf#writerIndex()} will not affect the return
+ * value of {@link #available()}.
+ *
+ * This stream implements {@link DataInput} for your convenience.
+ * The endianness of the stream is not always big endian but depends on
+ * the endianness of the underlying buffer.
+ *
+ * @see ByteBufOutputStream
+ */
+public class ByteBufInputStream extends InputStream implements DataInput {
+
+ private final ByteBuf buffer;
+ private final int startIndex;
+ private final int endIndex;
+
+ /**
+ * Creates a new stream which reads data from the specified {@code buffer}
+ * starting at the current {@code readerIndex} and ending at the current
+ * {@code writerIndex}.
+ */
+ public ByteBufInputStream(ByteBuf buffer) {
+ this(buffer, buffer.readableBytes());
+ }
+
+ /**
+ * Creates a new stream which reads data from the specified {@code buffer}
+ * starting at the current {@code readerIndex} and ending at
+ * {@code readerIndex + length}.
+ *
+ * @throws IndexOutOfBoundsException
+ * if {@code readerIndex + length} is greater than
+ * {@code writerIndex}
+ */
+ public ByteBufInputStream(ByteBuf buffer, int length) {
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException("length: " + length);
+ }
+ if (length > buffer.readableBytes()) {
+ throw new IndexOutOfBoundsException("Too many bytes to be read - Needs "
+ + length + ", maximum is " + buffer.readableBytes());
+ }
+
+ this.buffer = buffer;
+ startIndex = buffer.readerIndex();
+ endIndex = startIndex + length;
+ buffer.markReaderIndex();
+ }
+
+ /**
+ * Returns the number of read bytes by this stream so far.
+ */
+ public int readBytes() {
+ return buffer.readerIndex() - startIndex;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return endIndex - buffer.readerIndex();
+ }
+
+ @Override
+ public void mark(int readlimit) {
+ buffer.markReaderIndex();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!buffer.isReadable()) {
+ return -1;
+ }
+ return buffer.readByte() & 0xff;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int available = available();
+ if (available == 0) {
+ return -1;
+ }
+
+ len = Math.min(available, len);
+ buffer.readBytes(b, off, len);
+ return len;
+ }
+
+ @Override
+ public void reset() throws IOException {
+ buffer.resetReaderIndex();
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n > Integer.MAX_VALUE) {
+ return skipBytes(Integer.MAX_VALUE);
+ } else {
+ return skipBytes((int) n);
+ }
+ }
+
+ @Override
+ public boolean readBoolean() throws IOException {
+ checkAvailable(1);
+ return read() != 0;
+ }
+
+ @Override
+ public byte readByte() throws IOException {
+ if (!buffer.isReadable()) {
+ throw new EOFException();
+ }
+ return buffer.readByte();
+ }
+
+ @Override
+ public char readChar() throws IOException {
+ return (char) readShort();
+ }
+
+ @Override
+ public double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ @Override
+ public float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ @Override
+ public void readFully(byte[] b) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ @Override
+ public void readFully(byte[] b, int off, int len) throws IOException {
+ checkAvailable(len);
+ buffer.readBytes(b, off, len);
+ }
+
+ @Override
+ public int readInt() throws IOException {
+ checkAvailable(4);
+ return buffer.readInt();
+ }
+
+ private final StringBuilder lineBuf = new StringBuilder();
+
+ @Override
+ public String readLine() throws IOException {
+ lineBuf.setLength(0);
+
+ loop: while (true) {
+ if (!buffer.isReadable()) {
+ return lineBuf.length() > 0 ? lineBuf.toString() : null;
+ }
+
+ int c = buffer.readUnsignedByte();
+ switch (c) {
+ case '\n':
+ break loop;
+
+ case '\r':
+ if (buffer.isReadable() && (char) buffer.getUnsignedByte(buffer.readerIndex()) == '\n') {
+ buffer.skipBytes(1);
+ }
+ break loop;
+
+ default:
+ lineBuf.append((char) c);
+ }
+ }
+
+ return lineBuf.toString();
+ }
+
+ @Override
+ public long readLong() throws IOException {
+ checkAvailable(8);
+ return buffer.readLong();
+ }
+
+ @Override
+ public short readShort() throws IOException {
+ checkAvailable(2);
+ return buffer.readShort();
+ }
+
+ @Override
+ public String readUTF() throws IOException {
+ return DataInputStream.readUTF(this);
+ }
+
+ @Override
+ public int readUnsignedByte() throws IOException {
+ return readByte() & 0xff;
+ }
+
+ @Override
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ @Override
+ public int skipBytes(int n) throws IOException {
+ int nBytes = Math.min(available(), n);
+ buffer.skipBytes(nBytes);
+ return nBytes;
+ }
+
+ private void checkAvailable(int fieldSize) throws IOException {
+ if (fieldSize < 0) {
+ throw new IndexOutOfBoundsException("fieldSize cannot be a negative number");
+ }
+ if (fieldSize > available()) {
+ throw new EOFException("fieldSize is too long! Length is " + fieldSize
+ + ", but maximum is " + available());
+ }
+ }
+}
diff --git a/common/src/common/net/buffer/ByteBufOutputStream.java b/common/src/common/net/buffer/ByteBufOutputStream.java
new file mode 100644
index 0000000..81af491
--- /dev/null
+++ b/common/src/common/net/buffer/ByteBufOutputStream.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An {@link OutputStream} which writes data to a {@link ByteBuf}.
+ *
+ * A write operation against this stream will occur at the {@code writerIndex}
+ * of its underlying buffer and the {@code writerIndex} will increase during
+ * the write operation.
+ *
+ * This stream implements {@link DataOutput} for your convenience.
+ * The endianness of the stream is not always big endian but depends on
+ * the endianness of the underlying buffer.
+ *
+ * @see ByteBufInputStream
+ */
+public class ByteBufOutputStream extends OutputStream implements DataOutput {
+
+ private final ByteBuf buffer;
+ private final int startIndex;
+ private final DataOutputStream utf8out = new DataOutputStream(this);
+
+ /**
+ * Creates a new stream which writes data to the specified {@code buffer}.
+ */
+ public ByteBufOutputStream(ByteBuf buffer) {
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ }
+ this.buffer = buffer;
+ startIndex = buffer.writerIndex();
+ }
+
+ /**
+ * Returns the number of written bytes by this stream so far.
+ */
+ public int writtenBytes() {
+ return buffer.writerIndex() - startIndex;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (len == 0) {
+ return;
+ }
+
+ buffer.writeBytes(b, off, len);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ buffer.writeBytes(b);
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ buffer.writeByte((byte) b);
+ }
+
+ @Override
+ public void writeBoolean(boolean v) throws IOException {
+ write(v? (byte) 1 : (byte) 0);
+ }
+
+ @Override
+ public void writeByte(int v) throws IOException {
+ write(v);
+ }
+
+ @Override
+ public void writeBytes(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0; i < len; i ++) {
+ write((byte) s.charAt(i));
+ }
+ }
+
+ @Override
+ public void writeChar(int v) throws IOException {
+ writeShort((short) v);
+ }
+
+ @Override
+ public void writeChars(String s) throws IOException {
+ int len = s.length();
+ for (int i = 0 ; i < len ; i ++) {
+ writeChar(s.charAt(i));
+ }
+ }
+
+ @Override
+ public void writeDouble(double v) throws IOException {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ @Override
+ public void writeFloat(float v) throws IOException {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ @Override
+ public void writeInt(int v) throws IOException {
+ buffer.writeInt(v);
+ }
+
+ @Override
+ public void writeLong(long v) throws IOException {
+ buffer.writeLong(v);
+ }
+
+ @Override
+ public void writeShort(int v) throws IOException {
+ buffer.writeShort((short) v);
+ }
+
+ @Override
+ public void writeUTF(String s) throws IOException {
+ utf8out.writeUTF(s);
+ }
+
+ /**
+ * Returns the buffer where this stream is writing data.
+ */
+ public ByteBuf buffer() {
+ return buffer;
+ }
+}
diff --git a/common/src/common/net/buffer/ByteBufProcessor.java b/common/src/common/net/buffer/ByteBufProcessor.java
new file mode 100644
index 0000000..67f4ad4
--- /dev/null
+++ b/common/src/common/net/buffer/ByteBufProcessor.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+public interface ByteBufProcessor {
+
+ /**
+ * Aborts on a {@code NUL (0x00)}.
+ */
+ ByteBufProcessor FIND_NUL = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value != 0;
+ }
+ };
+
+ /**
+ * Aborts on a non-{@code NUL (0x00)}.
+ */
+ ByteBufProcessor FIND_NON_NUL = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value == 0;
+ }
+ };
+
+ /**
+ * Aborts on a {@code CR ('\r')}.
+ */
+ ByteBufProcessor FIND_CR = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value != '\r';
+ }
+ };
+
+ /**
+ * Aborts on a non-{@code CR ('\r')}.
+ */
+ ByteBufProcessor FIND_NON_CR = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value == '\r';
+ }
+ };
+
+ /**
+ * Aborts on a {@code LF ('\n')}.
+ */
+ ByteBufProcessor FIND_LF = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value != '\n';
+ }
+ };
+
+ /**
+ * Aborts on a non-{@code LF ('\n')}.
+ */
+ ByteBufProcessor FIND_NON_LF = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value == '\n';
+ }
+ };
+
+ /**
+ * Aborts on a {@code CR ('\r')} or a {@code LF ('\n')}.
+ */
+ ByteBufProcessor FIND_CRLF = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value != '\r' && value != '\n';
+ }
+ };
+
+ /**
+ * Aborts on a byte which is neither a {@code CR ('\r')} nor a {@code LF ('\n')}.
+ */
+ ByteBufProcessor FIND_NON_CRLF = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value == '\r' || value == '\n';
+ }
+ };
+
+ /**
+ * Aborts on a linear whitespace (a ({@code ' '} or a {@code '\t'}).
+ */
+ ByteBufProcessor FIND_LINEAR_WHITESPACE = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value != ' ' && value != '\t';
+ }
+ };
+
+ /**
+ * Aborts on a byte which is not a linear whitespace (neither {@code ' '} nor {@code '\t'}).
+ */
+ ByteBufProcessor FIND_NON_LINEAR_WHITESPACE = new ByteBufProcessor() {
+ @Override
+ public boolean process(byte value) throws Exception {
+ return value == ' ' || value == '\t';
+ }
+ };
+
+ /**
+ * @return {@code true} if the processor wants to continue the loop and handle the next byte in the buffer.
+ * {@code false} if the processor wants to stop handling bytes and abort the loop.
+ */
+ boolean process(byte value) throws Exception;
+}
diff --git a/common/src/common/net/buffer/ByteBufUtil.java b/common/src/common/net/buffer/ByteBufUtil.java
new file mode 100644
index 0000000..21b042f
--- /dev/null
+++ b/common/src/common/net/buffer/ByteBufUtil.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.util.Locale;
+
+import common.net.util.CharsetUtil;
+import common.net.util.Recycler;
+import common.net.util.Recycler.Handle;
+import common.net.util.internal.PlatformDependent;
+import common.net.util.internal.SystemPropertyUtil;
+import common.net.util.internal.logging.InternalLogger;
+import common.net.util.internal.logging.InternalLoggerFactory;
+
+/**
+ * A collection of utility methods that is related with handling {@link ByteBuf}.
+ */
+public final class ByteBufUtil {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class);
+
+ private static final char[] HEXDUMP_TABLE = new char[256 * 4];
+
+ static final ByteBufAllocator DEFAULT_ALLOCATOR;
+
+ private static final int THREAD_LOCAL_BUFFER_SIZE;
+
+ static {
+ final char[] DIGITS = "0123456789abcdef".toCharArray();
+ for (int i = 0; i < 256; i ++) {
+ HEXDUMP_TABLE[ i << 1 ] = DIGITS[i >>> 4 & 0x0F];
+ HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
+ }
+
+ String allocType = SystemPropertyUtil.get("game.net.allocator.type", "unpooled").toLowerCase(Locale.US).trim();
+ ByteBufAllocator alloc;
+ if ("unpooled".equals(allocType)) {
+ alloc = UnpooledByteBufAllocator.DEFAULT;
+ logger.debug("-Dgame.net.allocator.type: {}", allocType);
+ } else if ("pooled".equals(allocType)) {
+ alloc = PooledByteBufAllocator.DEFAULT;
+ logger.debug("-Dgame.net.allocator.type: {}", allocType);
+ } else {
+ alloc = UnpooledByteBufAllocator.DEFAULT;
+ logger.debug("-Dgame.net.allocator.type: unpooled (unknown: {})", allocType);
+ }
+
+ DEFAULT_ALLOCATOR = alloc;
+
+ THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("game.net.threadLocalDirectBufferSize", 64 * 1024);
+ logger.debug("-Dgame.net.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
+ }
+
+ /**
+ * Returns a hex dump
+ * of the specified buffer's readable bytes.
+ */
+ public static String hexDump(ByteBuf buffer) {
+ return hexDump(buffer, buffer.readerIndex(), buffer.readableBytes());
+ }
+
+ /**
+ * Returns a hex dump
+ * of the specified buffer's sub-region.
+ */
+ public static String hexDump(ByteBuf buffer, int fromIndex, int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length: " + length);
+ }
+ if (length == 0) {
+ return "";
+ }
+
+ int endIndex = fromIndex + length;
+ char[] buf = new char[length << 1];
+
+ int srcIdx = fromIndex;
+ int dstIdx = 0;
+ for (; srcIdx < endIndex; srcIdx ++, dstIdx += 2) {
+ System.arraycopy(
+ HEXDUMP_TABLE, buffer.getUnsignedByte(srcIdx) << 1,
+ buf, dstIdx, 2);
+ }
+
+ return new String(buf);
+ }
+
+ /**
+ * Calculates the hash code of the specified buffer. This method is
+ * useful when implementing a new buffer type.
+ */
+ public static int hashCode(ByteBuf buffer) {
+ final int aLen = buffer.readableBytes();
+ final int intCount = aLen >>> 2;
+ final int byteCount = aLen & 3;
+
+ int hashCode = 1;
+ int arrayIndex = buffer.readerIndex();
+ if (buffer.order() == ByteOrder.BIG_ENDIAN) {
+ for (int i = intCount; i > 0; i --) {
+ hashCode = 31 * hashCode + buffer.getInt(arrayIndex);
+ arrayIndex += 4;
+ }
+ } else {
+ for (int i = intCount; i > 0; i --) {
+ hashCode = 31 * hashCode + swapInt(buffer.getInt(arrayIndex));
+ arrayIndex += 4;
+ }
+ }
+
+ for (int i = byteCount; i > 0; i --) {
+ hashCode = 31 * hashCode + buffer.getByte(arrayIndex ++);
+ }
+
+ if (hashCode == 0) {
+ hashCode = 1;
+ }
+
+ return hashCode;
+ }
+
+ /**
+ * Returns {@code true} if and only if the two specified buffers are
+ * identical to each other as described in {@code ChannelBuffer#equals(Object)}.
+ * This method is useful when implementing a new buffer type.
+ */
+ public static boolean equals(ByteBuf bufferA, ByteBuf bufferB) {
+ final int aLen = bufferA.readableBytes();
+ if (aLen != bufferB.readableBytes()) {
+ return false;
+ }
+
+ final int longCount = aLen >>> 3;
+ final int byteCount = aLen & 7;
+
+ int aIndex = bufferA.readerIndex();
+ int bIndex = bufferB.readerIndex();
+
+ if (bufferA.order() == bufferB.order()) {
+ for (int i = longCount; i > 0; i --) {
+ if (bufferA.getLong(aIndex) != bufferB.getLong(bIndex)) {
+ return false;
+ }
+ aIndex += 8;
+ bIndex += 8;
+ }
+ } else {
+ for (int i = longCount; i > 0; i --) {
+ if (bufferA.getLong(aIndex) != swapLong(bufferB.getLong(bIndex))) {
+ return false;
+ }
+ aIndex += 8;
+ bIndex += 8;
+ }
+ }
+
+ for (int i = byteCount; i > 0; i --) {
+ if (bufferA.getByte(aIndex) != bufferB.getByte(bIndex)) {
+ return false;
+ }
+ aIndex ++;
+ bIndex ++;
+ }
+
+ return true;
+ }
+
+ /**
+ * Compares the two specified buffers as described in {@link ByteBuf#compareTo(ByteBuf)}.
+ * This method is useful when implementing a new buffer type.
+ */
+ public static int compare(ByteBuf bufferA, ByteBuf bufferB) {
+ final int aLen = bufferA.readableBytes();
+ final int bLen = bufferB.readableBytes();
+ final int minLength = Math.min(aLen, bLen);
+ final int uintCount = minLength >>> 2;
+ final int byteCount = minLength & 3;
+
+ int aIndex = bufferA.readerIndex();
+ int bIndex = bufferB.readerIndex();
+
+ if (bufferA.order() == bufferB.order()) {
+ for (int i = uintCount; i > 0; i --) {
+ long va = bufferA.getUnsignedInt(aIndex);
+ long vb = bufferB.getUnsignedInt(bIndex);
+ if (va > vb) {
+ return 1;
+ }
+ if (va < vb) {
+ return -1;
+ }
+ aIndex += 4;
+ bIndex += 4;
+ }
+ } else {
+ for (int i = uintCount; i > 0; i --) {
+ long va = bufferA.getUnsignedInt(aIndex);
+ long vb = swapInt(bufferB.getInt(bIndex)) & 0xFFFFFFFFL;
+ if (va > vb) {
+ return 1;
+ }
+ if (va < vb) {
+ return -1;
+ }
+ aIndex += 4;
+ bIndex += 4;
+ }
+ }
+
+ for (int i = byteCount; i > 0; i --) {
+ short va = bufferA.getUnsignedByte(aIndex);
+ short vb = bufferB.getUnsignedByte(bIndex);
+ if (va > vb) {
+ return 1;
+ }
+ if (va < vb) {
+ return -1;
+ }
+ aIndex ++;
+ bIndex ++;
+ }
+
+ return aLen - bLen;
+ }
+
+ /**
+ * The default implementation of {@link ByteBuf#indexOf(int, int, byte)}.
+ * This method is useful when implementing a new buffer type.
+ */
+ public static int indexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
+ if (fromIndex <= toIndex) {
+ return firstIndexOf(buffer, fromIndex, toIndex, value);
+ } else {
+ return lastIndexOf(buffer, fromIndex, toIndex, value);
+ }
+ }
+
+ /**
+ * Toggles the endianness of the specified 16-bit short integer.
+ */
+ public static short swapShort(short value) {
+ return Short.reverseBytes(value);
+ }
+
+ /**
+ * Toggles the endianness of the specified 24-bit medium integer.
+ */
+ public static int swapMedium(int value) {
+ int swapped = value << 16 & 0xff0000 | value & 0xff00 | value >>> 16 & 0xff;
+ if ((swapped & 0x800000) != 0) {
+ swapped |= 0xff000000;
+ }
+ return swapped;
+ }
+
+ /**
+ * Toggles the endianness of the specified 32-bit integer.
+ */
+ public static int swapInt(int value) {
+ return Integer.reverseBytes(value);
+ }
+
+ /**
+ * Toggles the endianness of the specified 64-bit long integer.
+ */
+ public static long swapLong(long value) {
+ return Long.reverseBytes(value);
+ }
+
+ /**
+ * Read the given amount of bytes into a new {@link ByteBuf} that is allocated from the {@link ByteBufAllocator}.
+ */
+ public static ByteBuf readBytes(ByteBufAllocator alloc, ByteBuf buffer, int length) {
+ boolean release = true;
+ ByteBuf dst = alloc.buffer(length);
+ try {
+ buffer.readBytes(dst);
+ release = false;
+ return dst;
+ } finally {
+ if (release) {
+ dst.release();
+ }
+ }
+ }
+
+ private static int firstIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
+ fromIndex = Math.max(fromIndex, 0);
+ if (fromIndex >= toIndex || buffer.capacity() == 0) {
+ return -1;
+ }
+
+ for (int i = fromIndex; i < toIndex; i ++) {
+ if (buffer.getByte(i) == value) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static int lastIndexOf(ByteBuf buffer, int fromIndex, int toIndex, byte value) {
+ fromIndex = Math.min(fromIndex, buffer.capacity());
+ if (fromIndex < 0 || buffer.capacity() == 0) {
+ return -1;
+ }
+
+ for (int i = fromIndex - 1; i >= toIndex; i --) {
+ if (buffer.getByte(i) == value) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which
+ * is allocated via the {@link ByteBufAllocator}.
+ */
+ public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset) {
+ return encodeString0(alloc, false, src, charset);
+ }
+
+ static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset) {
+ final CharsetEncoder encoder = CharsetUtil.getEncoder(charset);
+ int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar());
+ boolean release = true;
+ final ByteBuf dst;
+ if (enforceHeap) {
+ dst = alloc.heapBuffer(length);
+ } else {
+ dst = alloc.buffer(length);
+ }
+ try {
+ final ByteBuffer dstBuf = dst.internalNioBuffer(0, length);
+ final int pos = dstBuf.position();
+ CoderResult cr = encoder.encode(src, dstBuf, true);
+ if (!cr.isUnderflow()) {
+ cr.throwException();
+ }
+ cr = encoder.flush(dstBuf);
+ if (!cr.isUnderflow()) {
+ cr.throwException();
+ }
+ dst.writerIndex(dst.writerIndex() + dstBuf.position() - pos);
+ release = false;
+ return dst;
+ } catch (CharacterCodingException x) {
+ throw new IllegalStateException(x);
+ } finally {
+ if (release) {
+ dst.release();
+ }
+ }
+ }
+
+ static String decodeString(ByteBuffer src, Charset charset) {
+ final CharsetDecoder decoder = CharsetUtil.getDecoder(charset);
+ final CharBuffer dst = CharBuffer.allocate(
+ (int) ((double) src.remaining() * decoder.maxCharsPerByte()));
+ try {
+ CoderResult cr = decoder.decode(src, dst, true);
+ if (!cr.isUnderflow()) {
+ cr.throwException();
+ }
+ cr = decoder.flush(dst);
+ if (!cr.isUnderflow()) {
+ cr.throwException();
+ }
+ } catch (CharacterCodingException x) {
+ throw new IllegalStateException(x);
+ }
+ return dst.flip().toString();
+ }
+
+ /**
+ * Returns a cached thread-local direct buffer, if available.
+ *
+ * @return a cached thread-local direct buffer, if available. {@code null} otherwise.
+ */
+ public static ByteBuf threadLocalDirectBuffer() {
+ if (THREAD_LOCAL_BUFFER_SIZE <= 0) {
+ return null;
+ }
+
+ if (PlatformDependent.hasUnsafe()) {
+ return ThreadLocalUnsafeDirectByteBuf.newInstance();
+ } else {
+ return ThreadLocalDirectByteBuf.newInstance();
+ }
+ }
+
+ static final class ThreadLocalUnsafeDirectByteBuf extends UnpooledUnsafeDirectByteBuf {
+
+ private static final Recycler RECYCLER =
+ new Recycler() {
+ @Override
+ protected ThreadLocalUnsafeDirectByteBuf newObject(Handle handle) {
+ return new ThreadLocalUnsafeDirectByteBuf(handle);
+ }
+ };
+
+ static ThreadLocalUnsafeDirectByteBuf newInstance() {
+ ThreadLocalUnsafeDirectByteBuf buf = RECYCLER.get();
+ buf.setRefCnt(1);
+ return buf;
+ }
+
+ private final Handle handle;
+
+ private ThreadLocalUnsafeDirectByteBuf(Handle handle) {
+ super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE);
+ this.handle = handle;
+ }
+
+ @Override
+ protected void deallocate() {
+ if (capacity() > THREAD_LOCAL_BUFFER_SIZE) {
+ super.deallocate();
+ } else {
+ clear();
+ RECYCLER.recycle(this, handle);
+ }
+ }
+ }
+
+ static final class ThreadLocalDirectByteBuf extends UnpooledDirectByteBuf {
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected ThreadLocalDirectByteBuf newObject(Handle handle) {
+ return new ThreadLocalDirectByteBuf(handle);
+ }
+ };
+
+ static ThreadLocalDirectByteBuf newInstance() {
+ ThreadLocalDirectByteBuf buf = RECYCLER.get();
+ buf.setRefCnt(1);
+ return buf;
+ }
+
+ private final Handle handle;
+
+ private ThreadLocalDirectByteBuf(Handle handle) {
+ super(UnpooledByteBufAllocator.DEFAULT, 256, Integer.MAX_VALUE);
+ this.handle = handle;
+ }
+
+ @Override
+ protected void deallocate() {
+ if (capacity() > THREAD_LOCAL_BUFFER_SIZE) {
+ super.deallocate();
+ } else {
+ clear();
+ RECYCLER.recycle(this, handle);
+ }
+ }
+ }
+
+ private ByteBufUtil() { }
+}
diff --git a/common/src/common/net/buffer/CompositeByteBuf.java b/common/src/common/net/buffer/CompositeByteBuf.java
new file mode 100644
index 0000000..1848209
--- /dev/null
+++ b/common/src/common/net/buffer/CompositeByteBuf.java
@@ -0,0 +1,1602 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import common.net.util.ResourceLeak;
+import common.net.util.internal.EmptyArrays;
+
+/**
+ * A virtual buffer which shows multiple buffers as a single merged buffer. It is recommended to use
+ * {@link ByteBufAllocator#compositeBuffer()} or {@link Unpooled#wrappedBuffer(ByteBuf...)} instead of calling the
+ * constructor explicitly.
+ */
+public class CompositeByteBuf extends AbstractReferenceCountedByteBuf {
+
+ private final ResourceLeak leak;
+ private final ByteBufAllocator alloc;
+ private final boolean direct;
+ private final List components = new ArrayList();
+ private final int maxNumComponents;
+ private static final ByteBuffer FULL_BYTEBUFFER = (ByteBuffer) ByteBuffer.allocate(1).position(1);
+
+ private boolean freed;
+
+ public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) {
+ super(Integer.MAX_VALUE);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ this.alloc = alloc;
+ this.direct = direct;
+ this.maxNumComponents = maxNumComponents;
+ leak = leakDetector.open(this);
+ }
+
+ public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents, ByteBuf... buffers) {
+ super(Integer.MAX_VALUE);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (maxNumComponents < 2) {
+ throw new IllegalArgumentException(
+ "maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
+ }
+
+ this.alloc = alloc;
+ this.direct = direct;
+ this.maxNumComponents = maxNumComponents;
+
+ addComponents0(0, buffers);
+ consolidateIfNeeded();
+ setIndex(0, capacity());
+ leak = leakDetector.open(this);
+ }
+
+ public CompositeByteBuf(
+ ByteBufAllocator alloc, boolean direct, int maxNumComponents, Iterable buffers) {
+ super(Integer.MAX_VALUE);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (maxNumComponents < 2) {
+ throw new IllegalArgumentException(
+ "maxNumComponents: " + maxNumComponents + " (expected: >= 2)");
+ }
+
+ this.alloc = alloc;
+ this.direct = direct;
+ this.maxNumComponents = maxNumComponents;
+ addComponents0(0, buffers);
+ consolidateIfNeeded();
+ setIndex(0, capacity());
+ leak = leakDetector.open(this);
+ }
+
+ /**
+ * Add the given {@link ByteBuf}.
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param buffer the {@link ByteBuf} to add
+ */
+ public CompositeByteBuf addComponent(ByteBuf buffer) {
+ addComponent0(components.size(), buffer);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ /**
+ * Add the given {@link ByteBuf}s.
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param buffers the {@link ByteBuf}s to add
+ */
+ public CompositeByteBuf addComponents(ByteBuf... buffers) {
+ addComponents0(components.size(), buffers);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ /**
+ * Add the given {@link ByteBuf}s.
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param buffers the {@link ByteBuf}s to add
+ */
+ public CompositeByteBuf addComponents(Iterable buffers) {
+ addComponents0(components.size(), buffers);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ /**
+ * Add the given {@link ByteBuf} on the specific index.
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param cIndex the index on which the {@link ByteBuf} will be added
+ * @param buffer the {@link ByteBuf} to add
+ */
+ public CompositeByteBuf addComponent(int cIndex, ByteBuf buffer) {
+ addComponent0(cIndex, buffer);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ private int addComponent0(int cIndex, ByteBuf buffer) {
+ checkComponentIndex(cIndex);
+
+ if (buffer == null) {
+ throw new NullPointerException("buffer");
+ }
+
+ int readableBytes = buffer.readableBytes();
+ if (readableBytes == 0) {
+ return cIndex;
+ }
+
+ // No need to consolidate - just add a component to the list.
+ Component c = new Component(buffer.order(ByteOrder.BIG_ENDIAN).slice());
+ if (cIndex == components.size()) {
+ components.add(c);
+ if (cIndex == 0) {
+ c.endOffset = readableBytes;
+ } else {
+ Component prev = components.get(cIndex - 1);
+ c.offset = prev.endOffset;
+ c.endOffset = c.offset + readableBytes;
+ }
+ } else {
+ components.add(cIndex, c);
+ updateComponentOffsets(cIndex);
+ }
+ return cIndex;
+ }
+
+ /**
+ * Add the given {@link ByteBuf}s on the specific index
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param cIndex the index on which the {@link ByteBuf} will be added.
+ * @param buffers the {@link ByteBuf}s to add
+ */
+ public CompositeByteBuf addComponents(int cIndex, ByteBuf... buffers) {
+ addComponents0(cIndex, buffers);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ private int addComponents0(int cIndex, ByteBuf... buffers) {
+ checkComponentIndex(cIndex);
+
+ if (buffers == null) {
+ throw new NullPointerException("buffers");
+ }
+
+ int readableBytes = 0;
+ for (ByteBuf b: buffers) {
+ if (b == null) {
+ break;
+ }
+ readableBytes += b.readableBytes();
+ }
+
+ if (readableBytes == 0) {
+ return cIndex;
+ }
+
+ // No need for consolidation
+ for (ByteBuf b: buffers) {
+ if (b == null) {
+ break;
+ }
+ if (b.isReadable()) {
+ cIndex = addComponent0(cIndex, b) + 1;
+ int size = components.size();
+ if (cIndex > size) {
+ cIndex = size;
+ }
+ } else {
+ b.release();
+ }
+ }
+ return cIndex;
+ }
+
+ /**
+ * Add the given {@link ByteBuf}s on the specific index
+ *
+ * Be aware that this method does not increase the {@code writerIndex} of the {@link CompositeByteBuf}.
+ * If you need to have it increased you need to handle it by your own.
+ *
+ * @param cIndex the index on which the {@link ByteBuf} will be added.
+ * @param buffers the {@link ByteBuf}s to add
+ */
+ public CompositeByteBuf addComponents(int cIndex, Iterable buffers) {
+ addComponents0(cIndex, buffers);
+ consolidateIfNeeded();
+ return this;
+ }
+
+ private int addComponents0(int cIndex, Iterable buffers) {
+ if (buffers == null) {
+ throw new NullPointerException("buffers");
+ }
+
+ if (buffers instanceof ByteBuf) {
+ // If buffers also implements ByteBuf (e.g. CompositeByteBuf), it has to go to addComponent(ByteBuf).
+ return addComponent0(cIndex, (ByteBuf) buffers);
+ }
+
+ if (!(buffers instanceof Collection)) {
+ List list = new ArrayList();
+ for (ByteBuf b: buffers) {
+ list.add(b);
+ }
+ buffers = list;
+ }
+
+ Collection col = (Collection) buffers;
+ return addComponents0(cIndex, col.toArray(new ByteBuf[col.size()]));
+ }
+
+ /**
+ * This should only be called as last operation from a method as this may adjust the underlying
+ * array of components and so affect the index etc.
+ */
+ private void consolidateIfNeeded() {
+ // Consolidate if the number of components will exceed the allowed maximum by the current
+ // operation.
+ final int numComponents = components.size();
+ if (numComponents > maxNumComponents) {
+ final int capacity = components.get(numComponents - 1).endOffset;
+
+ ByteBuf consolidated = allocBuffer(capacity);
+
+ // We're not using foreach to avoid creating an iterator.
+ for (int i = 0; i < numComponents; i ++) {
+ Component c = components.get(i);
+ ByteBuf b = c.buf;
+ consolidated.writeBytes(b);
+ c.freeIfNecessary();
+ }
+ Component c = new Component(consolidated);
+ c.endOffset = c.length;
+ components.clear();
+ components.add(c);
+ }
+ }
+
+ private void checkComponentIndex(int cIndex) {
+ ensureAccessible();
+ if (cIndex < 0 || cIndex > components.size()) {
+ throw new IndexOutOfBoundsException(String.format(
+ "cIndex: %d (expected: >= 0 && <= numComponents(%d))",
+ cIndex, components.size()));
+ }
+ }
+
+ private void checkComponentIndex(int cIndex, int numComponents) {
+ ensureAccessible();
+ if (cIndex < 0 || cIndex + numComponents > components.size()) {
+ throw new IndexOutOfBoundsException(String.format(
+ "cIndex: %d, numComponents: %d " +
+ "(expected: cIndex >= 0 && cIndex + numComponents <= totalNumComponents(%d))",
+ cIndex, numComponents, components.size()));
+ }
+ }
+
+ private void updateComponentOffsets(int cIndex) {
+ int size = components.size();
+ if (size <= cIndex) {
+ return;
+ }
+
+ Component c = components.get(cIndex);
+ if (cIndex == 0) {
+ c.offset = 0;
+ c.endOffset = c.length;
+ cIndex ++;
+ }
+
+ for (int i = cIndex; i < size; i ++) {
+ Component prev = components.get(i - 1);
+ Component cur = components.get(i);
+ cur.offset = prev.endOffset;
+ cur.endOffset = cur.offset + cur.length;
+ }
+ }
+
+ /**
+ * Remove the {@link ByteBuf} from the given index.
+ *
+ * @param cIndex the index on from which the {@link ByteBuf} will be remove
+ */
+ public CompositeByteBuf removeComponent(int cIndex) {
+ checkComponentIndex(cIndex);
+ components.remove(cIndex).freeIfNecessary();
+ updateComponentOffsets(cIndex);
+ return this;
+ }
+
+ /**
+ * Remove the number of {@link ByteBuf}s starting from the given index.
+ *
+ * @param cIndex the index on which the {@link ByteBuf}s will be started to removed
+ * @param numComponents the number of components to remove
+ */
+ public CompositeByteBuf removeComponents(int cIndex, int numComponents) {
+ checkComponentIndex(cIndex, numComponents);
+
+ List toRemove = components.subList(cIndex, cIndex + numComponents);
+ for (Component c: toRemove) {
+ c.freeIfNecessary();
+ }
+ toRemove.clear();
+
+ updateComponentOffsets(cIndex);
+ return this;
+ }
+
+ public Iterator iterator() {
+ ensureAccessible();
+ List list = new ArrayList(components.size());
+ for (Component c: components) {
+ list.add(c.buf);
+ }
+ return list.iterator();
+ }
+
+ /**
+ * Same with {@link #slice(int, int)} except that this method returns a list.
+ */
+ public List decompose(int offset, int length) {
+ checkIndex(offset, length);
+ if (length == 0) {
+ return Collections.emptyList();
+ }
+
+ int componentId = toComponentIndex(offset);
+ List slice = new ArrayList(components.size());
+
+ // The first component
+ Component firstC = components.get(componentId);
+ ByteBuf first = firstC.buf.duplicate();
+ first.readerIndex(offset - firstC.offset);
+
+ ByteBuf buf = first;
+ int bytesToSlice = length;
+ do {
+ int readableBytes = buf.readableBytes();
+ if (bytesToSlice <= readableBytes) {
+ // Last component
+ buf.writerIndex(buf.readerIndex() + bytesToSlice);
+ slice.add(buf);
+ break;
+ } else {
+ // Not the last component
+ slice.add(buf);
+ bytesToSlice -= readableBytes;
+ componentId ++;
+
+ // Fetch the next component.
+ buf = components.get(componentId).buf.duplicate();
+ }
+ } while (bytesToSlice > 0);
+
+ // Slice all components because only readable bytes are interesting.
+ for (int i = 0; i < slice.size(); i ++) {
+ slice.set(i, slice.get(i).slice());
+ }
+
+ return slice;
+ }
+
+ @Override
+ public boolean isDirect() {
+ int size = components.size();
+ if (size == 0) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (!components.get(i).buf.isDirect()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean hasArray() {
+ if (components.size() == 1) {
+ return components.get(0).buf.hasArray();
+ }
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ if (components.size() == 1) {
+ return components.get(0).buf.array();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int arrayOffset() {
+ if (components.size() == 1) {
+ return components.get(0).buf.arrayOffset();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ if (components.size() == 1) {
+ return components.get(0).buf.hasMemoryAddress();
+ }
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ if (components.size() == 1) {
+ return components.get(0).buf.memoryAddress();
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int capacity() {
+ if (components.isEmpty()) {
+ return 0;
+ }
+ return components.get(components.size() - 1).endOffset;
+ }
+
+ @Override
+ public CompositeByteBuf capacity(int newCapacity) {
+ ensureAccessible();
+ if (newCapacity < 0 || newCapacity > maxCapacity()) {
+ throw new IllegalArgumentException("newCapacity: " + newCapacity);
+ }
+
+ int oldCapacity = capacity();
+ if (newCapacity > oldCapacity) {
+ final int paddingLength = newCapacity - oldCapacity;
+ ByteBuf padding;
+ int nComponents = components.size();
+ if (nComponents < maxNumComponents) {
+ padding = allocBuffer(paddingLength);
+ padding.setIndex(0, paddingLength);
+ addComponent0(components.size(), padding);
+ } else {
+ padding = allocBuffer(paddingLength);
+ padding.setIndex(0, paddingLength);
+ // FIXME: No need to create a padding buffer and consolidate.
+ // Just create a big single buffer and put the current content there.
+ addComponent0(components.size(), padding);
+ consolidateIfNeeded();
+ }
+ } else if (newCapacity < oldCapacity) {
+ int bytesToTrim = oldCapacity - newCapacity;
+ for (ListIterator i = components.listIterator(components.size()); i.hasPrevious();) {
+ Component c = i.previous();
+ if (bytesToTrim >= c.length) {
+ bytesToTrim -= c.length;
+ i.remove();
+ continue;
+ }
+
+ // Replace the last component with the trimmed slice.
+ Component newC = new Component(c.buf.slice(0, c.length - bytesToTrim));
+ newC.offset = c.offset;
+ newC.endOffset = newC.offset + newC.length;
+ i.set(newC);
+ break;
+ }
+
+ if (readerIndex() > newCapacity) {
+ setIndex(newCapacity, newCapacity);
+ } else if (writerIndex() > newCapacity) {
+ writerIndex(newCapacity);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return alloc;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ /**
+ * Return the current number of {@link ByteBuf}'s that are composed in this instance
+ */
+ public int numComponents() {
+ return components.size();
+ }
+
+ /**
+ * Return the max number of {@link ByteBuf}'s that are composed in this instance
+ */
+ public int maxNumComponents() {
+ return maxNumComponents;
+ }
+
+ /**
+ * Return the index for the given offset
+ */
+ public int toComponentIndex(int offset) {
+ checkIndex(offset);
+
+ for (int low = 0, high = components.size(); low <= high;) {
+ int mid = low + high >>> 1;
+ Component c = components.get(mid);
+ if (offset >= c.endOffset) {
+ low = mid + 1;
+ } else if (offset < c.offset) {
+ high = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+
+ throw new Error("should not reach here");
+ }
+
+ public int toByteIndex(int cIndex) {
+ checkComponentIndex(cIndex);
+ return components.get(cIndex).offset;
+ }
+
+ @Override
+ public byte getByte(int index) {
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ Component c = findComponent(index);
+ return c.buf.getByte(index - c.offset);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ Component c = findComponent(index);
+ if (index + 2 <= c.endOffset) {
+ return c.buf.getShort(index - c.offset);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ return (short) ((_getByte(index) & 0xff) << 8 | _getByte(index + 1) & 0xff);
+ } else {
+ return (short) (_getByte(index) & 0xff | (_getByte(index + 1) & 0xff) << 8);
+ }
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ Component c = findComponent(index);
+ if (index + 3 <= c.endOffset) {
+ return c.buf.getUnsignedMedium(index - c.offset);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ return (_getShort(index) & 0xffff) << 8 | _getByte(index + 2) & 0xff;
+ } else {
+ return _getShort(index) & 0xFFFF | (_getByte(index + 2) & 0xFF) << 16;
+ }
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ Component c = findComponent(index);
+ if (index + 4 <= c.endOffset) {
+ return c.buf.getInt(index - c.offset);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ return (_getShort(index) & 0xffff) << 16 | _getShort(index + 2) & 0xffff;
+ } else {
+ return _getShort(index) & 0xFFFF | (_getShort(index + 2) & 0xFFFF) << 16;
+ }
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ Component c = findComponent(index);
+ if (index + 8 <= c.endOffset) {
+ return c.buf.getLong(index - c.offset);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ return (_getInt(index) & 0xffffffffL) << 32 | _getInt(index + 4) & 0xffffffffL;
+ } else {
+ return _getInt(index) & 0xFFFFFFFFL | (_getInt(index + 4) & 0xFFFFFFFFL) << 32;
+ }
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.getBytes(index - adjustment, dst, dstIndex, localLength);
+ index += localLength;
+ dstIndex += localLength;
+ length -= localLength;
+ i ++;
+ }
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, ByteBuffer dst) {
+ int limit = dst.limit();
+ int length = dst.remaining();
+
+ checkIndex(index, length);
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ try {
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ dst.limit(dst.position() + localLength);
+ s.getBytes(index - adjustment, dst);
+ index += localLength;
+ length -= localLength;
+ i ++;
+ }
+ } finally {
+ dst.limit(limit);
+ }
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.getBytes(index - adjustment, dst, dstIndex, localLength);
+ index += localLength;
+ dstIndex += localLength;
+ length -= localLength;
+ i ++;
+ }
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length)
+ throws IOException {
+ int count = nioBufferCount();
+ if (count == 1) {
+ return out.write(internalNioBuffer(index, length));
+ } else {
+ long writtenBytes = out.write(nioBuffers(index, length));
+ if (writtenBytes > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ } else {
+ return (int) writtenBytes;
+ }
+ }
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.getBytes(index - adjustment, out, localLength);
+ index += localLength;
+ length -= localLength;
+ i ++;
+ }
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf setByte(int index, int value) {
+ Component c = findComponent(index);
+ c.buf.setByte(index - c.offset, value);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ setByte(index, value);
+ }
+
+ @Override
+ public CompositeByteBuf setShort(int index, int value) {
+ return (CompositeByteBuf) super.setShort(index, value);
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ Component c = findComponent(index);
+ if (index + 2 <= c.endOffset) {
+ c.buf.setShort(index - c.offset, value);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ _setByte(index, (byte) (value >>> 8));
+ _setByte(index + 1, (byte) value);
+ } else {
+ _setByte(index, (byte) value);
+ _setByte(index + 1, (byte) (value >>> 8));
+ }
+ }
+
+ @Override
+ public CompositeByteBuf setMedium(int index, int value) {
+ return (CompositeByteBuf) super.setMedium(index, value);
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ Component c = findComponent(index);
+ if (index + 3 <= c.endOffset) {
+ c.buf.setMedium(index - c.offset, value);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ _setShort(index, (short) (value >> 8));
+ _setByte(index + 2, (byte) value);
+ } else {
+ _setShort(index, (short) value);
+ _setByte(index + 2, (byte) (value >>> 16));
+ }
+ }
+
+ @Override
+ public CompositeByteBuf setInt(int index, int value) {
+ return (CompositeByteBuf) super.setInt(index, value);
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ Component c = findComponent(index);
+ if (index + 4 <= c.endOffset) {
+ c.buf.setInt(index - c.offset, value);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ _setShort(index, (short) (value >>> 16));
+ _setShort(index + 2, (short) value);
+ } else {
+ _setShort(index, (short) value);
+ _setShort(index + 2, (short) (value >>> 16));
+ }
+ }
+
+ @Override
+ public CompositeByteBuf setLong(int index, long value) {
+ return (CompositeByteBuf) super.setLong(index, value);
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ Component c = findComponent(index);
+ if (index + 8 <= c.endOffset) {
+ c.buf.setLong(index - c.offset, value);
+ } else if (order() == ByteOrder.BIG_ENDIAN) {
+ _setInt(index, (int) (value >>> 32));
+ _setInt(index + 4, (int) value);
+ } else {
+ _setInt(index, (int) value);
+ _setInt(index + 4, (int) (value >>> 32));
+ }
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.length);
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.setBytes(index - adjustment, src, srcIndex, localLength);
+ index += localLength;
+ srcIndex += localLength;
+ length -= localLength;
+ i ++;
+ }
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, ByteBuffer src) {
+ int limit = src.limit();
+ int length = src.remaining();
+
+ checkIndex(index, length);
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ try {
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ src.limit(src.position() + localLength);
+ s.setBytes(index - adjustment, src);
+ index += localLength;
+ length -= localLength;
+ i ++;
+ }
+ } finally {
+ src.limit(limit);
+ }
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.capacity());
+ if (length == 0) {
+ return this;
+ }
+
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.setBytes(index - adjustment, src, srcIndex, localLength);
+ index += localLength;
+ srcIndex += localLength;
+ length -= localLength;
+ i ++;
+ }
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return in.read(EmptyArrays.EMPTY_BYTES);
+ }
+
+ int i = toComponentIndex(index);
+ int readBytes = 0;
+
+ do {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ int localReadBytes = s.setBytes(index - adjustment, in, localLength);
+ if (localReadBytes < 0) {
+ if (readBytes == 0) {
+ return -1;
+ } else {
+ break;
+ }
+ }
+
+ if (localReadBytes == localLength) {
+ index += localLength;
+ length -= localLength;
+ readBytes += localLength;
+ i ++;
+ } else {
+ index += localReadBytes;
+ length -= localReadBytes;
+ readBytes += localReadBytes;
+ }
+ } while (length > 0);
+
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return in.read(FULL_BYTEBUFFER);
+ }
+
+ int i = toComponentIndex(index);
+ int readBytes = 0;
+ do {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ int localReadBytes = s.setBytes(index - adjustment, in, localLength);
+
+ if (localReadBytes == 0) {
+ break;
+ }
+
+ if (localReadBytes < 0) {
+ if (readBytes == 0) {
+ return -1;
+ } else {
+ break;
+ }
+ }
+
+ if (localReadBytes == localLength) {
+ index += localLength;
+ length -= localLength;
+ readBytes += localLength;
+ i ++;
+ } else {
+ index += localReadBytes;
+ length -= localReadBytes;
+ readBytes += localReadBytes;
+ }
+ } while (length > 0);
+
+ return readBytes;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf dst = Unpooled.buffer(length);
+ if (length != 0) {
+ copyTo(index, length, toComponentIndex(index), dst);
+ }
+ return dst;
+ }
+
+ private void copyTo(int index, int length, int componentId, ByteBuf dst) {
+ int dstIndex = 0;
+ int i = componentId;
+
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ s.getBytes(index - adjustment, dst, dstIndex, localLength);
+ index += localLength;
+ dstIndex += localLength;
+ length -= localLength;
+ i ++;
+ }
+
+ dst.writerIndex(dst.capacity());
+ }
+
+ /**
+ * Return the {@link ByteBuf} on the specified index
+ *
+ * @param cIndex the index for which the {@link ByteBuf} should be returned
+ * @return buf the {@link ByteBuf} on the specified index
+ */
+ public ByteBuf component(int cIndex) {
+ return internalComponent(cIndex).duplicate();
+ }
+
+ /**
+ * Return the {@link ByteBuf} on the specified index
+ *
+ * @param offset the offset for which the {@link ByteBuf} should be returned
+ * @return the {@link ByteBuf} on the specified index
+ */
+ public ByteBuf componentAtOffset(int offset) {
+ return internalComponentAtOffset(offset).duplicate();
+ }
+
+ /**
+ * Return the internal {@link ByteBuf} on the specified index. Note that updating the indexes of the returned
+ * buffer will lead to an undefined behavior of this buffer.
+ *
+ * @param cIndex the index for which the {@link ByteBuf} should be returned
+ */
+ public ByteBuf internalComponent(int cIndex) {
+ checkComponentIndex(cIndex);
+ return components.get(cIndex).buf;
+ }
+
+ /**
+ * Return the internal {@link ByteBuf} on the specified offset. Note that updating the indexes of the returned
+ * buffer will lead to an undefined behavior of this buffer.
+ *
+ * @param offset the offset for which the {@link ByteBuf} should be returned
+ */
+ public ByteBuf internalComponentAtOffset(int offset) {
+ return findComponent(offset).buf;
+ }
+
+ private Component findComponent(int offset) {
+ checkIndex(offset);
+
+ for (int low = 0, high = components.size(); low <= high;) {
+ int mid = low + high >>> 1;
+ Component c = components.get(mid);
+ if (offset >= c.endOffset) {
+ low = mid + 1;
+ } else if (offset < c.offset) {
+ high = mid - 1;
+ } else {
+ return c;
+ }
+ }
+
+ throw new Error("should not reach here");
+ }
+
+ @Override
+ public int nioBufferCount() {
+ if (components.size() == 1) {
+ return components.get(0).buf.nioBufferCount();
+ } else {
+ int count = 0;
+ int componentsCount = components.size();
+ for (int i = 0; i < componentsCount; i++) {
+ Component c = components.get(i);
+ count += c.buf.nioBufferCount();
+ }
+ return count;
+ }
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ if (components.size() == 1) {
+ return components.get(0).buf.internalNioBuffer(index, length);
+ }
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ if (components.size() == 1) {
+ ByteBuf buf = components.get(0).buf;
+ if (buf.nioBufferCount() == 1) {
+ return components.get(0).buf.nioBuffer(index, length);
+ }
+ }
+ ByteBuffer merged = ByteBuffer.allocate(length).order(order());
+ ByteBuffer[] buffers = nioBuffers(index, length);
+
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < buffers.length; i++) {
+ merged.put(buffers[i]);
+ }
+
+ merged.flip();
+ return merged;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ checkIndex(index, length);
+ if (length == 0) {
+ return EmptyArrays.EMPTY_BYTE_BUFFERS;
+ }
+
+ List buffers = new ArrayList(components.size());
+ int i = toComponentIndex(index);
+ while (length > 0) {
+ Component c = components.get(i);
+ ByteBuf s = c.buf;
+ int adjustment = c.offset;
+ int localLength = Math.min(length, s.capacity() - (index - adjustment));
+ switch (s.nioBufferCount()) {
+ case 0:
+ throw new UnsupportedOperationException();
+ case 1:
+ buffers.add(s.nioBuffer(index - adjustment, localLength));
+ break;
+ default:
+ Collections.addAll(buffers, s.nioBuffers(index - adjustment, localLength));
+ }
+
+ index += localLength;
+ length -= localLength;
+ i ++;
+ }
+
+ return buffers.toArray(new ByteBuffer[buffers.size()]);
+ }
+
+ /**
+ * Consolidate the composed {@link ByteBuf}s
+ */
+ public CompositeByteBuf consolidate() {
+ ensureAccessible();
+ final int numComponents = numComponents();
+ if (numComponents <= 1) {
+ return this;
+ }
+
+ final Component last = components.get(numComponents - 1);
+ final int capacity = last.endOffset;
+ final ByteBuf consolidated = allocBuffer(capacity);
+
+ for (int i = 0; i < numComponents; i ++) {
+ Component c = components.get(i);
+ ByteBuf b = c.buf;
+ consolidated.writeBytes(b);
+ c.freeIfNecessary();
+ }
+
+ components.clear();
+ components.add(new Component(consolidated));
+ updateComponentOffsets(0);
+ return this;
+ }
+
+ /**
+ * Consolidate the composed {@link ByteBuf}s
+ *
+ * @param cIndex the index on which to start to compose
+ * @param numComponents the number of components to compose
+ */
+ public CompositeByteBuf consolidate(int cIndex, int numComponents) {
+ checkComponentIndex(cIndex, numComponents);
+ if (numComponents <= 1) {
+ return this;
+ }
+
+ final int endCIndex = cIndex + numComponents;
+ final Component last = components.get(endCIndex - 1);
+ final int capacity = last.endOffset - components.get(cIndex).offset;
+ final ByteBuf consolidated = allocBuffer(capacity);
+
+ for (int i = cIndex; i < endCIndex; i ++) {
+ Component c = components.get(i);
+ ByteBuf b = c.buf;
+ consolidated.writeBytes(b);
+ c.freeIfNecessary();
+ }
+
+ components.subList(cIndex + 1, endCIndex).clear();
+ components.set(cIndex, new Component(consolidated));
+ updateComponentOffsets(cIndex);
+ return this;
+ }
+
+ /**
+ * Discard all {@link ByteBuf}s which are read.
+ */
+ public CompositeByteBuf discardReadComponents() {
+ ensureAccessible();
+ final int readerIndex = readerIndex();
+ if (readerIndex == 0) {
+ return this;
+ }
+
+ // Discard everything if (readerIndex = writerIndex = capacity).
+ int writerIndex = writerIndex();
+ if (readerIndex == writerIndex && writerIndex == capacity()) {
+ for (Component c: components) {
+ c.freeIfNecessary();
+ }
+ components.clear();
+ setIndex(0, 0);
+ adjustMarkers(readerIndex);
+ return this;
+ }
+
+ // Remove read components.
+ int firstComponentId = toComponentIndex(readerIndex);
+ for (int i = 0; i < firstComponentId; i ++) {
+ components.get(i).freeIfNecessary();
+ }
+ components.subList(0, firstComponentId).clear();
+
+ // Update indexes and markers.
+ Component first = components.get(0);
+ int offset = first.offset;
+ updateComponentOffsets(0);
+ setIndex(readerIndex - offset, writerIndex - offset);
+ adjustMarkers(offset);
+ return this;
+ }
+
+ @Override
+ public CompositeByteBuf discardReadBytes() {
+ ensureAccessible();
+ final int readerIndex = readerIndex();
+ if (readerIndex == 0) {
+ return this;
+ }
+
+ // Discard everything if (readerIndex = writerIndex = capacity).
+ int writerIndex = writerIndex();
+ if (readerIndex == writerIndex && writerIndex == capacity()) {
+ for (Component c: components) {
+ c.freeIfNecessary();
+ }
+ components.clear();
+ setIndex(0, 0);
+ adjustMarkers(readerIndex);
+ return this;
+ }
+
+ // Remove read components.
+ int firstComponentId = toComponentIndex(readerIndex);
+ for (int i = 0; i < firstComponentId; i ++) {
+ components.get(i).freeIfNecessary();
+ }
+ components.subList(0, firstComponentId).clear();
+
+ // Remove or replace the first readable component with a new slice.
+ Component c = components.get(0);
+ int adjustment = readerIndex - c.offset;
+ if (adjustment == c.length) {
+ // new slice would be empty, so remove instead
+ components.remove(0);
+ } else {
+ Component newC = new Component(c.buf.slice(adjustment, c.length - adjustment));
+ components.set(0, newC);
+ }
+
+ // Update indexes and markers.
+ updateComponentOffsets(0);
+ setIndex(0, writerIndex - readerIndex);
+ adjustMarkers(readerIndex);
+ return this;
+ }
+
+ private ByteBuf allocBuffer(int capacity) {
+ if (direct) {
+ return alloc().directBuffer(capacity);
+ }
+ return alloc().heapBuffer(capacity);
+ }
+
+ @Override
+ public String toString() {
+ String result = super.toString();
+ result = result.substring(0, result.length() - 1);
+ return result + ", components=" + components.size() + ')';
+ }
+
+ private static final class Component {
+ final ByteBuf buf;
+ final int length;
+ int offset;
+ int endOffset;
+
+ Component(ByteBuf buf) {
+ this.buf = buf;
+ length = buf.readableBytes();
+ }
+
+ void freeIfNecessary() {
+ // Unwrap so that we can free slices, too.
+ buf.release(); // We should not get a NPE here. If so, it must be a bug.
+ }
+ }
+
+ @Override
+ public CompositeByteBuf readerIndex(int readerIndex) {
+ return (CompositeByteBuf) super.readerIndex(readerIndex);
+ }
+
+ @Override
+ public CompositeByteBuf writerIndex(int writerIndex) {
+ return (CompositeByteBuf) super.writerIndex(writerIndex);
+ }
+
+ @Override
+ public CompositeByteBuf setIndex(int readerIndex, int writerIndex) {
+ return (CompositeByteBuf) super.setIndex(readerIndex, writerIndex);
+ }
+
+ @Override
+ public CompositeByteBuf clear() {
+ return (CompositeByteBuf) super.clear();
+ }
+
+ @Override
+ public CompositeByteBuf markReaderIndex() {
+ return (CompositeByteBuf) super.markReaderIndex();
+ }
+
+ @Override
+ public CompositeByteBuf resetReaderIndex() {
+ return (CompositeByteBuf) super.resetReaderIndex();
+ }
+
+ @Override
+ public CompositeByteBuf markWriterIndex() {
+ return (CompositeByteBuf) super.markWriterIndex();
+ }
+
+ @Override
+ public CompositeByteBuf resetWriterIndex() {
+ return (CompositeByteBuf) super.resetWriterIndex();
+ }
+
+ @Override
+ public CompositeByteBuf ensureWritable(int minWritableBytes) {
+ return (CompositeByteBuf) super.ensureWritable(minWritableBytes);
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, ByteBuf dst) {
+ return (CompositeByteBuf) super.getBytes(index, dst);
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, ByteBuf dst, int length) {
+ return (CompositeByteBuf) super.getBytes(index, dst, length);
+ }
+
+ @Override
+ public CompositeByteBuf getBytes(int index, byte[] dst) {
+ return (CompositeByteBuf) super.getBytes(index, dst);
+ }
+
+ @Override
+ public CompositeByteBuf setBoolean(int index, boolean value) {
+ return (CompositeByteBuf) super.setBoolean(index, value);
+ }
+
+ @Override
+ public CompositeByteBuf setChar(int index, int value) {
+ return (CompositeByteBuf) super.setChar(index, value);
+ }
+
+ @Override
+ public CompositeByteBuf setFloat(int index, float value) {
+ return (CompositeByteBuf) super.setFloat(index, value);
+ }
+
+ @Override
+ public CompositeByteBuf setDouble(int index, double value) {
+ return (CompositeByteBuf) super.setDouble(index, value);
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, ByteBuf src) {
+ return (CompositeByteBuf) super.setBytes(index, src);
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, ByteBuf src, int length) {
+ return (CompositeByteBuf) super.setBytes(index, src, length);
+ }
+
+ @Override
+ public CompositeByteBuf setBytes(int index, byte[] src) {
+ return (CompositeByteBuf) super.setBytes(index, src);
+ }
+
+ @Override
+ public CompositeByteBuf setZero(int index, int length) {
+ return (CompositeByteBuf) super.setZero(index, length);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(ByteBuf dst) {
+ return (CompositeByteBuf) super.readBytes(dst);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(ByteBuf dst, int length) {
+ return (CompositeByteBuf) super.readBytes(dst, length);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
+ return (CompositeByteBuf) super.readBytes(dst, dstIndex, length);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(byte[] dst) {
+ return (CompositeByteBuf) super.readBytes(dst);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ return (CompositeByteBuf) super.readBytes(dst, dstIndex, length);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(ByteBuffer dst) {
+ return (CompositeByteBuf) super.readBytes(dst);
+ }
+
+ @Override
+ public CompositeByteBuf readBytes(OutputStream out, int length) throws IOException {
+ return (CompositeByteBuf) super.readBytes(out, length);
+ }
+
+ @Override
+ public CompositeByteBuf skipBytes(int length) {
+ return (CompositeByteBuf) super.skipBytes(length);
+ }
+
+ @Override
+ public CompositeByteBuf writeBoolean(boolean value) {
+ return (CompositeByteBuf) super.writeBoolean(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeByte(int value) {
+ return (CompositeByteBuf) super.writeByte(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeShort(int value) {
+ return (CompositeByteBuf) super.writeShort(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeMedium(int value) {
+ return (CompositeByteBuf) super.writeMedium(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeInt(int value) {
+ return (CompositeByteBuf) super.writeInt(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeLong(long value) {
+ return (CompositeByteBuf) super.writeLong(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeChar(int value) {
+ return (CompositeByteBuf) super.writeChar(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeFloat(float value) {
+ return (CompositeByteBuf) super.writeFloat(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeDouble(double value) {
+ return (CompositeByteBuf) super.writeDouble(value);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(ByteBuf src) {
+ return (CompositeByteBuf) super.writeBytes(src);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(ByteBuf src, int length) {
+ return (CompositeByteBuf) super.writeBytes(src, length);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
+ return (CompositeByteBuf) super.writeBytes(src, srcIndex, length);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(byte[] src) {
+ return (CompositeByteBuf) super.writeBytes(src);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(byte[] src, int srcIndex, int length) {
+ return (CompositeByteBuf) super.writeBytes(src, srcIndex, length);
+ }
+
+ @Override
+ public CompositeByteBuf writeBytes(ByteBuffer src) {
+ return (CompositeByteBuf) super.writeBytes(src);
+ }
+
+ @Override
+ public CompositeByteBuf writeZero(int length) {
+ return (CompositeByteBuf) super.writeZero(length);
+ }
+
+ @Override
+ public CompositeByteBuf retain(int increment) {
+ return (CompositeByteBuf) super.retain(increment);
+ }
+
+ @Override
+ public CompositeByteBuf retain() {
+ return (CompositeByteBuf) super.retain();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers() {
+ return nioBuffers(readerIndex(), readableBytes());
+ }
+
+ @Override
+ public CompositeByteBuf discardSomeReadBytes() {
+ return discardReadComponents();
+ }
+
+ @Override
+ protected void deallocate() {
+ if (freed) {
+ return;
+ }
+
+ freed = true;
+ int size = components.size();
+ // We're not using foreach to avoid creating an iterator.
+ // see https://github.com/netty/netty/issues/2642
+ for (int i = 0; i < size; i++) {
+ components.get(i).freeIfNecessary();
+ }
+
+ if (leak != null) {
+ leak.close();
+ }
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+}
diff --git a/common/src/common/net/buffer/DuplicatedByteBuf.java b/common/src/common/net/buffer/DuplicatedByteBuf.java
new file mode 100644
index 0000000..145631c
--- /dev/null
+++ b/common/src/common/net/buffer/DuplicatedByteBuf.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+
+/**
+ * A derived buffer which simply forwards all data access requests to its
+ * parent. It is recommended to use {@link ByteBuf#duplicate()} instead
+ * of calling the constructor explicitly.
+ */
+public class DuplicatedByteBuf extends AbstractDerivedByteBuf {
+
+ private final ByteBuf buffer;
+
+ public DuplicatedByteBuf(ByteBuf buffer) {
+ super(buffer.maxCapacity());
+
+ if (buffer instanceof DuplicatedByteBuf) {
+ this.buffer = ((DuplicatedByteBuf) buffer).buffer;
+ } else {
+ this.buffer = buffer;
+ }
+
+ setIndex(buffer.readerIndex(), buffer.writerIndex());
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return buffer;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return buffer.alloc();
+ }
+
+ @Override
+ public ByteOrder order() {
+ return buffer.order();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buffer.isDirect();
+ }
+
+ @Override
+ public int capacity() {
+ return buffer.capacity();
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ buffer.capacity(newCapacity);
+ return this;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return buffer.hasArray();
+ }
+
+ @Override
+ public byte[] array() {
+ return buffer.array();
+ }
+
+ @Override
+ public int arrayOffset() {
+ return buffer.arrayOffset();
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return buffer.hasMemoryAddress();
+ }
+
+ @Override
+ public long memoryAddress() {
+ return buffer.memoryAddress();
+ }
+
+ @Override
+ public byte getByte(int index) {
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return buffer.getByte(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ return _getShort(index);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return buffer.getShort(index);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ return _getUnsignedMedium(index);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return buffer.getUnsignedMedium(index);
+ }
+
+ @Override
+ public int getInt(int index) {
+ return _getInt(index);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return buffer.getInt(index);
+ }
+
+ @Override
+ public long getLong(int index) {
+ return _getLong(index);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return buffer.getLong(index);
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ return buffer.copy(index, length);
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return buffer.slice(index, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ buffer.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ buffer.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ buffer.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ _setByte(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ buffer.setByte(index, value);
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ _setShort(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ buffer.setShort(index, value);
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ _setMedium(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ buffer.setMedium(index, value);
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ _setInt(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ buffer.setInt(index, value);
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ _setLong(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ buffer.setLong(index, value);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ buffer.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ buffer.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ buffer.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length)
+ throws IOException {
+ buffer.getBytes(index, out, length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length)
+ throws IOException {
+ return buffer.getBytes(index, out, length);
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length)
+ throws IOException {
+ return buffer.setBytes(index, in, length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length)
+ throws IOException {
+ return buffer.setBytes(index, in, length);
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return buffer.nioBufferCount();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return buffer.nioBuffers(index, length);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ return nioBuffer(index, length);
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ return buffer.forEachByte(index, length, processor);
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ return buffer.forEachByteDesc(index, length, processor);
+ }
+}
+
diff --git a/common/src/common/net/buffer/EmptyByteBuf.java b/common/src/common/net/buffer/EmptyByteBuf.java
new file mode 100644
index 0000000..1f1cb49
--- /dev/null
+++ b/common/src/common/net/buffer/EmptyByteBuf.java
@@ -0,0 +1,870 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ReadOnlyBufferException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+import java.nio.charset.Charset;
+
+import common.net.util.internal.EmptyArrays;
+import common.net.util.internal.PlatformDependent;
+import common.net.util.internal.StringUtil;
+
+/**
+ * An empty {@link ByteBuf} whose capacity and maximum capacity are all {@code 0}.
+ */
+public final class EmptyByteBuf extends ByteBuf {
+
+ private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocateDirect(0);
+ private static final long EMPTY_BYTE_BUFFER_ADDRESS;
+
+ static {
+ long emptyByteBufferAddress = 0;
+ try {
+ if (PlatformDependent.hasUnsafe()) {
+ emptyByteBufferAddress = PlatformDependent.directBufferAddress(EMPTY_BYTE_BUFFER);
+ }
+ } catch (Throwable t) {
+ // Ignore
+ }
+ EMPTY_BYTE_BUFFER_ADDRESS = emptyByteBufferAddress;
+ }
+
+ private final ByteBufAllocator alloc;
+ private final ByteOrder order;
+ private final String str;
+ private EmptyByteBuf swapped;
+
+ public EmptyByteBuf(ByteBufAllocator alloc) {
+ this(alloc, ByteOrder.BIG_ENDIAN);
+ }
+
+ private EmptyByteBuf(ByteBufAllocator alloc, ByteOrder order) {
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+
+ this.alloc = alloc;
+ this.order = order;
+ str = StringUtil.simpleClassName(this) + (order == ByteOrder.BIG_ENDIAN? "BE" : "LE");
+ }
+
+ @Override
+ public int capacity() {
+ return 0;
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return alloc;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return order;
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+
+ @Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ public int maxCapacity() {
+ return 0;
+ }
+
+ @Override
+ public ByteBuf order(ByteOrder endianness) {
+ if (endianness == null) {
+ throw new NullPointerException("endianness");
+ }
+ if (endianness == order()) {
+ return this;
+ }
+
+ EmptyByteBuf swapped = this.swapped;
+ if (swapped != null) {
+ return swapped;
+ }
+
+ this.swapped = swapped = new EmptyByteBuf(alloc(), endianness);
+ return swapped;
+ }
+
+ @Override
+ public int readerIndex() {
+ return 0;
+ }
+
+ @Override
+ public ByteBuf readerIndex(int readerIndex) {
+ return checkIndex(readerIndex);
+ }
+
+ @Override
+ public int writerIndex() {
+ return 0;
+ }
+
+ @Override
+ public ByteBuf writerIndex(int writerIndex) {
+ return checkIndex(writerIndex);
+ }
+
+ @Override
+ public ByteBuf setIndex(int readerIndex, int writerIndex) {
+ checkIndex(readerIndex);
+ checkIndex(writerIndex);
+ return this;
+ }
+
+ @Override
+ public int readableBytes() {
+ return 0;
+ }
+
+ @Override
+ public int writableBytes() {
+ return 0;
+ }
+
+ @Override
+ public int maxWritableBytes() {
+ return 0;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWritable() {
+ return false;
+ }
+
+ @Override
+ public ByteBuf clear() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf markReaderIndex() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetReaderIndex() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf markWriterIndex() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetWriterIndex() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardReadBytes() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardSomeReadBytes() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf ensureWritable(int minWritableBytes) {
+ if (minWritableBytes < 0) {
+ throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)");
+ }
+ if (minWritableBytes != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ return this;
+ }
+
+ @Override
+ public int ensureWritable(int minWritableBytes, boolean force) {
+ if (minWritableBytes < 0) {
+ throw new IllegalArgumentException("minWritableBytes: " + minWritableBytes + " (expected: >= 0)");
+ }
+
+ if (minWritableBytes == 0) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ @Override
+ public boolean getBoolean(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public byte getByte(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public short getUnsignedByte(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public short getShort(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int getUnsignedShort(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int getMedium(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int getInt(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public long getUnsignedInt(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public long getLong(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public char getChar(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public float getFloat(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public double getDouble(int index) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst) {
+ return checkIndex(index, dst.writableBytes());
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst) {
+ return checkIndex(index, dst.length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ return checkIndex(index, dst.remaining());
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) {
+ checkIndex(index, length);
+ return 0;
+ }
+
+ @Override
+ public ByteBuf setBoolean(int index, boolean value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setChar(int index, int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setFloat(int index, float value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setDouble(int index, double value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src) {
+ return checkIndex(index, src.length);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ return checkIndex(index, src.remaining());
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) {
+ checkIndex(index, length);
+ return 0;
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) {
+ checkIndex(index, length);
+ return 0;
+ }
+
+ @Override
+ public ByteBuf setZero(int index, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public boolean readBoolean() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public byte readByte() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public short readUnsignedByte() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public short readShort() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int readMedium() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int readUnsignedMedium() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public int readInt() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public long readUnsignedInt() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public long readLong() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public char readChar() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public float readFloat() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public double readDouble() {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf readBytes(int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf readSlice(int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst) {
+ return checkLength(dst.writableBytes());
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst) {
+ return checkLength(dst.length);
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ return checkLength(dst.remaining());
+ }
+
+ @Override
+ public ByteBuf readBytes(OutputStream out, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) {
+ checkLength(length);
+ return 0;
+ }
+
+ @Override
+ public ByteBuf skipBytes(int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf writeBoolean(boolean value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeByte(int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeShort(int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeMedium(int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeInt(int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeLong(long value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeChar(int value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeFloat(float value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeDouble(double value) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src) {
+ throw new IndexOutOfBoundsException();
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src) {
+ return checkLength(src.length);
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuffer src) {
+ return checkLength(src.remaining());
+ }
+
+ @Override
+ public int writeBytes(InputStream in, int length) {
+ checkLength(length);
+ return 0;
+ }
+
+ @Override
+ public int writeBytes(ScatteringByteChannel in, int length) {
+ checkLength(length);
+ return 0;
+ }
+
+ @Override
+ public ByteBuf writeZero(int length) {
+ return checkLength(length);
+ }
+
+ @Override
+ public int indexOf(int fromIndex, int toIndex, byte value) {
+ checkIndex(fromIndex);
+ checkIndex(toIndex);
+ return -1;
+ }
+
+ @Override
+ public int bytesBefore(byte value) {
+ return -1;
+ }
+
+ @Override
+ public int bytesBefore(int length, byte value) {
+ checkLength(length);
+ return -1;
+ }
+
+ @Override
+ public int bytesBefore(int index, int length, byte value) {
+ checkIndex(index, length);
+ return -1;
+ }
+
+ @Override
+ public int forEachByte(ByteBufProcessor processor) {
+ return -1;
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ checkIndex(index, length);
+ return -1;
+ }
+
+ @Override
+ public int forEachByteDesc(ByteBufProcessor processor) {
+ return -1;
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ checkIndex(index, length);
+ return -1;
+ }
+
+ @Override
+ public ByteBuf copy() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf slice() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return checkIndex(index, length);
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return this;
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer() {
+ return EMPTY_BYTE_BUFFER;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return nioBuffer();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers() {
+ return new ByteBuffer[] { EMPTY_BYTE_BUFFER };
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ checkIndex(index, length);
+ return nioBuffers();
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ return EMPTY_BYTE_BUFFER;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return true;
+ }
+
+ @Override
+ public byte[] array() {
+ return EmptyArrays.EMPTY_BYTES;
+ }
+
+ @Override
+ public int arrayOffset() {
+ return 0;
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return EMPTY_BYTE_BUFFER_ADDRESS != 0;
+ }
+
+ @Override
+ public long memoryAddress() {
+ if (hasMemoryAddress()) {
+ return EMPTY_BYTE_BUFFER_ADDRESS;
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return "";
+ }
+
+ @Override
+ public String toString(int index, int length, Charset charset) {
+ checkIndex(index, length);
+ return toString(charset);
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ByteBuf && !((ByteBuf) obj).isReadable();
+ }
+
+ @Override
+ public int compareTo(ByteBuf buffer) {
+ return buffer.isReadable()? -1 : 0;
+ }
+
+ @Override
+ public String toString() {
+ return str;
+ }
+
+ @Override
+ public boolean isReadable(int size) {
+ return false;
+ }
+
+ @Override
+ public boolean isWritable(int size) {
+ return false;
+ }
+
+ @Override
+ public int refCnt() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuf retain() {
+ return this;
+ }
+
+ @Override
+ public ByteBuf retain(int increment) {
+ return this;
+ }
+
+ @Override
+ public boolean release() {
+ return false;
+ }
+
+ @Override
+ public boolean release(int decrement) {
+ return false;
+ }
+
+ private ByteBuf checkIndex(int index) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ return this;
+ }
+
+ private ByteBuf checkIndex(int index, int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length: " + length);
+ }
+ if (index != 0 || length != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ return this;
+ }
+
+ private ByteBuf checkLength(int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException("length: " + length + " (expected: >= 0)");
+ }
+ if (length != 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ return this;
+ }
+}
diff --git a/common/src/common/net/buffer/PoolArena.java b/common/src/common/net/buffer/PoolArena.java
new file mode 100644
index 0000000..ac2c5f6
--- /dev/null
+++ b/common/src/common/net/buffer/PoolArena.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.nio.ByteBuffer;
+
+import common.net.util.internal.PlatformDependent;
+import common.net.util.internal.StringUtil;
+
+abstract class PoolArena {
+
+ static final int numTinySubpagePools = 512 >>> 4;
+
+ final PooledByteBufAllocator parent;
+
+ private final int maxOrder;
+ final int pageSize;
+ final int pageShifts;
+ final int chunkSize;
+ final int subpageOverflowMask;
+ final int numSmallSubpagePools;
+ private final PoolSubpage[] tinySubpagePools;
+ private final PoolSubpage[] smallSubpagePools;
+
+ private final PoolChunkList q050;
+ private final PoolChunkList q025;
+ private final PoolChunkList q000;
+ private final PoolChunkList qInit;
+ private final PoolChunkList q075;
+ private final PoolChunkList q100;
+
+ // TODO: Test if adding padding helps under contention
+ //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
+
+ protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ this.parent = parent;
+ this.pageSize = pageSize;
+ this.maxOrder = maxOrder;
+ this.pageShifts = pageShifts;
+ this.chunkSize = chunkSize;
+ subpageOverflowMask = ~(pageSize - 1);
+ tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
+ for (int i = 0; i < tinySubpagePools.length; i ++) {
+ tinySubpagePools[i] = newSubpagePoolHead(pageSize);
+ }
+
+ numSmallSubpagePools = pageShifts - 9;
+ smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
+ for (int i = 0; i < smallSubpagePools.length; i ++) {
+ smallSubpagePools[i] = newSubpagePoolHead(pageSize);
+ }
+
+ q100 = new PoolChunkList(this, null, 100, Integer.MAX_VALUE);
+ q075 = new PoolChunkList(this, q100, 75, 100);
+ q050 = new PoolChunkList(this, q075, 50, 100);
+ q025 = new PoolChunkList(this, q050, 25, 75);
+ q000 = new PoolChunkList(this, q025, 1, 50);
+ qInit = new PoolChunkList(this, q000, Integer.MIN_VALUE, 25);
+
+ q100.prevList = q075;
+ q075.prevList = q050;
+ q050.prevList = q025;
+ q025.prevList = q000;
+ q000.prevList = null;
+ qInit.prevList = qInit;
+ }
+
+ private PoolSubpage newSubpagePoolHead(int pageSize) {
+ PoolSubpage head = new PoolSubpage(pageSize);
+ head.prev = head;
+ head.next = head;
+ return head;
+ }
+
+
+ private PoolSubpage[] newSubpagePoolArray(int size) {
+ return new PoolSubpage[size];
+ }
+
+ abstract boolean isDirect();
+
+ PooledByteBuf allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
+ PooledByteBuf buf = newByteBuf(maxCapacity);
+ allocate(cache, buf, reqCapacity);
+ return buf;
+ }
+
+ static int tinyIdx(int normCapacity) {
+ return normCapacity >>> 4;
+ }
+
+ static int smallIdx(int normCapacity) {
+ int tableIdx = 0;
+ int i = normCapacity >>> 10;
+ while (i != 0) {
+ i >>>= 1;
+ tableIdx ++;
+ }
+ return tableIdx;
+ }
+
+ // capacity < pageSize
+ boolean isTinyOrSmall(int normCapacity) {
+ return (normCapacity & subpageOverflowMask) == 0;
+ }
+
+ // normCapacity < 512
+ static boolean isTiny(int normCapacity) {
+ return (normCapacity & 0xFFFFFE00) == 0;
+ }
+
+ private void allocate(PoolThreadCache cache, PooledByteBuf buf, final int reqCapacity) {
+ final int normCapacity = normalizeCapacity(reqCapacity);
+ if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
+ int tableIdx;
+ PoolSubpage[] table;
+ if (isTiny(normCapacity)) { // < 512
+ if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
+ // was able to allocate out of the cache so move on
+ return;
+ }
+ tableIdx = tinyIdx(normCapacity);
+ table = tinySubpagePools;
+ } else {
+ if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
+ // was able to allocate out of the cache so move on
+ return;
+ }
+ tableIdx = smallIdx(normCapacity);
+ table = smallSubpagePools;
+ }
+
+ synchronized (this) {
+ final PoolSubpage head = table[tableIdx];
+ final PoolSubpage s = head.next;
+ if (s != head) {
+ assert s.doNotDestroy && s.elemSize == normCapacity;
+ long handle = s.allocate();
+ assert handle >= 0;
+ s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
+ return;
+ }
+ }
+ } else if (normCapacity <= chunkSize) {
+ if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
+ // was able to allocate out of the cache so move on
+ return;
+ }
+ } else {
+ // Huge allocations are never served via the cache so just call allocateHuge
+ allocateHuge(buf, reqCapacity);
+ return;
+ }
+ allocateNormal(buf, reqCapacity, normCapacity);
+ }
+
+ private synchronized void allocateNormal(PooledByteBuf buf, int reqCapacity, int normCapacity) {
+ if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
+ q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
+ q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) {
+ return;
+ }
+
+ // Add a new chunk.
+ PoolChunk c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
+ long handle = c.allocate(normCapacity);
+ assert handle > 0;
+ c.initBuf(buf, handle, reqCapacity);
+ qInit.add(c);
+ }
+
+ private void allocateHuge(PooledByteBuf buf, int reqCapacity) {
+ buf.initUnpooled(newUnpooledChunk(reqCapacity), reqCapacity);
+ }
+
+ void free(PoolChunk chunk, long handle, int normCapacity) {
+ if (chunk.unpooled) {
+ destroyChunk(chunk);
+ } else {
+ PoolThreadCache cache = parent.threadCache.get();
+ if (cache.add(this, chunk, handle, normCapacity)) {
+ // cached so not free it.
+ return;
+ }
+ synchronized (this) {
+ chunk.parent.free(chunk, handle);
+ }
+ }
+ }
+
+ PoolSubpage findSubpagePoolHead(int elemSize) {
+ int tableIdx;
+ PoolSubpage[] table;
+ if (isTiny(elemSize)) { // < 512
+ tableIdx = elemSize >>> 4;
+ table = tinySubpagePools;
+ } else {
+ tableIdx = 0;
+ elemSize >>>= 10;
+ while (elemSize != 0) {
+ elemSize >>>= 1;
+ tableIdx ++;
+ }
+ table = smallSubpagePools;
+ }
+
+ return table[tableIdx];
+ }
+
+ int normalizeCapacity(int reqCapacity) {
+ if (reqCapacity < 0) {
+ throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)");
+ }
+ if (reqCapacity >= chunkSize) {
+ return reqCapacity;
+ }
+
+ if (!isTiny(reqCapacity)) { // >= 512
+ // Doubled
+
+ int normalizedCapacity = reqCapacity;
+ normalizedCapacity --;
+ normalizedCapacity |= normalizedCapacity >>> 1;
+ normalizedCapacity |= normalizedCapacity >>> 2;
+ normalizedCapacity |= normalizedCapacity >>> 4;
+ normalizedCapacity |= normalizedCapacity >>> 8;
+ normalizedCapacity |= normalizedCapacity >>> 16;
+ normalizedCapacity ++;
+
+ if (normalizedCapacity < 0) {
+ normalizedCapacity >>>= 1;
+ }
+
+ return normalizedCapacity;
+ }
+
+ // Quantum-spaced
+ if ((reqCapacity & 15) == 0) {
+ return reqCapacity;
+ }
+
+ return (reqCapacity & ~15) + 16;
+ }
+
+ void reallocate(PooledByteBuf buf, int newCapacity, boolean freeOldMemory) {
+ if (newCapacity < 0 || newCapacity > buf.maxCapacity()) {
+ throw new IllegalArgumentException("newCapacity: " + newCapacity);
+ }
+
+ int oldCapacity = buf.length;
+ if (oldCapacity == newCapacity) {
+ return;
+ }
+
+ PoolChunk oldChunk = buf.chunk;
+ long oldHandle = buf.handle;
+ T oldMemory = buf.memory;
+ int oldOffset = buf.offset;
+ int oldMaxLength = buf.maxLength;
+ int readerIndex = buf.readerIndex();
+ int writerIndex = buf.writerIndex();
+
+ allocate(parent.threadCache.get(), buf, newCapacity);
+ if (newCapacity > oldCapacity) {
+ memoryCopy(
+ oldMemory, oldOffset,
+ buf.memory, buf.offset, oldCapacity);
+ } else if (newCapacity < oldCapacity) {
+ if (readerIndex < newCapacity) {
+ if (writerIndex > newCapacity) {
+ writerIndex = newCapacity;
+ }
+ memoryCopy(
+ oldMemory, oldOffset + readerIndex,
+ buf.memory, buf.offset + readerIndex, writerIndex - readerIndex);
+ } else {
+ readerIndex = writerIndex = newCapacity;
+ }
+ }
+
+ buf.setIndex(readerIndex, writerIndex);
+
+ if (freeOldMemory) {
+ free(oldChunk, oldHandle, oldMaxLength);
+ }
+ }
+
+ protected abstract PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize);
+ protected abstract PoolChunk newUnpooledChunk(int capacity);
+ protected abstract PooledByteBuf newByteBuf(int maxCapacity);
+ protected abstract void memoryCopy(T src, int srcOffset, T dst, int dstOffset, int length);
+ protected abstract void destroyChunk(PoolChunk chunk);
+
+ public synchronized String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("Chunk(s) at 0~25%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(qInit);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("Chunk(s) at 0~50%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(q000);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("Chunk(s) at 25~75%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(q025);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("Chunk(s) at 50~100%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(q050);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("Chunk(s) at 75~100%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(q075);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("Chunk(s) at 100%:");
+ buf.append(StringUtil.NEWLINE);
+ buf.append(q100);
+ buf.append(StringUtil.NEWLINE);
+ buf.append("tiny subpages:");
+ for (int i = 1; i < tinySubpagePools.length; i ++) {
+ PoolSubpage head = tinySubpagePools[i];
+ if (head.next == head) {
+ continue;
+ }
+
+ buf.append(StringUtil.NEWLINE);
+ buf.append(i);
+ buf.append(": ");
+ PoolSubpage s = head.next;
+ for (;;) {
+ buf.append(s);
+ s = s.next;
+ if (s == head) {
+ break;
+ }
+ }
+ }
+ buf.append(StringUtil.NEWLINE);
+ buf.append("small subpages:");
+ for (int i = 1; i < smallSubpagePools.length; i ++) {
+ PoolSubpage head = smallSubpagePools[i];
+ if (head.next == head) {
+ continue;
+ }
+
+ buf.append(StringUtil.NEWLINE);
+ buf.append(i);
+ buf.append(": ");
+ PoolSubpage s = head.next;
+ for (;;) {
+ buf.append(s);
+ s = s.next;
+ if (s == head) {
+ break;
+ }
+ }
+ }
+ buf.append(StringUtil.NEWLINE);
+
+ return buf.toString();
+ }
+
+ static final class HeapArena extends PoolArena {
+
+ HeapArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ super(parent, pageSize, maxOrder, pageShifts, chunkSize);
+ }
+
+ @Override
+ boolean isDirect() {
+ return false;
+ }
+
+ @Override
+ protected PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ return new PoolChunk(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize);
+ }
+
+ @Override
+ protected PoolChunk newUnpooledChunk(int capacity) {
+ return new PoolChunk(this, new byte[capacity], capacity);
+ }
+
+ @Override
+ protected void destroyChunk(PoolChunk chunk) {
+ // Rely on GC.
+ }
+
+ @Override
+ protected PooledByteBuf newByteBuf(int maxCapacity) {
+ return PooledHeapByteBuf.newInstance(maxCapacity);
+ }
+
+ @Override
+ protected void memoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int length) {
+ if (length == 0) {
+ return;
+ }
+
+ System.arraycopy(src, srcOffset, dst, dstOffset, length);
+ }
+ }
+
+ static final class DirectArena extends PoolArena {
+
+ private static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe();
+
+ DirectArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ super(parent, pageSize, maxOrder, pageShifts, chunkSize);
+ }
+
+ @Override
+ boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ protected PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ return new PoolChunk(
+ this, ByteBuffer.allocateDirect(chunkSize), pageSize, maxOrder, pageShifts, chunkSize);
+ }
+
+ @Override
+ protected PoolChunk newUnpooledChunk(int capacity) {
+ return new PoolChunk(this, ByteBuffer.allocateDirect(capacity), capacity);
+ }
+
+ @Override
+ protected void destroyChunk(PoolChunk chunk) {
+ PlatformDependent.freeDirectBuffer(chunk.memory);
+ }
+
+ @Override
+ protected PooledByteBuf newByteBuf(int maxCapacity) {
+ if (HAS_UNSAFE) {
+ return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
+ } else {
+ return PooledDirectByteBuf.newInstance(maxCapacity);
+ }
+ }
+
+ @Override
+ protected void memoryCopy(ByteBuffer src, int srcOffset, ByteBuffer dst, int dstOffset, int length) {
+ if (length == 0) {
+ return;
+ }
+
+ if (HAS_UNSAFE) {
+ PlatformDependent.copyMemory(
+ PlatformDependent.directBufferAddress(src) + srcOffset,
+ PlatformDependent.directBufferAddress(dst) + dstOffset, length);
+ } else {
+ // We must duplicate the NIO buffers because they may be accessed by other Netty buffers.
+ src = src.duplicate();
+ dst = dst.duplicate();
+ src.position(srcOffset).limit(srcOffset + length);
+ dst.position(dstOffset);
+ dst.put(src);
+ }
+ }
+ }
+}
diff --git a/common/src/common/net/buffer/PoolChunk.java b/common/src/common/net/buffer/PoolChunk.java
new file mode 100644
index 0000000..87234ec
--- /dev/null
+++ b/common/src/common/net/buffer/PoolChunk.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+/**
+ * Description of algorithm for PageRun/PoolSubpage allocation from PoolChunk
+ *
+ * Notation: The following terms are important to understand the code
+ * > page - a page is the smallest unit of memory chunk that can be allocated
+ * > chunk - a chunk is a collection of pages
+ * > in this code chunkSize = 2^{maxOrder} * pageSize
+ *
+ * To begin we allocate a byte array of size = chunkSize
+ * Whenever a ByteBuf of given size needs to be created we search for the first position
+ * in the byte array that has enough empty space to accommodate the requested size and
+ * return a (long) handle that encodes this offset information, (this memory segment is then
+ * marked as reserved so it is always used by exactly one ByteBuf and no more)
+ *
+ * For simplicity all sizes are normalized according to PoolArena#normalizeCapacity method
+ * This ensures that when we request for memory segments of size >= pageSize the normalizedCapacity
+ * equals the next nearest power of 2
+ *
+ * To search for the first offset in chunk that has at least requested size available we construct a
+ * complete balanced binary tree and store it in an array (just like heaps) - memoryMap
+ *
+ * The tree looks like this (the size of each node being mentioned in the parenthesis)
+ *
+ * depth=0 1 node (chunkSize)
+ * depth=1 2 nodes (chunkSize/2)
+ * ..
+ * ..
+ * depth=d 2^d nodes (chunkSize/2^d)
+ * ..
+ * depth=maxOrder 2^maxOrder nodes (chunkSize/2^{maxOrder} = pageSize)
+ *
+ * depth=maxOrder is the last level and the leafs consist of pages
+ *
+ * With this tree available searching in chunkArray translates like this:
+ * To allocate a memory segment of size chunkSize/2^k we search for the first node (from left) at height k
+ * which is unused
+ *
+ * Algorithm:
+ * ----------
+ * Encode the tree in memoryMap with the notation
+ * memoryMap[id] = x => in the subtree rooted at id, the first node that is free to be allocated
+ * is at depth x (counted from depth=0) i.e., at depths [depth_of_id, x), there is no node that is free
+ *
+ * As we allocate & free nodes, we update values stored in memoryMap so that the property is maintained
+ *
+ * Initialization -
+ * In the beginning we construct the memoryMap array by storing the depth of a node at each node
+ * i.e., memoryMap[id] = depth_of_id
+ *
+ * Observations:
+ * -------------
+ * 1) memoryMap[id] = depth_of_id => it is free / unallocated
+ * 2) memoryMap[id] > depth_of_id => at least one of its child nodes is allocated, so we cannot allocate it, but
+ * some of its children can still be allocated based on their availability
+ * 3) memoryMap[id] = maxOrder + 1 => the node is fully allocated & thus none of its children can be allocated, it
+ * is thus marked as unusable
+ *
+ * Algorithm: [allocateNode(d) => we want to find the first node (from left) at height h that can be allocated]
+ * ----------
+ * 1) start at root (i.e., depth = 0 or id = 1)
+ * 2) if memoryMap[1] > d => cannot be allocated from this chunk
+ * 3) if left node value <= h; we can allocate from left subtree so move to left and repeat until found
+ * 4) else try in right subtree
+ *
+ * Algorithm: [allocateRun(size)]
+ * ----------
+ * 1) Compute d = log_2(chunkSize/size)
+ * 2) Return allocateNode(d)
+ *
+ * Algorithm: [allocateSubpage(size)]
+ * ----------
+ * 1) use allocateNode(maxOrder) to find an empty (i.e., unused) leaf (i.e., page)
+ * 2) use this handle to construct the PoolSubpage object or if it already exists just call init(normCapacity)
+ * note that this PoolSubpage object is added to subpagesPool in the PoolArena when we init() it
+ *
+ * Note:
+ * -----
+ * In the implementation for improving cache coherence,
+ * we store 2 pieces of information (i.e, 2 byte vals) as a short value in memoryMap
+ *
+ * memoryMap[id]= (depth_of_id, x)
+ * where as per convention defined above
+ * the second value (i.e, x) indicates that the first node which is free to be allocated is at depth x (from root)
+ */
+
+final class PoolChunk {
+
+ final PoolArena arena;
+ final T memory;
+ final boolean unpooled;
+
+ private final byte[] memoryMap;
+ private final byte[] depthMap;
+ private final PoolSubpage[] subpages;
+ /** Used to determine if the requested capacity is equal to or greater than pageSize. */
+ private final int subpageOverflowMask;
+ private final int pageSize;
+ private final int pageShifts;
+ private final int maxOrder;
+ private final int chunkSize;
+ private final int log2ChunkSize;
+ private final int maxSubpageAllocs;
+ /** Used to mark memory as unusable */
+ private final byte unusable;
+
+ private int freeBytes;
+
+ PoolChunkList parent;
+ PoolChunk prev;
+ PoolChunk next;
+
+ // TODO: Test if adding padding helps under contention
+ //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
+
+ PoolChunk(PoolArena arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
+ unpooled = false;
+ this.arena = arena;
+ this.memory = memory;
+ this.pageSize = pageSize;
+ this.pageShifts = pageShifts;
+ this.maxOrder = maxOrder;
+ this.chunkSize = chunkSize;
+ unusable = (byte) (maxOrder + 1);
+ log2ChunkSize = log2(chunkSize);
+ subpageOverflowMask = ~(pageSize - 1);
+ freeBytes = chunkSize;
+
+ assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
+ maxSubpageAllocs = 1 << maxOrder;
+
+ // Generate the memory map.
+ memoryMap = new byte[maxSubpageAllocs << 1];
+ depthMap = new byte[memoryMap.length];
+ int memoryMapIndex = 1;
+ for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
+ int depth = 1 << d;
+ for (int p = 0; p < depth; ++ p) {
+ // in each level traverse left to right and set value to the depth of subtree
+ memoryMap[memoryMapIndex] = (byte) d;
+ depthMap[memoryMapIndex] = (byte) d;
+ memoryMapIndex ++;
+ }
+ }
+
+ subpages = newSubpageArray(maxSubpageAllocs);
+ }
+
+ /** Creates a special chunk that is not pooled. */
+ PoolChunk(PoolArena arena, T memory, int size) {
+ unpooled = true;
+ this.arena = arena;
+ this.memory = memory;
+ memoryMap = null;
+ depthMap = null;
+ subpages = null;
+ subpageOverflowMask = 0;
+ pageSize = 0;
+ pageShifts = 0;
+ maxOrder = 0;
+ unusable = (byte) (maxOrder + 1);
+ chunkSize = size;
+ log2ChunkSize = log2(chunkSize);
+ maxSubpageAllocs = 0;
+ }
+
+
+ private PoolSubpage[] newSubpageArray(int size) {
+ return new PoolSubpage[size];
+ }
+
+ int usage() {
+ final int freeBytes = this.freeBytes;
+ if (freeBytes == 0) {
+ return 100;
+ }
+
+ int freePercentage = (int) (freeBytes * 100L / chunkSize);
+ if (freePercentage == 0) {
+ return 99;
+ }
+ return 100 - freePercentage;
+ }
+
+ long allocate(int normCapacity) {
+ if ((normCapacity & subpageOverflowMask) != 0) { // >= pageSize
+ return allocateRun(normCapacity);
+ } else {
+ return allocateSubpage(normCapacity);
+ }
+ }
+
+ /**
+ * Update method used by allocate
+ * This is triggered only when a successor is allocated and all its predecessors
+ * need to update their state
+ * The minimal depth at which subtree rooted at id has some free space
+ *
+ * @param id id
+ */
+ private void updateParentsAlloc(int id) {
+ while (id > 1) {
+ int parentId = id >>> 1;
+ byte val1 = value(id);
+ byte val2 = value(id ^ 1);
+ byte val = val1 < val2 ? val1 : val2;
+ setValue(parentId, val);
+ id = parentId;
+ }
+ }
+
+ /**
+ * Update method used by free
+ * This needs to handle the special case when both children are completely free
+ * in which case parent be directly allocated on request of size = child-size * 2
+ *
+ * @param id id
+ */
+ private void updateParentsFree(int id) {
+ int logChild = depth(id) + 1;
+ while (id > 1) {
+ int parentId = id >>> 1;
+ byte val1 = value(id);
+ byte val2 = value(id ^ 1);
+ logChild -= 1; // in first iteration equals log, subsequently reduce 1 from logChild as we traverse up
+
+ if (val1 == logChild && val2 == logChild) {
+ setValue(parentId, (byte) (logChild - 1));
+ } else {
+ byte val = val1 < val2 ? val1 : val2;
+ setValue(parentId, val);
+ }
+
+ id = parentId;
+ }
+ }
+
+ /**
+ * Algorithm to allocate an index in memoryMap when we query for a free node
+ * at depth d
+ *
+ * @param d depth
+ * @return index in memoryMap
+ */
+ private int allocateNode(int d) {
+ int id = 1;
+ int initial = - (1 << d); // has last d bits = 0 and rest all = 1
+ byte val = value(id);
+ if (val > d) { // unusable
+ return -1;
+ }
+ while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
+ id <<= 1;
+ val = value(id);
+ if (val > d) {
+ id ^= 1;
+ val = value(id);
+ }
+ }
+ byte value = value(id);
+ assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
+ value, id & initial, d);
+ setValue(id, unusable); // mark as unusable
+ updateParentsAlloc(id);
+ return id;
+ }
+
+ /**
+ * Allocate a run of pages (>=1)
+ *
+ * @param normCapacity normalized capacity
+ * @return index in memoryMap
+ */
+ private long allocateRun(int normCapacity) {
+ int d = maxOrder - (log2(normCapacity) - pageShifts);
+ int id = allocateNode(d);
+ if (id < 0) {
+ return id;
+ }
+ freeBytes -= runLength(id);
+ return id;
+ }
+
+ /**
+ * Create/ initialize a new PoolSubpage of normCapacity
+ * Any PoolSubpage created/ initialized here is added to subpage pool in the PoolArena that owns this PoolChunk
+ *
+ * @param normCapacity normalized capacity
+ * @return index in memoryMap
+ */
+ private long allocateSubpage(int normCapacity) {
+ int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
+ int id = allocateNode(d);
+ if (id < 0) {
+ return id;
+ }
+
+ final PoolSubpage[] subpages = this.subpages;
+ final int pageSize = this.pageSize;
+
+ freeBytes -= pageSize;
+
+ int subpageIdx = subpageIdx(id);
+ PoolSubpage subpage = subpages[subpageIdx];
+ if (subpage == null) {
+ subpage = new PoolSubpage(this, id, runOffset(id), pageSize, normCapacity);
+ subpages[subpageIdx] = subpage;
+ } else {
+ subpage.init(normCapacity);
+ }
+ return subpage.allocate();
+ }
+
+ /**
+ * Free a subpage or a run of pages
+ * When a subpage is freed from PoolSubpage, it might be added back to subpage pool of the owning PoolArena
+ * If the subpage pool in PoolArena has at least one other PoolSubpage of given elemSize, we can
+ * completely free the owning Page so it is available for subsequent allocations
+ *
+ * @param handle handle to free
+ */
+ void free(long handle) {
+ int memoryMapIdx = (int) handle;
+ int bitmapIdx = (int) (handle >>> Integer.SIZE);
+
+ if (bitmapIdx != 0) { // free a subpage
+ PoolSubpage subpage = subpages[subpageIdx(memoryMapIdx)];
+ assert subpage != null && subpage.doNotDestroy;
+ if (subpage.free(bitmapIdx & 0x3FFFFFFF)) {
+ return;
+ }
+ }
+ freeBytes += runLength(memoryMapIdx);
+ setValue(memoryMapIdx, depth(memoryMapIdx));
+ updateParentsFree(memoryMapIdx);
+ }
+
+ void initBuf(PooledByteBuf buf, long handle, int reqCapacity) {
+ int memoryMapIdx = (int) handle;
+ int bitmapIdx = (int) (handle >>> Integer.SIZE);
+ if (bitmapIdx == 0) {
+ byte val = value(memoryMapIdx);
+ assert val == unusable : String.valueOf(val);
+ buf.init(this, handle, runOffset(memoryMapIdx), reqCapacity, runLength(memoryMapIdx));
+ } else {
+ initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity);
+ }
+ }
+
+ void initBufWithSubpage(PooledByteBuf buf, long handle, int reqCapacity) {
+ initBufWithSubpage(buf, handle, (int) (handle >>> Integer.SIZE), reqCapacity);
+ }
+
+ private void initBufWithSubpage(PooledByteBuf buf, long handle, int bitmapIdx, int reqCapacity) {
+ assert bitmapIdx != 0;
+
+ int memoryMapIdx = (int) handle;
+
+ PoolSubpage subpage = subpages[subpageIdx(memoryMapIdx)];
+ assert subpage.doNotDestroy;
+ assert reqCapacity <= subpage.elemSize;
+
+ buf.init(
+ this, handle,
+ runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize);
+ }
+
+ private byte value(int id) {
+ return memoryMap[id];
+ }
+
+ private void setValue(int id, byte val) {
+ memoryMap[id] = val;
+ }
+
+ private byte depth(int id) {
+ return depthMap[id];
+ }
+
+ private static int log2(int val) {
+ // compute the (0-based, with lsb = 0) position of highest set bit i.e, log2
+ return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(val);
+ }
+
+ private int runLength(int id) {
+ // represents the size in #bytes supported by node 'id' in the tree
+ return 1 << log2ChunkSize - depth(id);
+ }
+
+ private int runOffset(int id) {
+ // represents the 0-based offset in #bytes from start of the byte-array chunk
+ int shift = id ^ 1 << depth(id);
+ return shift * runLength(id);
+ }
+
+ private int subpageIdx(int memoryMapIdx) {
+ return memoryMapIdx ^ maxSubpageAllocs; // remove highest set bit, to get offset
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("Chunk(");
+ buf.append(Integer.toHexString(System.identityHashCode(this)));
+ buf.append(": ");
+ buf.append(usage());
+ buf.append("%, ");
+ buf.append(chunkSize - freeBytes);
+ buf.append('/');
+ buf.append(chunkSize);
+ buf.append(')');
+ return buf.toString();
+ }
+}
diff --git a/common/src/common/net/buffer/PoolChunkList.java b/common/src/common/net/buffer/PoolChunkList.java
new file mode 100644
index 0000000..f0a5f40
--- /dev/null
+++ b/common/src/common/net/buffer/PoolChunkList.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import common.net.util.internal.StringUtil;
+
+final class PoolChunkList {
+ private final PoolArena arena;
+ private final PoolChunkList nextList;
+ PoolChunkList prevList;
+
+ private final int minUsage;
+ private final int maxUsage;
+
+ private PoolChunk head;
+
+ // TODO: Test if adding padding helps under contention
+ //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
+
+ PoolChunkList(PoolArena arena, PoolChunkList nextList, int minUsage, int maxUsage) {
+ this.arena = arena;
+ this.nextList = nextList;
+ this.minUsage = minUsage;
+ this.maxUsage = maxUsage;
+ }
+
+ boolean allocate(PooledByteBuf buf, int reqCapacity, int normCapacity) {
+ if (head == null) {
+ return false;
+ }
+
+ for (PoolChunk cur = head;;) {
+ long handle = cur.allocate(normCapacity);
+ if (handle < 0) {
+ cur = cur.next;
+ if (cur == null) {
+ return false;
+ }
+ } else {
+ cur.initBuf(buf, handle, reqCapacity);
+ if (cur.usage() >= maxUsage) {
+ remove(cur);
+ nextList.add(cur);
+ }
+ return true;
+ }
+ }
+ }
+
+ void free(PoolChunk chunk, long handle) {
+ chunk.free(handle);
+ if (chunk.usage() < minUsage) {
+ remove(chunk);
+ if (prevList == null) {
+ assert chunk.usage() == 0;
+ arena.destroyChunk(chunk);
+ } else {
+ prevList.add(chunk);
+ }
+ }
+ }
+
+ void add(PoolChunk chunk) {
+ if (chunk.usage() >= maxUsage) {
+ nextList.add(chunk);
+ return;
+ }
+
+ chunk.parent = this;
+ if (head == null) {
+ head = chunk;
+ chunk.prev = null;
+ chunk.next = null;
+ } else {
+ chunk.prev = null;
+ chunk.next = head;
+ head.prev = chunk;
+ head = chunk;
+ }
+ }
+
+ private void remove(PoolChunk cur) {
+ if (cur == head) {
+ head = cur.next;
+ if (head != null) {
+ head.prev = null;
+ }
+ } else {
+ PoolChunk next = cur.next;
+ cur.prev.next = next;
+ if (next != null) {
+ next.prev = cur.prev;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (head == null) {
+ return "none";
+ }
+
+ StringBuilder buf = new StringBuilder();
+ for (PoolChunk cur = head;;) {
+ buf.append(cur);
+ cur = cur.next;
+ if (cur == null) {
+ break;
+ }
+ buf.append(StringUtil.NEWLINE);
+ }
+
+ return buf.toString();
+ }
+}
diff --git a/common/src/common/net/buffer/PoolSubpage.java b/common/src/common/net/buffer/PoolSubpage.java
new file mode 100644
index 0000000..ae643c7
--- /dev/null
+++ b/common/src/common/net/buffer/PoolSubpage.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+final class PoolSubpage {
+
+ final PoolChunk chunk;
+ private final int memoryMapIdx;
+ private final int runOffset;
+ private final int pageSize;
+ private final long[] bitmap;
+
+ PoolSubpage prev;
+ PoolSubpage next;
+
+ boolean doNotDestroy;
+ int elemSize;
+ private int maxNumElems;
+ private int bitmapLength;
+ private int nextAvail;
+ private int numAvail;
+
+ // TODO: Test if adding padding helps under contention
+ //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
+
+ /** Special constructor that creates a linked list head */
+ PoolSubpage(int pageSize) {
+ chunk = null;
+ memoryMapIdx = -1;
+ runOffset = -1;
+ elemSize = -1;
+ this.pageSize = pageSize;
+ bitmap = null;
+ }
+
+ PoolSubpage(PoolChunk chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
+ this.chunk = chunk;
+ this.memoryMapIdx = memoryMapIdx;
+ this.runOffset = runOffset;
+ this.pageSize = pageSize;
+ bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
+ init(elemSize);
+ }
+
+ void init(int elemSize) {
+ doNotDestroy = true;
+ this.elemSize = elemSize;
+ if (elemSize != 0) {
+ maxNumElems = numAvail = pageSize / elemSize;
+ nextAvail = 0;
+ bitmapLength = maxNumElems >>> 6;
+ if ((maxNumElems & 63) != 0) {
+ bitmapLength ++;
+ }
+
+ for (int i = 0; i < bitmapLength; i ++) {
+ bitmap[i] = 0;
+ }
+ }
+
+ addToPool();
+ }
+
+ /**
+ * Returns the bitmap index of the subpage allocation.
+ */
+ long allocate() {
+ if (elemSize == 0) {
+ return toHandle(0);
+ }
+
+ if (numAvail == 0 || !doNotDestroy) {
+ return -1;
+ }
+
+ final int bitmapIdx = getNextAvail();
+ int q = bitmapIdx >>> 6;
+ int r = bitmapIdx & 63;
+ assert (bitmap[q] >>> r & 1) == 0;
+ bitmap[q] |= 1L << r;
+
+ if (-- numAvail == 0) {
+ removeFromPool();
+ }
+
+ return toHandle(bitmapIdx);
+ }
+
+ /**
+ * @return {@code true} if this subpage is in use.
+ * {@code false} if this subpage is not used by its chunk and thus it's OK to be released.
+ */
+ boolean free(int bitmapIdx) {
+
+ if (elemSize == 0) {
+ return true;
+ }
+
+ int q = bitmapIdx >>> 6;
+ int r = bitmapIdx & 63;
+ assert (bitmap[q] >>> r & 1) != 0;
+ bitmap[q] ^= 1L << r;
+
+ setNextAvail(bitmapIdx);
+
+ if (numAvail ++ == 0) {
+ addToPool();
+ return true;
+ }
+
+ if (numAvail != maxNumElems) {
+ return true;
+ } else {
+ // Subpage not in use (numAvail == maxNumElems)
+ if (prev == next) {
+ // Do not remove if this subpage is the only one left in the pool.
+ return true;
+ }
+
+ // Remove this subpage from the pool if there are other subpages left in the pool.
+ doNotDestroy = false;
+ removeFromPool();
+ return false;
+ }
+ }
+
+ private void addToPool() {
+ PoolSubpage head = chunk.arena.findSubpagePoolHead(elemSize);
+ assert prev == null && next == null;
+ prev = head;
+ next = head.next;
+ next.prev = this;
+ head.next = this;
+ }
+
+ private void removeFromPool() {
+ assert prev != null && next != null;
+ prev.next = next;
+ next.prev = prev;
+ next = null;
+ prev = null;
+ }
+
+ private void setNextAvail(int bitmapIdx) {
+ nextAvail = bitmapIdx;
+ }
+
+ private int getNextAvail() {
+ int nextAvail = this.nextAvail;
+ if (nextAvail >= 0) {
+ this.nextAvail = -1;
+ return nextAvail;
+ }
+ return findNextAvail();
+ }
+
+ private int findNextAvail() {
+ final long[] bitmap = this.bitmap;
+ final int bitmapLength = this.bitmapLength;
+ for (int i = 0; i < bitmapLength; i ++) {
+ long bits = bitmap[i];
+ if (~bits != 0) {
+ return findNextAvail0(i, bits);
+ }
+ }
+ return -1;
+ }
+
+ private int findNextAvail0(int i, long bits) {
+ final int maxNumElems = this.maxNumElems;
+ final int baseVal = i << 6;
+
+ for (int j = 0; j < 64; j ++) {
+ if ((bits & 1) == 0) {
+ int val = baseVal | j;
+ if (val < maxNumElems) {
+ return val;
+ } else {
+ break;
+ }
+ }
+ bits >>>= 1;
+ }
+ return -1;
+ }
+
+ private long toHandle(int bitmapIdx) {
+ return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
+ }
+
+ public String toString() {
+ if (!doNotDestroy) {
+ return "(" + memoryMapIdx + ": not in use)";
+ }
+
+ return String.valueOf('(') + memoryMapIdx + ": " + (maxNumElems - numAvail) + '/' + maxNumElems +
+ ", offset: " + runOffset + ", length: " + pageSize + ", elemSize: " + elemSize + ')';
+ }
+}
diff --git a/common/src/common/net/buffer/PoolThreadCache.java b/common/src/common/net/buffer/PoolThreadCache.java
new file mode 100644
index 0000000..1a41c18
--- /dev/null
+++ b/common/src/common/net/buffer/PoolThreadCache.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+
+import java.nio.ByteBuffer;
+
+import common.net.util.ThreadDeathWatcher;
+import common.net.util.internal.logging.InternalLogger;
+import common.net.util.internal.logging.InternalLoggerFactory;
+
+/**
+ * Acts a Thread cache for allocations. This implementation is moduled after
+ * jemalloc and the descripted
+ * technics of Scalable memory allocation using jemalloc .
+ */
+final class PoolThreadCache {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(PoolThreadCache.class);
+
+ final PoolArena heapArena;
+ final PoolArena directArena;
+
+ // Hold the caches for the different size classes, which are tiny, small and normal.
+ private final MemoryRegionCache[] tinySubPageHeapCaches;
+ private final MemoryRegionCache[] smallSubPageHeapCaches;
+ private final MemoryRegionCache[] tinySubPageDirectCaches;
+ private final MemoryRegionCache[] smallSubPageDirectCaches;
+ private final MemoryRegionCache[] normalHeapCaches;
+ private final MemoryRegionCache[] normalDirectCaches;
+
+ // Used for bitshifting when calculate the index of normal caches later
+ private final int numShiftsNormalDirect;
+ private final int numShiftsNormalHeap;
+ private final int freeSweepAllocationThreshold;
+
+ private int allocations;
+
+ private final Thread thread = Thread.currentThread();
+ private final Runnable freeTask = new Runnable() {
+ @Override
+ public void run() {
+ free0();
+ }
+ };
+
+ // TODO: Test if adding padding helps under contention
+ //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
+
+ PoolThreadCache(PoolArena heapArena, PoolArena directArena,
+ int tinyCacheSize, int smallCacheSize, int normalCacheSize,
+ int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
+ if (maxCachedBufferCapacity < 0) {
+ throw new IllegalArgumentException("maxCachedBufferCapacity: "
+ + maxCachedBufferCapacity + " (expected: >= 0)");
+ }
+ if (freeSweepAllocationThreshold < 1) {
+ throw new IllegalArgumentException("freeSweepAllocationThreshold: "
+ + maxCachedBufferCapacity + " (expected: > 0)");
+ }
+ this.freeSweepAllocationThreshold = freeSweepAllocationThreshold;
+ this.heapArena = heapArena;
+ this.directArena = directArena;
+ if (directArena != null) {
+ tinySubPageDirectCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools);
+ smallSubPageDirectCaches = createSubPageCaches(smallCacheSize, directArena.numSmallSubpagePools);
+
+ numShiftsNormalDirect = log2(directArena.pageSize);
+ normalDirectCaches = createNormalCaches(
+ normalCacheSize, maxCachedBufferCapacity, directArena);
+ } else {
+ // No directArea is configured so just null out all caches
+ tinySubPageDirectCaches = null;
+ smallSubPageDirectCaches = null;
+ normalDirectCaches = null;
+ numShiftsNormalDirect = -1;
+ }
+ if (heapArena != null) {
+ // Create the caches for the heap allocations
+ tinySubPageHeapCaches = createSubPageCaches(tinyCacheSize, PoolArena.numTinySubpagePools);
+ smallSubPageHeapCaches = createSubPageCaches(smallCacheSize, heapArena.numSmallSubpagePools);
+
+ numShiftsNormalHeap = log2(heapArena.pageSize);
+ normalHeapCaches = createNormalCaches(
+ normalCacheSize, maxCachedBufferCapacity, heapArena);
+ } else {
+ // No heapArea is configured so just null out all caches
+ tinySubPageHeapCaches = null;
+ smallSubPageHeapCaches = null;
+ normalHeapCaches = null;
+ numShiftsNormalHeap = -1;
+ }
+
+ // The thread-local cache will keep a list of pooled buffers which must be returned to
+ // the pool when the thread is not alive anymore.
+ ThreadDeathWatcher.watch(thread, freeTask);
+ }
+
+ private static SubPageMemoryRegionCache[] createSubPageCaches(int cacheSize, int numCaches) {
+ if (cacheSize > 0) {
+
+ SubPageMemoryRegionCache[] cache = new SubPageMemoryRegionCache[numCaches];
+ for (int i = 0; i < cache.length; i++) {
+ // TODO: maybe use cacheSize / cache.length
+ cache[i] = new SubPageMemoryRegionCache(cacheSize);
+ }
+ return cache;
+ } else {
+ return null;
+ }
+ }
+
+ private static NormalMemoryRegionCache[] createNormalCaches(
+ int cacheSize, int maxCachedBufferCapacity, PoolArena area) {
+ if (cacheSize > 0) {
+ int max = Math.min(area.chunkSize, maxCachedBufferCapacity);
+ int arraySize = Math.max(1, max / area.pageSize);
+
+
+ NormalMemoryRegionCache[] cache = new NormalMemoryRegionCache[arraySize];
+ for (int i = 0; i < cache.length; i++) {
+ cache[i] = new NormalMemoryRegionCache(cacheSize);
+ }
+ return cache;
+ } else {
+ return null;
+ }
+ }
+
+ private static int log2(int val) {
+ int res = 0;
+ while (val > 1) {
+ val >>= 1;
+ res++;
+ }
+ return res;
+ }
+
+ /**
+ * Try to allocate a tiny buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
+ */
+ boolean allocateTiny(PoolArena> area, PooledByteBuf> buf, int reqCapacity, int normCapacity) {
+ return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity);
+ }
+
+ /**
+ * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
+ */
+ boolean allocateSmall(PoolArena> area, PooledByteBuf> buf, int reqCapacity, int normCapacity) {
+ return allocate(cacheForSmall(area, normCapacity), buf, reqCapacity);
+ }
+
+ /**
+ * Try to allocate a small buffer out of the cache. Returns {@code true} if successful {@code false} otherwise
+ */
+ boolean allocateNormal(PoolArena> area, PooledByteBuf> buf, int reqCapacity, int normCapacity) {
+ return allocate(cacheForNormal(area, normCapacity), buf, reqCapacity);
+ }
+
+
+ private boolean allocate(MemoryRegionCache> cache, PooledByteBuf buf, int reqCapacity) {
+ if (cache == null) {
+ // no cache found so just return false here
+ return false;
+ }
+ boolean allocated = cache.allocate(buf, reqCapacity);
+ if (++ allocations >= freeSweepAllocationThreshold) {
+ allocations = 0;
+ trim();
+ }
+ return allocated;
+ }
+
+ /**
+ * Add {@link PoolChunk} and {@code handle} to the cache if there is enough room.
+ * Returns {@code true} if it fit into the cache {@code false} otherwise.
+ */
+
+ boolean add(PoolArena> area, PoolChunk chunk, long handle, int normCapacity) {
+ MemoryRegionCache> cache;
+ if (area.isTinyOrSmall(normCapacity)) {
+ if (PoolArena.isTiny(normCapacity)) {
+ cache = cacheForTiny(area, normCapacity);
+ } else {
+ cache = cacheForSmall(area, normCapacity);
+ }
+ } else {
+ cache = cacheForNormal(area, normCapacity);
+ }
+ if (cache == null) {
+ return false;
+ }
+ return cache.add(chunk, handle);
+ }
+
+ /**
+ * Should be called if the Thread that uses this cache is about to exist to release resources out of the cache
+ */
+ void free() {
+ ThreadDeathWatcher.unwatch(thread, freeTask);
+ free0();
+ }
+
+ private void free0() {
+ int numFreed = free(tinySubPageDirectCaches) +
+ free(smallSubPageDirectCaches) +
+ free(normalDirectCaches) +
+ free(tinySubPageHeapCaches) +
+ free(smallSubPageHeapCaches) +
+ free(normalHeapCaches);
+
+ if (numFreed > 0 && logger.isDebugEnabled()) {
+ logger.debug("Freed {} thread-local buffer(s) from thread: {}", numFreed, thread.getName());
+ }
+ }
+
+ private static int free(MemoryRegionCache>[] caches) {
+ if (caches == null) {
+ return 0;
+ }
+
+ int numFreed = 0;
+ for (MemoryRegionCache> c: caches) {
+ numFreed += free(c);
+ }
+ return numFreed;
+ }
+
+ private static int free(MemoryRegionCache> cache) {
+ if (cache == null) {
+ return 0;
+ }
+ return cache.free();
+ }
+
+ void trim() {
+ trim(tinySubPageDirectCaches);
+ trim(smallSubPageDirectCaches);
+ trim(normalDirectCaches);
+ trim(tinySubPageHeapCaches);
+ trim(smallSubPageHeapCaches);
+ trim(normalHeapCaches);
+ }
+
+ private static void trim(MemoryRegionCache>[] caches) {
+ if (caches == null) {
+ return;
+ }
+ for (MemoryRegionCache> c: caches) {
+ trim(c);
+ }
+ }
+
+ private static void trim(MemoryRegionCache> cache) {
+ if (cache == null) {
+ return;
+ }
+ cache.trim();
+ }
+
+ private MemoryRegionCache> cacheForTiny(PoolArena> area, int normCapacity) {
+ int idx = PoolArena.tinyIdx(normCapacity);
+ if (area.isDirect()) {
+ return cache(tinySubPageDirectCaches, idx);
+ }
+ return cache(tinySubPageHeapCaches, idx);
+ }
+
+ private MemoryRegionCache> cacheForSmall(PoolArena> area, int normCapacity) {
+ int idx = PoolArena.smallIdx(normCapacity);
+ if (area.isDirect()) {
+ return cache(smallSubPageDirectCaches, idx);
+ }
+ return cache(smallSubPageHeapCaches, idx);
+ }
+
+ private MemoryRegionCache> cacheForNormal(PoolArena> area, int normCapacity) {
+ if (area.isDirect()) {
+ int idx = log2(normCapacity >> numShiftsNormalDirect);
+ return cache(normalDirectCaches, idx);
+ }
+ int idx = log2(normCapacity >> numShiftsNormalHeap);
+ return cache(normalHeapCaches, idx);
+ }
+
+ private static MemoryRegionCache cache(MemoryRegionCache[] cache, int idx) {
+ if (cache == null || idx > cache.length - 1) {
+ return null;
+ }
+ return cache[idx];
+ }
+
+ /**
+ * Cache used for buffers which are backed by TINY or SMALL size.
+ */
+ private static final class SubPageMemoryRegionCache extends MemoryRegionCache {
+ SubPageMemoryRegionCache(int size) {
+ super(size);
+ }
+
+ @Override
+ protected void initBuf(
+ PoolChunk chunk, long handle, PooledByteBuf buf, int reqCapacity) {
+ chunk.initBufWithSubpage(buf, handle, reqCapacity);
+ }
+ }
+
+ /**
+ * Cache used for buffers which are backed by NORMAL size.
+ */
+ private static final class NormalMemoryRegionCache extends MemoryRegionCache {
+ NormalMemoryRegionCache(int size) {
+ super(size);
+ }
+
+ @Override
+ protected void initBuf(
+ PoolChunk chunk, long handle, PooledByteBuf buf, int reqCapacity) {
+ chunk.initBuf(buf, handle, reqCapacity);
+ }
+ }
+
+ /**
+ * Cache of {@link PoolChunk} and handles which can be used to allocate a buffer without locking at all.
+ */
+ private abstract static class MemoryRegionCache {
+ private final Entry[] entries;
+ private final int maxUnusedCached;
+ private int head;
+ private int tail;
+ private int maxEntriesInUse;
+ private int entriesInUse;
+
+
+ MemoryRegionCache(int size) {
+ entries = new Entry[powerOfTwo(size)];
+ for (int i = 0; i < entries.length; i++) {
+ entries[i] = new Entry();
+ }
+ maxUnusedCached = size / 2;
+ }
+
+ private static int powerOfTwo(int res) {
+ if (res <= 2) {
+ return 2;
+ }
+ res--;
+ res |= res >> 1;
+ res |= res >> 2;
+ res |= res >> 4;
+ res |= res >> 8;
+ res |= res >> 16;
+ res++;
+ return res;
+ }
+
+ /**
+ * Init the {@link PooledByteBuf} using the provided chunk and handle with the capacity restrictions.
+ */
+ protected abstract void initBuf(PoolChunk chunk, long handle,
+ PooledByteBuf buf, int reqCapacity);
+
+ /**
+ * Add to cache if not already full.
+ */
+ public boolean add(PoolChunk chunk, long handle) {
+ Entry entry = entries[tail];
+ if (entry.chunk != null) {
+ // cache is full
+ return false;
+ }
+ entriesInUse --;
+
+ entry.chunk = chunk;
+ entry.handle = handle;
+ tail = nextIdx(tail);
+ return true;
+ }
+
+ /**
+ * Allocate something out of the cache if possible and remove the entry from the cache.
+ */
+ public boolean allocate(PooledByteBuf buf, int reqCapacity) {
+ Entry entry = entries[head];
+ if (entry.chunk == null) {
+ return false;
+ }
+
+ entriesInUse ++;
+ if (maxEntriesInUse < entriesInUse) {
+ maxEntriesInUse = entriesInUse;
+ }
+ initBuf(entry.chunk, entry.handle, buf, reqCapacity);
+ // only null out the chunk as we only use the chunk to check if the buffer is full or not.
+ entry.chunk = null;
+ head = nextIdx(head);
+ return true;
+ }
+
+ /**
+ * Clear out this cache and free up all previous cached {@link PoolChunk}s and {@code handle}s.
+ */
+ public int free() {
+ int numFreed = 0;
+ entriesInUse = 0;
+ maxEntriesInUse = 0;
+ for (int i = head;; i = nextIdx(i)) {
+ if (freeEntry(entries[i])) {
+ numFreed++;
+ } else {
+ // all cleared
+ return numFreed;
+ }
+ }
+ }
+
+ /**
+ * Free up cached {@link PoolChunk}s if not allocated frequently enough.
+ */
+ private void trim() {
+ int free = size() - maxEntriesInUse;
+ entriesInUse = 0;
+ maxEntriesInUse = 0;
+
+ if (free <= maxUnusedCached) {
+ return;
+ }
+
+ int i = head;
+ for (; free > 0; free--) {
+ if (!freeEntry(entries[i])) {
+ // all freed
+ return;
+ }
+ i = nextIdx(i);
+ }
+ }
+
+
+ private static boolean freeEntry(Entry entry) {
+ PoolChunk chunk = entry.chunk;
+ if (chunk == null) {
+ return false;
+ }
+ // need to synchronize on the area from which it was allocated before.
+ synchronized (chunk.arena) {
+ chunk.parent.free(chunk, entry.handle);
+ }
+ entry.chunk = null;
+ return true;
+ }
+
+ /**
+ * Return the number of cached entries.
+ */
+ private int size() {
+ return tail - head & entries.length - 1;
+ }
+
+ private int nextIdx(int index) {
+ // use bitwise operation as this is faster as using modulo.
+ return index + 1 & entries.length - 1;
+ }
+
+ private static final class Entry {
+ PoolChunk chunk;
+ long handle;
+ }
+ }
+}
diff --git a/common/src/common/net/buffer/PooledByteBuf.java b/common/src/common/net/buffer/PooledByteBuf.java
new file mode 100644
index 0000000..34324d7
--- /dev/null
+++ b/common/src/common/net/buffer/PooledByteBuf.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import common.net.util.Recycler;
+
+abstract class PooledByteBuf extends AbstractReferenceCountedByteBuf {
+
+ private final Recycler.Handle recyclerHandle;
+
+ protected PoolChunk chunk;
+ protected long handle;
+ protected T memory;
+ protected int offset;
+ protected int length;
+ int maxLength;
+
+ private ByteBuffer tmpNioBuf;
+
+ protected PooledByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
+ super(maxCapacity);
+ this.recyclerHandle = recyclerHandle;
+ }
+
+ void init(PoolChunk chunk, long handle, int offset, int length, int maxLength) {
+ assert handle >= 0;
+ assert chunk != null;
+
+ this.chunk = chunk;
+ this.handle = handle;
+ memory = chunk.memory;
+ this.offset = offset;
+ this.length = length;
+ this.maxLength = maxLength;
+ setIndex(0, 0);
+ tmpNioBuf = null;
+ }
+
+ void initUnpooled(PoolChunk chunk, int length) {
+ assert chunk != null;
+
+ this.chunk = chunk;
+ handle = 0;
+ memory = chunk.memory;
+ offset = 0;
+ this.length = maxLength = length;
+ setIndex(0, 0);
+ tmpNioBuf = null;
+ }
+
+ @Override
+ public final int capacity() {
+ return length;
+ }
+
+ @Override
+ public final ByteBuf capacity(int newCapacity) {
+ ensureAccessible();
+
+ // If the request capacity does not require reallocation, just update the length of the memory.
+ if (chunk.unpooled) {
+ if (newCapacity == length) {
+ return this;
+ }
+ } else {
+ if (newCapacity > length) {
+ if (newCapacity <= maxLength) {
+ length = newCapacity;
+ return this;
+ }
+ } else if (newCapacity < length) {
+ if (newCapacity > maxLength >>> 1) {
+ if (maxLength <= 512) {
+ if (newCapacity > maxLength - 16) {
+ length = newCapacity;
+ setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
+ return this;
+ }
+ } else { // > 512 (i.e. >= 1024)
+ length = newCapacity;
+ setIndex(Math.min(readerIndex(), newCapacity), Math.min(writerIndex(), newCapacity));
+ return this;
+ }
+ }
+ } else {
+ return this;
+ }
+ }
+
+ // Reallocation required.
+ chunk.arena.reallocate(this, newCapacity, true);
+ return this;
+ }
+
+ @Override
+ public final ByteBufAllocator alloc() {
+ return chunk.arena.parent;
+ }
+
+ @Override
+ public final ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ @Override
+ public final ByteBuf unwrap() {
+ return null;
+ }
+
+ protected final ByteBuffer internalNioBuffer() {
+ ByteBuffer tmpNioBuf = this.tmpNioBuf;
+ if (tmpNioBuf == null) {
+ this.tmpNioBuf = tmpNioBuf = newInternalNioBuffer(memory);
+ }
+ return tmpNioBuf;
+ }
+
+ protected abstract ByteBuffer newInternalNioBuffer(T memory);
+
+ @Override
+ protected final void deallocate() {
+ if (handle >= 0) {
+ final long handle = this.handle;
+ this.handle = -1;
+ memory = null;
+ chunk.arena.free(chunk, handle, maxLength);
+ recycle();
+ }
+ }
+
+ private void recycle() {
+ Recycler.Handle recyclerHandle = this.recyclerHandle;
+ if (recyclerHandle != null) {
+ ((Recycler) recycler()).recycle(this, recyclerHandle);
+ }
+ }
+
+ protected abstract Recycler> recycler();
+
+ protected final int idx(int index) {
+ return offset + index;
+ }
+}
diff --git a/common/src/common/net/buffer/PooledByteBufAllocator.java b/common/src/common/net/buffer/PooledByteBufAllocator.java
new file mode 100644
index 0000000..4aaf1a0
--- /dev/null
+++ b/common/src/common/net/buffer/PooledByteBufAllocator.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import common.net.util.concurrent.FastThreadLocal;
+import common.net.util.internal.PlatformDependent;
+import common.net.util.internal.SystemPropertyUtil;
+import common.net.util.internal.logging.InternalLogger;
+import common.net.util.internal.logging.InternalLoggerFactory;
+
+public class PooledByteBufAllocator extends AbstractByteBufAllocator {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(PooledByteBufAllocator.class);
+ private static final int DEFAULT_NUM_HEAP_ARENA;
+ private static final int DEFAULT_NUM_DIRECT_ARENA;
+
+ private static final int DEFAULT_PAGE_SIZE;
+ private static final int DEFAULT_MAX_ORDER; // 8192 << 11 = 16 MiB per chunk
+ private static final int DEFAULT_TINY_CACHE_SIZE;
+ private static final int DEFAULT_SMALL_CACHE_SIZE;
+ private static final int DEFAULT_NORMAL_CACHE_SIZE;
+ private static final int DEFAULT_MAX_CACHED_BUFFER_CAPACITY;
+ private static final int DEFAULT_CACHE_TRIM_INTERVAL;
+
+ private static final int MIN_PAGE_SIZE = 4096;
+ private static final int MAX_CHUNK_SIZE = (int) (((long) Integer.MAX_VALUE + 1) / 2);
+
+ static {
+ int defaultPageSize = SystemPropertyUtil.getInt("game.net.allocator.pageSize", 8192);
+ Throwable pageSizeFallbackCause = null;
+ try {
+ validateAndCalculatePageShifts(defaultPageSize);
+ } catch (Throwable t) {
+ pageSizeFallbackCause = t;
+ defaultPageSize = 8192;
+ }
+ DEFAULT_PAGE_SIZE = defaultPageSize;
+
+ int defaultMaxOrder = SystemPropertyUtil.getInt("game.net.allocator.maxOrder", 11);
+ Throwable maxOrderFallbackCause = null;
+ try {
+ validateAndCalculateChunkSize(DEFAULT_PAGE_SIZE, defaultMaxOrder);
+ } catch (Throwable t) {
+ maxOrderFallbackCause = t;
+ defaultMaxOrder = 11;
+ }
+ DEFAULT_MAX_ORDER = defaultMaxOrder;
+
+ // Determine reasonable default for nHeapArena and nDirectArena.
+ // Assuming each arena has 3 chunks, the pool should not consume more than 50% of max memory.
+ final Runtime runtime = Runtime.getRuntime();
+ final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
+ DEFAULT_NUM_HEAP_ARENA = Math.max(0,
+ SystemPropertyUtil.getInt(
+ "game.net.allocator.numHeapArenas",
+ (int) Math.min(
+ runtime.availableProcessors(),
+ Runtime.getRuntime().maxMemory() / defaultChunkSize / 2 / 3)));
+ DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
+ SystemPropertyUtil.getInt(
+ "game.net.allocator.numDirectArenas",
+ (int) Math.min(
+ runtime.availableProcessors(),
+ PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
+
+ // cache sizes
+ DEFAULT_TINY_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.tinyCacheSize", 512);
+ DEFAULT_SMALL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.smallCacheSize", 256);
+ DEFAULT_NORMAL_CACHE_SIZE = SystemPropertyUtil.getInt("game.net.allocator.normalCacheSize", 64);
+
+ // 32 kb is the default maximum capacity of the cached buffer. Similar to what is explained in
+ // 'Scalable memory allocation using jemalloc'
+ DEFAULT_MAX_CACHED_BUFFER_CAPACITY = SystemPropertyUtil.getInt(
+ "game.net.allocator.maxCachedBufferCapacity", 32 * 1024);
+
+ // the number of threshold of allocations when cached entries will be freed up if not frequently used
+ DEFAULT_CACHE_TRIM_INTERVAL = SystemPropertyUtil.getInt(
+ "game.net.allocator.cacheTrimInterval", 8192);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("-Dgame.net.allocator.numHeapArenas: {}", DEFAULT_NUM_HEAP_ARENA);
+ logger.debug("-Dgame.net.allocator.numDirectArenas: {}", DEFAULT_NUM_DIRECT_ARENA);
+ if (pageSizeFallbackCause == null) {
+ logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE);
+ } else {
+ logger.debug("-Dgame.net.allocator.pageSize: {}", DEFAULT_PAGE_SIZE, pageSizeFallbackCause);
+ }
+ if (maxOrderFallbackCause == null) {
+ logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER);
+ } else {
+ logger.debug("-Dgame.net.allocator.maxOrder: {}", DEFAULT_MAX_ORDER, maxOrderFallbackCause);
+ }
+ logger.debug("-Dgame.net.allocator.chunkSize: {}", DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER);
+ logger.debug("-Dgame.net.allocator.tinyCacheSize: {}", DEFAULT_TINY_CACHE_SIZE);
+ logger.debug("-Dgame.net.allocator.smallCacheSize: {}", DEFAULT_SMALL_CACHE_SIZE);
+ logger.debug("-Dgame.net.allocator.normalCacheSize: {}", DEFAULT_NORMAL_CACHE_SIZE);
+ logger.debug("-Dgame.net.allocator.maxCachedBufferCapacity: {}", DEFAULT_MAX_CACHED_BUFFER_CAPACITY);
+ logger.debug("-Dgame.net.allocator.cacheTrimInterval: {}", DEFAULT_CACHE_TRIM_INTERVAL);
+ }
+ }
+
+ public static final PooledByteBufAllocator DEFAULT =
+ new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());
+
+ private final PoolArena[] heapArenas;
+ private final PoolArena[] directArenas;
+ private final int tinyCacheSize;
+ private final int smallCacheSize;
+ private final int normalCacheSize;
+
+ final PoolThreadLocalCache threadCache;
+
+ public PooledByteBufAllocator() {
+ this(false);
+ }
+
+ public PooledByteBufAllocator(boolean preferDirect) {
+ this(preferDirect, DEFAULT_NUM_HEAP_ARENA, DEFAULT_NUM_DIRECT_ARENA, DEFAULT_PAGE_SIZE, DEFAULT_MAX_ORDER);
+ }
+
+ public PooledByteBufAllocator(int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
+ this(false, nHeapArena, nDirectArena, pageSize, maxOrder);
+ }
+
+ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder) {
+ this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
+ DEFAULT_TINY_CACHE_SIZE, DEFAULT_SMALL_CACHE_SIZE, DEFAULT_NORMAL_CACHE_SIZE);
+ }
+
+ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
+ int tinyCacheSize, int smallCacheSize, int normalCacheSize) {
+ super(preferDirect);
+ threadCache = new PoolThreadLocalCache();
+ this.tinyCacheSize = tinyCacheSize;
+ this.smallCacheSize = smallCacheSize;
+ this.normalCacheSize = normalCacheSize;
+ final int chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
+
+ if (nHeapArena < 0) {
+ throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)");
+ }
+ if (nDirectArena < 0) {
+ throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)");
+ }
+
+ int pageShifts = validateAndCalculatePageShifts(pageSize);
+
+ if (nHeapArena > 0) {
+ heapArenas = newArenaArray(nHeapArena);
+ for (int i = 0; i < heapArenas.length; i ++) {
+ heapArenas[i] = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);
+ }
+ } else {
+ heapArenas = null;
+ }
+
+ if (nDirectArena > 0) {
+ directArenas = newArenaArray(nDirectArena);
+ for (int i = 0; i < directArenas.length; i ++) {
+ directArenas[i] = new PoolArena.DirectArena(this, pageSize, maxOrder, pageShifts, chunkSize);
+ }
+ } else {
+ directArenas = null;
+ }
+ }
+
+ @Deprecated
+
+ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
+ int tinyCacheSize, int smallCacheSize, int normalCacheSize,
+ long cacheThreadAliveCheckInterval) {
+ this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
+ tinyCacheSize, smallCacheSize, normalCacheSize);
+ }
+
+
+ private static PoolArena[] newArenaArray(int size) {
+ return new PoolArena[size];
+ }
+
+ private static int validateAndCalculatePageShifts(int pageSize) {
+ if (pageSize < MIN_PAGE_SIZE) {
+ throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + "+)");
+ }
+
+ if ((pageSize & pageSize - 1) != 0) {
+ throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
+ }
+
+ // Logarithm base 2. At this point we know that pageSize is a power of two.
+ return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
+ }
+
+ private static int validateAndCalculateChunkSize(int pageSize, int maxOrder) {
+ if (maxOrder > 14) {
+ throw new IllegalArgumentException("maxOrder: " + maxOrder + " (expected: 0-14)");
+ }
+
+ // Ensure the resulting chunkSize does not overflow.
+ int chunkSize = pageSize;
+ for (int i = maxOrder; i > 0; i --) {
+ if (chunkSize > MAX_CHUNK_SIZE / 2) {
+ throw new IllegalArgumentException(String.format(
+ "pageSize (%d) << maxOrder (%d) must not exceed %d", pageSize, maxOrder, MAX_CHUNK_SIZE));
+ }
+ chunkSize <<= 1;
+ }
+ return chunkSize;
+ }
+
+ @Override
+ protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
+ PoolThreadCache cache = threadCache.get();
+ PoolArena heapArena = cache.heapArena;
+
+ ByteBuf buf;
+ if (heapArena != null) {
+ buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
+ } else {
+ buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
+ }
+
+ return toLeakAwareBuffer(buf);
+ }
+
+ @Override
+ protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
+ PoolThreadCache cache = threadCache.get();
+ PoolArena directArena = cache.directArena;
+
+ ByteBuf buf;
+ if (directArena != null) {
+ buf = directArena.allocate(cache, initialCapacity, maxCapacity);
+ } else {
+ if (PlatformDependent.hasUnsafe()) {
+ buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
+ } else {
+ buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
+ }
+ }
+
+ return toLeakAwareBuffer(buf);
+ }
+
+ @Override
+ public boolean isDirectBufferPooled() {
+ return directArenas != null;
+ }
+
+ /**
+ * Returns {@code true} if the calling {@link Thread} has a {@link ThreadLocal} cache for the allocated
+ * buffers.
+ */
+ @Deprecated
+ public boolean hasThreadLocalCache() {
+ return threadCache.isSet();
+ }
+
+ /**
+ * Free all cached buffers for the calling {@link Thread}.
+ */
+ @Deprecated
+ public void freeThreadLocalCache() {
+ threadCache.remove();
+ }
+
+ final class PoolThreadLocalCache extends FastThreadLocal {
+ private final AtomicInteger index = new AtomicInteger();
+
+ @Override
+ protected PoolThreadCache initialValue() {
+ final int idx = index.getAndIncrement();
+ final PoolArena heapArena;
+ final PoolArena directArena;
+
+ if (heapArenas != null) {
+ heapArena = heapArenas[Math.abs(idx % heapArenas.length)];
+ } else {
+ heapArena = null;
+ }
+
+ if (directArenas != null) {
+ directArena = directArenas[Math.abs(idx % directArenas.length)];
+ } else {
+ directArena = null;
+ }
+
+ return new PoolThreadCache(
+ heapArena, directArena, tinyCacheSize, smallCacheSize, normalCacheSize,
+ DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
+ }
+
+ @Override
+ protected void onRemoval(PoolThreadCache value) {
+ value.free();
+ }
+ }
+
+// Too noisy at the moment.
+//
+// public String toString() {
+// StringBuilder buf = new StringBuilder();
+// buf.append(heapArenas.length);
+// buf.append(" heap arena(s):");
+// buf.append(StringUtil.NEWLINE);
+// for (PoolArena a: heapArenas) {
+// buf.append(a);
+// }
+// buf.append(directArenas.length);
+// buf.append(" direct arena(s):");
+// buf.append(StringUtil.NEWLINE);
+// for (PoolArena a: directArenas) {
+// buf.append(a);
+// }
+// return buf.toString();
+// }
+}
diff --git a/common/src/common/net/buffer/PooledDirectByteBuf.java b/common/src/common/net/buffer/PooledDirectByteBuf.java
new file mode 100644
index 0000000..74b7e55
--- /dev/null
+++ b/common/src/common/net/buffer/PooledDirectByteBuf.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.Recycler;
+
+final class PooledDirectByteBuf extends PooledByteBuf {
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected PooledDirectByteBuf newObject(Handle handle) {
+ return new PooledDirectByteBuf(handle, 0);
+ }
+ };
+
+ static PooledDirectByteBuf newInstance(int maxCapacity) {
+ PooledDirectByteBuf buf = RECYCLER.get();
+ buf.setRefCnt(1);
+ buf.maxCapacity(maxCapacity);
+ return buf;
+ }
+
+ private PooledDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
+ super(recyclerHandle, maxCapacity);
+ }
+
+ @Override
+ protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) {
+ return memory.duplicate();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return memory.get(idx(index));
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return memory.getShort(idx(index));
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ index = idx(index);
+ return (memory.get(index) & 0xff) << 16 | (memory.get(index + 1) & 0xff) << 8 | memory.get(index + 2) & 0xff;
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return memory.getInt(idx(index));
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return memory.getLong(idx(index));
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (dst.hasArray()) {
+ getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else if (dst.nioBufferCount() > 0) {
+ for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
+ int bbLen = bb.remaining();
+ getBytes(index, bb);
+ index += bbLen;
+ }
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ getBytes(index, dst, dstIndex, length, false);
+ return this;
+ }
+
+ private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ tmpBuf.get(dst, dstIndex, length);
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, dstIndex, length, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ getBytes(index, dst, false);
+ return this;
+ }
+
+ private void getBytes(int index, ByteBuffer dst, boolean internal) {
+ checkIndex(index);
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ int length = dst.remaining();
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ getBytes(index, out, length, false);
+ return this;
+ }
+
+ private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return;
+ }
+
+ byte[] tmp = new byte[length];
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ tmpBuf.clear().position(idx(index));
+ tmpBuf.get(tmp);
+ out.write(tmp);
+ }
+
+ @Override
+ public ByteBuf readBytes(OutputStream out, int length) throws IOException {
+ checkReadableBytes(length);
+ getBytes(readerIndex, out, length, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return 0;
+ }
+
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ return out.write(tmpBuf);
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ memory.put(idx(index), (byte) value);
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ memory.putShort(idx(index), (short) value);
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ index = idx(index);
+ memory.put(index, (byte) (value >>> 16));
+ memory.put(index + 1, (byte) (value >>> 8));
+ memory.put(index + 2, (byte) value);
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ memory.putInt(idx(index), value);
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ memory.putLong(idx(index), value);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.capacity());
+ if (src.hasArray()) {
+ setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
+ } else if (src.nioBufferCount() > 0) {
+ for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) {
+ int bbLen = bb.remaining();
+ setBytes(index, bb);
+ index += bbLen;
+ }
+ } else {
+ src.getBytes(srcIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.length);
+ ByteBuffer tmpBuf = internalNioBuffer();
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ tmpBuf.put(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ checkIndex(index, src.remaining());
+ ByteBuffer tmpBuf = internalNioBuffer();
+ if (src == tmpBuf) {
+ src = src.duplicate();
+ }
+
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + src.remaining());
+ tmpBuf.put(src);
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ byte[] tmp = new byte[length];
+ int readBytes = in.read(tmp);
+ if (readBytes <= 0) {
+ return readBytes;
+ }
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(idx(index));
+ tmpBuf.put(tmp, 0, readBytes);
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ checkIndex(index, length);
+ ByteBuffer tmpBuf = internalNioBuffer();
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ try {
+ return in.read(tmpBuf);
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf copy = alloc().directBuffer(length, maxCapacity());
+ copy.writeBytes(this, index, length);
+ return copy;
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ @Override
+ public boolean hasArray() {
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public int arrayOffset() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected Recycler> recycler() {
+ return RECYCLER;
+ }
+}
diff --git a/common/src/common/net/buffer/PooledHeapByteBuf.java b/common/src/common/net/buffer/PooledHeapByteBuf.java
new file mode 100644
index 0000000..33e0ec6
--- /dev/null
+++ b/common/src/common/net/buffer/PooledHeapByteBuf.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file tothe License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.Recycler;
+import common.net.util.internal.PlatformDependent;
+
+final class PooledHeapByteBuf extends PooledByteBuf {
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected PooledHeapByteBuf newObject(Handle handle) {
+ return new PooledHeapByteBuf(handle, 0);
+ }
+ };
+
+ static PooledHeapByteBuf newInstance(int maxCapacity) {
+ PooledHeapByteBuf buf = RECYCLER.get();
+ buf.setRefCnt(1);
+ buf.maxCapacity(maxCapacity);
+ return buf;
+ }
+
+ private PooledHeapByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
+ super(recyclerHandle, maxCapacity);
+ }
+
+ @Override
+ public boolean isDirect() {
+ return false;
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return memory[idx(index)];
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ index = idx(index);
+ return (short) (memory[index] << 8 | memory[index + 1] & 0xFF);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ index = idx(index);
+ return (memory[index] & 0xff) << 16 |
+ (memory[index + 1] & 0xff) << 8 |
+ memory[index + 2] & 0xff;
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ index = idx(index);
+ return (memory[index] & 0xff) << 24 |
+ (memory[index + 1] & 0xff) << 16 |
+ (memory[index + 2] & 0xff) << 8 |
+ memory[index + 3] & 0xff;
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ index = idx(index);
+ return ((long) memory[index] & 0xff) << 56 |
+ ((long) memory[index + 1] & 0xff) << 48 |
+ ((long) memory[index + 2] & 0xff) << 40 |
+ ((long) memory[index + 3] & 0xff) << 32 |
+ ((long) memory[index + 4] & 0xff) << 24 |
+ ((long) memory[index + 5] & 0xff) << 16 |
+ ((long) memory[index + 6] & 0xff) << 8 |
+ (long) memory[index + 7] & 0xff;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (dst.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(memory, idx(index), dst.memoryAddress() + dstIndex, length);
+ } else if (dst.hasArray()) {
+ getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else {
+ dst.setBytes(dstIndex, memory, idx(index), length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+ System.arraycopy(memory, idx(index), dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ checkIndex(index);
+ dst.put(memory, idx(index), Math.min(capacity() - index, dst.remaining()));
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ checkIndex(index, length);
+ out.write(memory, idx(index), length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ checkIndex(index, length);
+ index = idx(index);
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = ByteBuffer.wrap(memory);
+ }
+ return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ memory[idx(index)] = (byte) value;
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ index = idx(index);
+ memory[index] = (byte) (value >>> 8);
+ memory[index + 1] = (byte) value;
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ index = idx(index);
+ memory[index] = (byte) (value >>> 16);
+ memory[index + 1] = (byte) (value >>> 8);
+ memory[index + 2] = (byte) value;
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ index = idx(index);
+ memory[index] = (byte) (value >>> 24);
+ memory[index + 1] = (byte) (value >>> 16);
+ memory[index + 2] = (byte) (value >>> 8);
+ memory[index + 3] = (byte) value;
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ index = idx(index);
+ memory[index] = (byte) (value >>> 56);
+ memory[index + 1] = (byte) (value >>> 48);
+ memory[index + 2] = (byte) (value >>> 40);
+ memory[index + 3] = (byte) (value >>> 32);
+ memory[index + 4] = (byte) (value >>> 24);
+ memory[index + 5] = (byte) (value >>> 16);
+ memory[index + 6] = (byte) (value >>> 8);
+ memory[index + 7] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.capacity());
+ if (src.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, memory, idx(index), length);
+ } else if (src.hasArray()) {
+ setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
+ } else {
+ src.getBytes(srcIndex, memory, idx(index), length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.length);
+ System.arraycopy(src, srcIndex, memory, idx(index), length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ int length = src.remaining();
+ checkIndex(index, length);
+ src.get(memory, idx(index), length);
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ return in.read(memory, idx(index), length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ checkIndex(index, length);
+ index = idx(index);
+ try {
+ return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length));
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf copy = alloc().heapBuffer(length, maxCapacity());
+ copy.writeBytes(memory, idx(index), length);
+ return copy;
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ ByteBuffer buf = ByteBuffer.wrap(memory, index, length);
+ return buf.slice();
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ @Override
+ public boolean hasArray() {
+ return true;
+ }
+
+ @Override
+ public byte[] array() {
+ return memory;
+ }
+
+ @Override
+ public int arrayOffset() {
+ return offset;
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected ByteBuffer newInternalNioBuffer(byte[] memory) {
+ return ByteBuffer.wrap(memory);
+ }
+
+ @Override
+ protected Recycler> recycler() {
+ return RECYCLER;
+ }
+}
diff --git a/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java b/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java
new file mode 100644
index 0000000..e28103f
--- /dev/null
+++ b/common/src/common/net/buffer/PooledUnsafeDirectByteBuf.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.Recycler;
+import common.net.util.internal.PlatformDependent;
+
+final class PooledUnsafeDirectByteBuf extends PooledByteBuf {
+
+ private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected PooledUnsafeDirectByteBuf newObject(Handle handle) {
+ return new PooledUnsafeDirectByteBuf(handle, 0);
+ }
+ };
+
+ static PooledUnsafeDirectByteBuf newInstance(int maxCapacity) {
+ PooledUnsafeDirectByteBuf buf = RECYCLER.get();
+ buf.setRefCnt(1);
+ buf.maxCapacity(maxCapacity);
+ return buf;
+ }
+
+ private long memoryAddress;
+
+ private PooledUnsafeDirectByteBuf(Recycler.Handle recyclerHandle, int maxCapacity) {
+ super(recyclerHandle, maxCapacity);
+ }
+
+ @Override
+ void init(PoolChunk chunk, long handle, int offset, int length, int maxLength) {
+ super.init(chunk, handle, offset, length, maxLength);
+ initMemoryAddress();
+ }
+
+ @Override
+ void initUnpooled(PoolChunk chunk, int length) {
+ super.initUnpooled(chunk, length);
+ initMemoryAddress();
+ }
+
+ private void initMemoryAddress() {
+ memoryAddress = PlatformDependent.directBufferAddress(memory) + offset;
+ }
+
+ @Override
+ protected ByteBuffer newInternalNioBuffer(ByteBuffer memory) {
+ return memory.duplicate();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return PlatformDependent.getByte(addr(index));
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ short v = PlatformDependent.getShort(addr(index));
+ return NATIVE_ORDER? v : Short.reverseBytes(v);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ long addr = addr(index);
+ return (PlatformDependent.getByte(addr) & 0xff) << 16 |
+ (PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
+ PlatformDependent.getByte(addr + 2) & 0xff;
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ int v = PlatformDependent.getInt(addr(index));
+ return NATIVE_ORDER? v : Integer.reverseBytes(v);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ long v = PlatformDependent.getLong(addr(index));
+ return NATIVE_ORDER? v : Long.reverseBytes(v);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
+ throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
+ }
+
+ if (length != 0) {
+ if (dst.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
+ } else if (dst.hasArray()) {
+ PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.length - length) {
+ throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
+ }
+ if (length != 0) {
+ PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ getBytes(index, dst, false);
+ return this;
+ }
+
+ private void getBytes(int index, ByteBuffer dst, boolean internal) {
+ checkIndex(index);
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ int length = dst.remaining();
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ checkIndex(index, length);
+ if (length != 0) {
+ byte[] tmp = new byte[length];
+ PlatformDependent.copyMemory(addr(index), tmp, 0, length);
+ out.write(tmp);
+ }
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ checkIndex(index, length);
+ if (length == 0) {
+ return 0;
+ }
+
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = memory.duplicate();
+ }
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ return out.write(tmpBuf);
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length)
+ throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ PlatformDependent.putByte(addr(index), (byte) value);
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value));
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ long addr = addr(index);
+ PlatformDependent.putByte(addr, (byte) (value >>> 16));
+ PlatformDependent.putByte(addr + 1, (byte) (value >>> 8));
+ PlatformDependent.putByte(addr + 2, (byte) value);
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value));
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value));
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkIndex(index, length);
+ if (src == null) {
+ throw new NullPointerException("src");
+ }
+ if (srcIndex < 0 || srcIndex > src.capacity() - length) {
+ throw new IndexOutOfBoundsException("srcIndex: " + srcIndex);
+ }
+
+ if (length != 0) {
+ if (src.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length);
+ } else if (src.hasArray()) {
+ PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length);
+ } else {
+ src.getBytes(srcIndex, this, index, length);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkIndex(index, length);
+ if (length != 0) {
+ PlatformDependent.copyMemory(src, srcIndex, addr(index), length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ checkIndex(index, src.remaining());
+ ByteBuffer tmpBuf = internalNioBuffer();
+ if (src == tmpBuf) {
+ src = src.duplicate();
+ }
+
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + src.remaining());
+ tmpBuf.put(src);
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ byte[] tmp = new byte[length];
+ int readBytes = in.read(tmp);
+ if (readBytes > 0) {
+ PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes);
+ }
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ checkIndex(index, length);
+ ByteBuffer tmpBuf = internalNioBuffer();
+ index = idx(index);
+ tmpBuf.clear().position(index).limit(index + length);
+ try {
+ return in.read(tmpBuf);
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf copy = alloc().directBuffer(length, maxCapacity());
+ if (length != 0) {
+ if (copy.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
+ copy.setIndex(0, length);
+ } else {
+ copy.writeBytes(this, index, length);
+ }
+ }
+ return copy;
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ return ((ByteBuffer) memory.duplicate().position(index).limit(index + length)).slice();
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ index = idx(index);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ @Override
+ public boolean hasArray() {
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public int arrayOffset() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return true;
+ }
+
+ @Override
+ public long memoryAddress() {
+ return memoryAddress;
+ }
+
+ private long addr(int index) {
+ return memoryAddress + index;
+ }
+
+ @Override
+ protected Recycler> recycler() {
+ return RECYCLER;
+ }
+
+ @Override
+ protected SwappedByteBuf newSwappedByteBuf() {
+ return new UnsafeDirectSwappedByteBuf(this);
+ }
+}
diff --git a/common/src/common/net/buffer/ReadOnlyByteBuf.java b/common/src/common/net/buffer/ReadOnlyByteBuf.java
new file mode 100644
index 0000000..17a80ac
--- /dev/null
+++ b/common/src/common/net/buffer/ReadOnlyByteBuf.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ReadOnlyBufferException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+/**
+ * A derived buffer which forbids any write requests to its parent. It is
+ * recommended to use {@link Unpooled#unmodifiableBuffer(ByteBuf)}
+ * instead of calling the constructor explicitly.
+ */
+public class ReadOnlyByteBuf extends AbstractDerivedByteBuf {
+
+ private final ByteBuf buffer;
+
+ public ReadOnlyByteBuf(ByteBuf buffer) {
+ super(buffer.maxCapacity());
+
+ if (buffer instanceof ReadOnlyByteBuf || buffer instanceof DuplicatedByteBuf) {
+ this.buffer = buffer.unwrap();
+ } else {
+ this.buffer = buffer;
+ }
+ setIndex(buffer.readerIndex(), buffer.writerIndex());
+ }
+
+ @Override
+ public boolean isWritable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWritable(int numBytes) {
+ return false;
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return buffer;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return buffer.alloc();
+ }
+
+ @Override
+ public ByteOrder order() {
+ return buffer.order();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buffer.isDirect();
+ }
+
+ @Override
+ public boolean hasArray() {
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int arrayOffset() {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf discardReadBytes() {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length)
+ throws IOException {
+ return buffer.getBytes(index, out, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length)
+ throws IOException {
+ buffer.getBytes(index, out, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ buffer.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ buffer.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ buffer.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return new ReadOnlyByteBuf(this);
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ return buffer.copy(index, length);
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return Unpooled.unmodifiableBuffer(buffer.slice(index, length));
+ }
+
+ @Override
+ public byte getByte(int index) {
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return buffer.getByte(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ return _getShort(index);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return buffer.getShort(index);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ return _getUnsignedMedium(index);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return buffer.getUnsignedMedium(index);
+ }
+
+ @Override
+ public int getInt(int index) {
+ return _getInt(index);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return buffer.getInt(index);
+ }
+
+ @Override
+ public long getLong(int index) {
+ return _getLong(index);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return buffer.getLong(index);
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return buffer.nioBufferCount();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ return buffer.nioBuffer(index, length).asReadOnlyBuffer();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return buffer.nioBuffers(index, length);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ return nioBuffer(index, length);
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ return buffer.forEachByte(index, length, processor);
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ return buffer.forEachByteDesc(index, length, processor);
+ }
+
+ @Override
+ public int capacity() {
+ return buffer.capacity();
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ throw new ReadOnlyBufferException();
+ }
+}
diff --git a/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java b/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java
new file mode 100644
index 0000000..4d158b7
--- /dev/null
+++ b/common/src/common/net/buffer/ReadOnlyByteBufferBuf.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ReadOnlyBufferException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.internal.StringUtil;
+
+
+/**
+ * Read-only ByteBuf which wraps a read-only ByteBuffer.
+ */
+class ReadOnlyByteBufferBuf extends AbstractReferenceCountedByteBuf {
+
+ protected final ByteBuffer buffer;
+ private final ByteBufAllocator allocator;
+ private ByteBuffer tmpNioBuf;
+
+ ReadOnlyByteBufferBuf(ByteBufAllocator allocator, ByteBuffer buffer) {
+ super(buffer.remaining());
+ if (!buffer.isReadOnly()) {
+ throw new IllegalArgumentException("must be a readonly buffer: " + StringUtil.simpleClassName(buffer));
+ }
+
+ this.allocator = allocator;
+ this.buffer = buffer.slice().order(ByteOrder.BIG_ENDIAN);
+ writerIndex(this.buffer.limit());
+ }
+
+ @Override
+ protected void deallocate() { }
+
+ @Override
+ public byte getByte(int index) {
+ ensureAccessible();
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return buffer.get(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ ensureAccessible();
+ return _getShort(index);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return buffer.getShort(index);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ ensureAccessible();
+ return _getUnsignedMedium(index);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff;
+ }
+
+ @Override
+ public int getInt(int index) {
+ ensureAccessible();
+ return _getInt(index);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return buffer.getInt(index);
+ }
+
+ @Override
+ public long getLong(int index) {
+ ensureAccessible();
+ return _getLong(index);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return buffer.getLong(index);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (dst.hasArray()) {
+ getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else if (dst.nioBufferCount() > 0) {
+ for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
+ int bbLen = bb.remaining();
+ getBytes(index, bb);
+ index += bbLen;
+ }
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+
+ if (dstIndex < 0 || dstIndex > dst.length - length) {
+ throw new IndexOutOfBoundsException(String.format(
+ "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
+ }
+
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + length);
+ tmpBuf.get(dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ checkIndex(index);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int capacity() {
+ return maxCapacity();
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return allocator;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buffer.isDirect();
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ ensureAccessible();
+ if (length == 0) {
+ return this;
+ }
+
+ if (buffer.hasArray()) {
+ out.write(buffer.array(), index + buffer.arrayOffset(), length);
+ } else {
+ byte[] tmp = new byte[length];
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index);
+ tmpBuf.get(tmp);
+ out.write(tmp);
+ }
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ ensureAccessible();
+ if (length == 0) {
+ return 0;
+ }
+
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + length);
+ return out.write(tmpBuf);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ throw new ReadOnlyBufferException();
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ throw new ReadOnlyBufferException();
+ }
+
+ protected final ByteBuffer internalNioBuffer() {
+ ByteBuffer tmpNioBuf = this.tmpNioBuf;
+ if (tmpNioBuf == null) {
+ this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
+ }
+ return tmpNioBuf;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ ensureAccessible();
+ ByteBuffer src;
+ try {
+ src = (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ } catch (IllegalArgumentException ignored) {
+ throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length));
+ }
+
+ ByteBuffer dst = ByteBuffer.allocateDirect(length);
+ dst.put(src);
+ dst.order(order());
+ dst.clear();
+ return new UnpooledDirectByteBuf(alloc(), dst, maxCapacity());
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ return (ByteBuffer) buffer.duplicate().position(index).limit(index + length);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ ensureAccessible();
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ @Override
+ public boolean hasArray() {
+ return buffer.hasArray();
+ }
+
+ @Override
+ public byte[] array() {
+ return buffer.array();
+ }
+
+ @Override
+ public int arrayOffset() {
+ return buffer.arrayOffset();
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java b/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java
new file mode 100644
index 0000000..6b8981c
--- /dev/null
+++ b/common/src/common/net/buffer/ReadOnlyUnsafeDirectByteBuf.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import common.net.util.internal.PlatformDependent;
+
+
+
+/**
+ * Read-only ByteBuf which wraps a read-only direct ByteBuffer and use unsafe for best performance.
+ */
+final class ReadOnlyUnsafeDirectByteBuf extends ReadOnlyByteBufferBuf {
+ private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+ private final long memoryAddress;
+
+ ReadOnlyUnsafeDirectByteBuf(ByteBufAllocator allocator, ByteBuffer buffer) {
+ super(allocator, buffer);
+ memoryAddress = PlatformDependent.directBufferAddress(buffer);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return PlatformDependent.getByte(addr(index));
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ short v = PlatformDependent.getShort(addr(index));
+ return NATIVE_ORDER? v : Short.reverseBytes(v);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ long addr = addr(index);
+ return (PlatformDependent.getByte(addr) & 0xff) << 16 |
+ (PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
+ PlatformDependent.getByte(addr + 2) & 0xff;
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ int v = PlatformDependent.getInt(addr(index));
+ return NATIVE_ORDER? v : Integer.reverseBytes(v);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ long v = PlatformDependent.getLong(addr(index));
+ return NATIVE_ORDER? v : Long.reverseBytes(v);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
+ throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
+ }
+
+ if (dst.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
+ } else if (dst.hasArray()) {
+ PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.length - length) {
+ throw new IndexOutOfBoundsException(String.format(
+ "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
+ }
+
+ if (length != 0) {
+ PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ checkIndex(index);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ return this;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf copy = alloc().directBuffer(length, maxCapacity());
+ if (length != 0) {
+ if (copy.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
+ copy.setIndex(0, length);
+ } else {
+ copy.writeBytes(this, index, length);
+ }
+ }
+ return copy;
+ }
+
+ private long addr(int index) {
+ return memoryAddress + index;
+ }
+}
diff --git a/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java b/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java
new file mode 100644
index 0000000..688af60
--- /dev/null
+++ b/common/src/common/net/buffer/SimpleLeakAwareByteBuf.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.nio.ByteOrder;
+
+import common.net.util.ResourceLeak;
+
+final class SimpleLeakAwareByteBuf extends WrappedByteBuf {
+
+ private final ResourceLeak leak;
+
+ SimpleLeakAwareByteBuf(ByteBuf buf, ResourceLeak leak) {
+ super(buf);
+ this.leak = leak;
+ }
+
+ @Override
+ public boolean release() {
+ boolean deallocated = super.release();
+ if (deallocated) {
+ leak.close();
+ }
+ return deallocated;
+ }
+
+ @Override
+ public boolean release(int decrement) {
+ boolean deallocated = super.release(decrement);
+ if (deallocated) {
+ leak.close();
+ }
+ return deallocated;
+ }
+
+ @Override
+ public ByteBuf order(ByteOrder endianness) {
+ leak.record();
+ if (order() == endianness) {
+ return this;
+ } else {
+ return new SimpleLeakAwareByteBuf(super.order(endianness), leak);
+ }
+ }
+
+ @Override
+ public ByteBuf slice() {
+ return new SimpleLeakAwareByteBuf(super.slice(), leak);
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return new SimpleLeakAwareByteBuf(super.slice(index, length), leak);
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return new SimpleLeakAwareByteBuf(super.duplicate(), leak);
+ }
+
+ @Override
+ public ByteBuf readSlice(int length) {
+ return new SimpleLeakAwareByteBuf(super.readSlice(length), leak);
+ }
+}
diff --git a/common/src/common/net/buffer/SlicedByteBuf.java b/common/src/common/net/buffer/SlicedByteBuf.java
new file mode 100644
index 0000000..0e7a208
--- /dev/null
+++ b/common/src/common/net/buffer/SlicedByteBuf.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+
+/**
+ * A derived buffer which exposes its parent's sub-region only. It is
+ * recommended to use {@link ByteBuf#slice()} and
+ * {@link ByteBuf#slice(int, int)} instead of calling the constructor
+ * explicitly.
+ */
+public class SlicedByteBuf extends AbstractDerivedByteBuf {
+
+ private final ByteBuf buffer;
+ private final int adjustment;
+ private final int length;
+
+ public SlicedByteBuf(ByteBuf buffer, int index, int length) {
+ super(length);
+ if (index < 0 || index > buffer.capacity() - length) {
+ throw new IndexOutOfBoundsException(buffer + ".slice(" + index + ", " + length + ')');
+ }
+
+ if (buffer instanceof SlicedByteBuf) {
+ this.buffer = ((SlicedByteBuf) buffer).buffer;
+ adjustment = ((SlicedByteBuf) buffer).adjustment + index;
+ } else if (buffer instanceof DuplicatedByteBuf) {
+ this.buffer = buffer.unwrap();
+ adjustment = index;
+ } else {
+ this.buffer = buffer;
+ adjustment = index;
+ }
+ this.length = length;
+
+ writerIndex(length);
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return buffer;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return buffer.alloc();
+ }
+
+ @Override
+ public ByteOrder order() {
+ return buffer.order();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buffer.isDirect();
+ }
+
+ @Override
+ public int capacity() {
+ return length;
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ throw new UnsupportedOperationException("sliced buffer");
+ }
+
+ @Override
+ public boolean hasArray() {
+ return buffer.hasArray();
+ }
+
+ @Override
+ public byte[] array() {
+ return buffer.array();
+ }
+
+ @Override
+ public int arrayOffset() {
+ return buffer.arrayOffset() + adjustment;
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return buffer.hasMemoryAddress();
+ }
+
+ @Override
+ public long memoryAddress() {
+ return buffer.memoryAddress() + adjustment;
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return buffer.getByte(index + adjustment);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return buffer.getShort(index + adjustment);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return buffer.getUnsignedMedium(index + adjustment);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return buffer.getInt(index + adjustment);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return buffer.getLong(index + adjustment);
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ ByteBuf duplicate = buffer.slice(adjustment, length);
+ duplicate.setIndex(readerIndex(), writerIndex());
+ return duplicate;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ return buffer.copy(index + adjustment, length);
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ checkIndex(index, length);
+ if (length == 0) {
+ return Unpooled.EMPTY_BUFFER;
+ }
+ return buffer.slice(index + adjustment, length);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ buffer.getBytes(index + adjustment, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ buffer.getBytes(index + adjustment, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ checkIndex(index, dst.remaining());
+ buffer.getBytes(index + adjustment, dst);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ buffer.setByte(index + adjustment, value);
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ buffer.setShort(index + adjustment, value);
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ buffer.setMedium(index + adjustment, value);
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ buffer.setInt(index + adjustment, value);
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ buffer.setLong(index + adjustment, value);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkIndex(index, length);
+ buffer.setBytes(index + adjustment, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkIndex(index, length);
+ buffer.setBytes(index + adjustment, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ checkIndex(index, src.remaining());
+ buffer.setBytes(index + adjustment, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ checkIndex(index, length);
+ buffer.getBytes(index + adjustment, out, length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ checkIndex(index, length);
+ return buffer.getBytes(index + adjustment, out, length);
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ return buffer.setBytes(index + adjustment, in, length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ checkIndex(index, length);
+ return buffer.setBytes(index + adjustment, in, length);
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return buffer.nioBufferCount();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return buffer.nioBuffer(index + adjustment, length);
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ checkIndex(index, length);
+ return buffer.nioBuffers(index + adjustment, length);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return nioBuffer(index, length);
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ int ret = buffer.forEachByte(index + adjustment, length, processor);
+ if (ret >= adjustment) {
+ return ret - adjustment;
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ int ret = buffer.forEachByteDesc(index + adjustment, length, processor);
+ if (ret >= adjustment) {
+ return ret - adjustment;
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/common/src/common/net/buffer/SwappedByteBuf.java b/common/src/common/net/buffer/SwappedByteBuf.java
new file mode 100644
index 0000000..88b4478
--- /dev/null
+++ b/common/src/common/net/buffer/SwappedByteBuf.java
@@ -0,0 +1,852 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+import java.nio.charset.Charset;
+
+/**
+ * Wrapper which swap the {@link ByteOrder} of a {@link ByteBuf}.
+ */
+public class SwappedByteBuf extends ByteBuf {
+
+ private final ByteBuf buf;
+ private final ByteOrder order;
+
+ public SwappedByteBuf(ByteBuf buf) {
+ if (buf == null) {
+ throw new NullPointerException("buf");
+ }
+ this.buf = buf;
+ if (buf.order() == ByteOrder.BIG_ENDIAN) {
+ order = ByteOrder.LITTLE_ENDIAN;
+ } else {
+ order = ByteOrder.BIG_ENDIAN;
+ }
+ }
+
+ @Override
+ public ByteOrder order() {
+ return order;
+ }
+
+ @Override
+ public ByteBuf order(ByteOrder endianness) {
+ if (endianness == null) {
+ throw new NullPointerException("endianness");
+ }
+ if (endianness == order) {
+ return this;
+ }
+ return buf;
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return buf.unwrap();
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return buf.alloc();
+ }
+
+ @Override
+ public int capacity() {
+ return buf.capacity();
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ buf.capacity(newCapacity);
+ return this;
+ }
+
+ @Override
+ public int maxCapacity() {
+ return buf.maxCapacity();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buf.isDirect();
+ }
+
+ @Override
+ public int readerIndex() {
+ return buf.readerIndex();
+ }
+
+ @Override
+ public ByteBuf readerIndex(int readerIndex) {
+ buf.readerIndex(readerIndex);
+ return this;
+ }
+
+ @Override
+ public int writerIndex() {
+ return buf.writerIndex();
+ }
+
+ @Override
+ public ByteBuf writerIndex(int writerIndex) {
+ buf.writerIndex(writerIndex);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setIndex(int readerIndex, int writerIndex) {
+ buf.setIndex(readerIndex, writerIndex);
+ return this;
+ }
+
+ @Override
+ public int readableBytes() {
+ return buf.readableBytes();
+ }
+
+ @Override
+ public int writableBytes() {
+ return buf.writableBytes();
+ }
+
+ @Override
+ public int maxWritableBytes() {
+ return buf.maxWritableBytes();
+ }
+
+ @Override
+ public boolean isReadable() {
+ return buf.isReadable();
+ }
+
+ @Override
+ public boolean isReadable(int size) {
+ return buf.isReadable(size);
+ }
+
+ @Override
+ public boolean isWritable() {
+ return buf.isWritable();
+ }
+
+ @Override
+ public boolean isWritable(int size) {
+ return buf.isWritable(size);
+ }
+
+ @Override
+ public ByteBuf clear() {
+ buf.clear();
+ return this;
+ }
+
+ @Override
+ public ByteBuf markReaderIndex() {
+ buf.markReaderIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetReaderIndex() {
+ buf.resetReaderIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf markWriterIndex() {
+ buf.markWriterIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetWriterIndex() {
+ buf.resetWriterIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardReadBytes() {
+ buf.discardReadBytes();
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardSomeReadBytes() {
+ buf.discardSomeReadBytes();
+ return this;
+ }
+
+ @Override
+ public ByteBuf ensureWritable(int writableBytes) {
+ buf.ensureWritable(writableBytes);
+ return this;
+ }
+
+ @Override
+ public int ensureWritable(int minWritableBytes, boolean force) {
+ return buf.ensureWritable(minWritableBytes, force);
+ }
+
+ @Override
+ public boolean getBoolean(int index) {
+ return buf.getBoolean(index);
+ }
+
+ @Override
+ public byte getByte(int index) {
+ return buf.getByte(index);
+ }
+
+ @Override
+ public short getUnsignedByte(int index) {
+ return buf.getUnsignedByte(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ return ByteBufUtil.swapShort(buf.getShort(index));
+ }
+
+ @Override
+ public int getUnsignedShort(int index) {
+ return getShort(index) & 0xFFFF;
+ }
+
+ @Override
+ public int getMedium(int index) {
+ return ByteBufUtil.swapMedium(buf.getMedium(index));
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ return getMedium(index) & 0xFFFFFF;
+ }
+
+ @Override
+ public int getInt(int index) {
+ return ByteBufUtil.swapInt(buf.getInt(index));
+ }
+
+ @Override
+ public long getUnsignedInt(int index) {
+ return getInt(index) & 0xFFFFFFFFL;
+ }
+
+ @Override
+ public long getLong(int index) {
+ return ByteBufUtil.swapLong(buf.getLong(index));
+ }
+
+ @Override
+ public char getChar(int index) {
+ return (char) getShort(index);
+ }
+
+ @Override
+ public float getFloat(int index) {
+ return Float.intBitsToFloat(getInt(index));
+ }
+
+ @Override
+ public double getDouble(int index) {
+ return Double.longBitsToDouble(getLong(index));
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int length) {
+ buf.getBytes(index, dst, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ buf.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ buf.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ buf.getBytes(index, out, length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return buf.getBytes(index, out, length);
+ }
+
+ @Override
+ public ByteBuf setBoolean(int index, boolean value) {
+ buf.setBoolean(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ buf.setByte(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ buf.setShort(index, ByteBufUtil.swapShort((short) value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ buf.setMedium(index, ByteBufUtil.swapMedium(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ buf.setInt(index, ByteBufUtil.swapInt(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ buf.setLong(index, ByteBufUtil.swapLong(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setChar(int index, int value) {
+ setShort(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setFloat(int index, float value) {
+ setInt(index, Float.floatToRawIntBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setDouble(int index, double value) {
+ setLong(index, Double.doubleToRawLongBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int length) {
+ buf.setBytes(index, src, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ buf.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ buf.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ return buf.setBytes(index, in, length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ return buf.setBytes(index, in, length);
+ }
+
+ @Override
+ public ByteBuf setZero(int index, int length) {
+ buf.setZero(index, length);
+ return this;
+ }
+
+ @Override
+ public boolean readBoolean() {
+ return buf.readBoolean();
+ }
+
+ @Override
+ public byte readByte() {
+ return buf.readByte();
+ }
+
+ @Override
+ public short readUnsignedByte() {
+ return buf.readUnsignedByte();
+ }
+
+ @Override
+ public short readShort() {
+ return ByteBufUtil.swapShort(buf.readShort());
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ return readShort() & 0xFFFF;
+ }
+
+ @Override
+ public int readMedium() {
+ return ByteBufUtil.swapMedium(buf.readMedium());
+ }
+
+ @Override
+ public int readUnsignedMedium() {
+ return readMedium() & 0xFFFFFF;
+ }
+
+ @Override
+ public int readInt() {
+ return ByteBufUtil.swapInt(buf.readInt());
+ }
+
+ @Override
+ public long readUnsignedInt() {
+ return readInt() & 0xFFFFFFFFL;
+ }
+
+ @Override
+ public long readLong() {
+ return ByteBufUtil.swapLong(buf.readLong());
+ }
+
+ @Override
+ public char readChar() {
+ return (char) readShort();
+ }
+
+ @Override
+ public float readFloat() {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ @Override
+ public double readDouble() {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ @Override
+ public ByteBuf readBytes(int length) {
+ return buf.readBytes(length).order(order());
+ }
+
+ @Override
+ public ByteBuf readSlice(int length) {
+ return buf.readSlice(length).order(order);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int length) {
+ buf.readBytes(dst, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
+ buf.readBytes(dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ buf.readBytes(dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(OutputStream out, int length) throws IOException {
+ buf.readBytes(out, length);
+ return this;
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ return buf.readBytes(out, length);
+ }
+
+ @Override
+ public ByteBuf skipBytes(int length) {
+ buf.skipBytes(length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBoolean(boolean value) {
+ buf.writeBoolean(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeByte(int value) {
+ buf.writeByte(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeShort(int value) {
+ buf.writeShort(ByteBufUtil.swapShort((short) value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeMedium(int value) {
+ buf.writeMedium(ByteBufUtil.swapMedium(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeInt(int value) {
+ buf.writeInt(ByteBufUtil.swapInt(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeLong(long value) {
+ buf.writeLong(ByteBufUtil.swapLong(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeChar(int value) {
+ writeShort(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeFloat(float value) {
+ writeInt(Float.floatToRawIntBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeDouble(double value) {
+ writeLong(Double.doubleToRawLongBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int length) {
+ buf.writeBytes(src, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
+ buf.writeBytes(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
+ buf.writeBytes(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuffer src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public int writeBytes(InputStream in, int length) throws IOException {
+ return buf.writeBytes(in, length);
+ }
+
+ @Override
+ public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
+ return buf.writeBytes(in, length);
+ }
+
+ @Override
+ public ByteBuf writeZero(int length) {
+ buf.writeZero(length);
+ return this;
+ }
+
+ @Override
+ public int indexOf(int fromIndex, int toIndex, byte value) {
+ return buf.indexOf(fromIndex, toIndex, value);
+ }
+
+ @Override
+ public int bytesBefore(byte value) {
+ return buf.bytesBefore(value);
+ }
+
+ @Override
+ public int bytesBefore(int length, byte value) {
+ return buf.bytesBefore(length, value);
+ }
+
+ @Override
+ public int bytesBefore(int index, int length, byte value) {
+ return buf.bytesBefore(index, length, value);
+ }
+
+ @Override
+ public int forEachByte(ByteBufProcessor processor) {
+ return buf.forEachByte(processor);
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ return buf.forEachByte(index, length, processor);
+ }
+
+ @Override
+ public int forEachByteDesc(ByteBufProcessor processor) {
+ return buf.forEachByteDesc(processor);
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ return buf.forEachByteDesc(index, length, processor);
+ }
+
+ @Override
+ public ByteBuf copy() {
+ return buf.copy().order(order);
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ return buf.copy(index, length).order(order);
+ }
+
+ @Override
+ public ByteBuf slice() {
+ return buf.slice().order(order);
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return buf.slice(index, length).order(order);
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return buf.duplicate().order(order);
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return buf.nioBufferCount();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer() {
+ return buf.nioBuffer().order(order);
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ return buf.nioBuffer(index, length).order(order);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ return nioBuffer(index, length);
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers() {
+ ByteBuffer[] nioBuffers = buf.nioBuffers();
+ for (int i = 0; i < nioBuffers.length; i++) {
+ nioBuffers[i] = nioBuffers[i].order(order);
+ }
+ return nioBuffers;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ ByteBuffer[] nioBuffers = buf.nioBuffers(index, length);
+ for (int i = 0; i < nioBuffers.length; i++) {
+ nioBuffers[i] = nioBuffers[i].order(order);
+ }
+ return nioBuffers;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return buf.hasArray();
+ }
+
+ @Override
+ public byte[] array() {
+ return buf.array();
+ }
+
+ @Override
+ public int arrayOffset() {
+ return buf.arrayOffset();
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return buf.hasMemoryAddress();
+ }
+
+ @Override
+ public long memoryAddress() {
+ return buf.memoryAddress();
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return buf.toString(charset);
+ }
+
+ @Override
+ public String toString(int index, int length, Charset charset) {
+ return buf.toString(index, length, charset);
+ }
+
+ @Override
+ public int refCnt() {
+ return buf.refCnt();
+ }
+
+ @Override
+ public ByteBuf retain() {
+ buf.retain();
+ return this;
+ }
+
+ @Override
+ public ByteBuf retain(int increment) {
+ buf.retain(increment);
+ return this;
+ }
+
+ @Override
+ public boolean release() {
+ return buf.release();
+ }
+
+ @Override
+ public boolean release(int decrement) {
+ return buf.release(decrement);
+ }
+
+ @Override
+ public int hashCode() {
+ return buf.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof ByteBuf) {
+ return ByteBufUtil.equals(this, (ByteBuf) obj);
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(ByteBuf buffer) {
+ return ByteBufUtil.compare(this, buffer);
+ }
+
+ @Override
+ public String toString() {
+ return "Swapped(" + buf.toString() + ')';
+ }
+}
diff --git a/common/src/common/net/buffer/Unpooled.java b/common/src/common/net/buffer/Unpooled.java
new file mode 100644
index 0000000..8bb2d67
--- /dev/null
+++ b/common/src/common/net/buffer/Unpooled.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+import common.net.util.internal.PlatformDependent;
+
+
+/**
+ * Creates a new {@link ByteBuf} by allocating new space or by wrapping
+ * or copying existing byte arrays, byte buffers and a string.
+ *
+ * Use static import
+ * This classes is intended to be used with Java 5 static import statement:
+ *
+ *
+ * import static game.net.buffer.{@link Unpooled}.*;
+ *
+ * {@link ByteBuf} heapBuffer = buffer(128);
+ * {@link ByteBuf} directBuffer = directBuffer(256);
+ * {@link ByteBuf} wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
+ * {@link ByteBuf} copiedBuffe r = copiedBuffer({@link ByteBuffer}.allocate(128));
+ *
+ *
+ * Allocating a new buffer
+ *
+ * Three buffer types are provided out of the box.
+ *
+ *
+ * {@link #buffer(int)} allocates a new fixed-capacity heap buffer.
+ * {@link #directBuffer(int)} allocates a new fixed-capacity direct buffer.
+ *
+ *
+ * Creating a wrapped buffer
+ *
+ * Wrapped buffer is a buffer which is a view of one or more existing
+ * byte arrays and byte buffers. Any changes in the content of the original
+ * array or buffer will be visible in the wrapped buffer. Various wrapper
+ * methods are provided and their name is all {@code wrappedBuffer()}.
+ * You might want to take a look at the methods that accept varargs closely if
+ * you want to create a buffer which is composed of more than one array to
+ * reduce the number of memory copy.
+ *
+ * Creating a copied buffer
+ *
+ * Copied buffer is a deep copy of one or more existing byte arrays, byte
+ * buffers or a string. Unlike a wrapped buffer, there's no shared data
+ * between the original data and the copied buffer. Various copy methods are
+ * provided and their name is all {@code copiedBuffer()}. It is also convenient
+ * to use this operation to merge multiple buffers into one buffer.
+ *
+ * Miscellaneous utility methods
+ *
+ * This class also provides various utility methods to help implementation
+ * of a new buffer type, generation of hex dump and swapping an integer's
+ * byte order.
+ */
+public final class Unpooled {
+
+ private static final ByteBufAllocator ALLOC = UnpooledByteBufAllocator.DEFAULT;
+
+ /**
+ * Big endian byte order.
+ */
+ public static final ByteOrder BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
+
+ /**
+ * Little endian byte order.
+ */
+ public static final ByteOrder LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
+
+ /**
+ * A buffer whose capacity is {@code 0}.
+ */
+ public static final ByteBuf EMPTY_BUFFER = ALLOC.buffer(0, 0);
+
+ /**
+ * Creates a new big-endian Java heap buffer with reasonably small initial capacity, which
+ * expands its capacity boundlessly on demand.
+ */
+ public static ByteBuf buffer() {
+ return ALLOC.heapBuffer();
+ }
+
+ /**
+ * Creates a new big-endian direct buffer with reasonably small initial capacity, which
+ * expands its capacity boundlessly on demand.
+ */
+ public static ByteBuf directBuffer() {
+ return ALLOC.directBuffer();
+ }
+
+ /**
+ * Creates a new big-endian Java heap buffer with the specified {@code capacity}, which
+ * expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and
+ * {@code writerIndex} are {@code 0}.
+ */
+ public static ByteBuf buffer(int initialCapacity) {
+ return ALLOC.heapBuffer(initialCapacity);
+ }
+
+ /**
+ * Creates a new big-endian direct buffer with the specified {@code capacity}, which
+ * expands its capacity boundlessly on demand. The new buffer's {@code readerIndex} and
+ * {@code writerIndex} are {@code 0}.
+ */
+ public static ByteBuf directBuffer(int initialCapacity) {
+ return ALLOC.directBuffer(initialCapacity);
+ }
+
+ /**
+ * Creates a new big-endian Java heap buffer with the specified
+ * {@code initialCapacity}, that may grow up to {@code maxCapacity}
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0}.
+ */
+ public static ByteBuf buffer(int initialCapacity, int maxCapacity) {
+ return ALLOC.heapBuffer(initialCapacity, maxCapacity);
+ }
+
+ /**
+ * Creates a new big-endian direct buffer with the specified
+ * {@code initialCapacity}, that may grow up to {@code maxCapacity}.
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0}.
+ */
+ public static ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
+ return ALLOC.directBuffer(initialCapacity, maxCapacity);
+ }
+
+ /**
+ * Creates a new big-endian buffer which wraps the specified {@code array}.
+ * A modification on the specified array's content will be visible to the
+ * returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(byte[] array) {
+ if (array.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ return new UnpooledHeapByteBuf(ALLOC, array, array.length);
+ }
+
+ /**
+ * Creates a new big-endian buffer which wraps the sub-region of the
+ * specified {@code array}. A modification on the specified array's
+ * content will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(byte[] array, int offset, int length) {
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+
+ if (offset == 0 && length == array.length) {
+ return wrappedBuffer(array);
+ }
+
+ return wrappedBuffer(array).slice(offset, length);
+ }
+
+ /**
+ * Creates a new buffer which wraps the specified NIO buffer's current
+ * slice. A modification on the specified buffer's content will be
+ * visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(ByteBuffer buffer) {
+ if (!buffer.hasRemaining()) {
+ return EMPTY_BUFFER;
+ }
+ if (buffer.hasArray()) {
+ return wrappedBuffer(
+ buffer.array(),
+ buffer.arrayOffset() + buffer.position(),
+ buffer.remaining()).order(buffer.order());
+ } else if (PlatformDependent.hasUnsafe()) {
+ if (buffer.isReadOnly()) {
+ if (buffer.isDirect()) {
+ return new ReadOnlyUnsafeDirectByteBuf(ALLOC, buffer);
+ } else {
+ return new ReadOnlyByteBufferBuf(ALLOC, buffer);
+ }
+ } else {
+ return new UnpooledUnsafeDirectByteBuf(ALLOC, buffer, buffer.remaining());
+ }
+ } else {
+ if (buffer.isReadOnly()) {
+ return new ReadOnlyByteBufferBuf(ALLOC, buffer);
+ } else {
+ return new UnpooledDirectByteBuf(ALLOC, buffer, buffer.remaining());
+ }
+ }
+ }
+
+ /**
+ * Creates a new buffer which wraps the specified buffer's readable bytes.
+ * A modification on the specified buffer's content will be visible to the
+ * returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(ByteBuf buffer) {
+ if (buffer.isReadable()) {
+ return buffer.slice();
+ } else {
+ return EMPTY_BUFFER;
+ }
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the specified
+ * arrays without copying them. A modification on the specified arrays'
+ * content will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(byte[]... arrays) {
+ return wrappedBuffer(16, arrays);
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the readable bytes of the
+ * specified buffers without copying them. A modification on the content
+ * of the specified buffers will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(ByteBuf... buffers) {
+ return wrappedBuffer(16, buffers);
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the slices of the specified
+ * NIO buffers without copying them. A modification on the content of the
+ * specified buffers will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(ByteBuffer... buffers) {
+ return wrappedBuffer(16, buffers);
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the specified
+ * arrays without copying them. A modification on the specified arrays'
+ * content will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(int maxNumComponents, byte[]... arrays) {
+ switch (arrays.length) {
+ case 0:
+ break;
+ case 1:
+ if (arrays[0].length != 0) {
+ return wrappedBuffer(arrays[0]);
+ }
+ break;
+ default:
+ // Get the list of the component, while guessing the byte order.
+ final List components = new ArrayList(arrays.length);
+ for (byte[] a: arrays) {
+ if (a == null) {
+ break;
+ }
+ if (a.length > 0) {
+ components.add(wrappedBuffer(a));
+ }
+ }
+
+ if (!components.isEmpty()) {
+ return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
+ }
+ }
+
+ return EMPTY_BUFFER;
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the readable bytes of the
+ * specified buffers without copying them. A modification on the content
+ * of the specified buffers will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuf... buffers) {
+ switch (buffers.length) {
+ case 0:
+ break;
+ case 1:
+ if (buffers[0].isReadable()) {
+ return wrappedBuffer(buffers[0].order(BIG_ENDIAN));
+ }
+ break;
+ default:
+ for (ByteBuf b: buffers) {
+ if (b.isReadable()) {
+ return new CompositeByteBuf(ALLOC, false, maxNumComponents, buffers);
+ }
+ }
+ }
+ return EMPTY_BUFFER;
+ }
+
+ /**
+ * Creates a new big-endian composite buffer which wraps the slices of the specified
+ * NIO buffers without copying them. A modification on the content of the
+ * specified buffers will be visible to the returned buffer.
+ */
+ public static ByteBuf wrappedBuffer(int maxNumComponents, ByteBuffer... buffers) {
+ switch (buffers.length) {
+ case 0:
+ break;
+ case 1:
+ if (buffers[0].hasRemaining()) {
+ return wrappedBuffer(buffers[0].order(BIG_ENDIAN));
+ }
+ break;
+ default:
+ // Get the list of the component, while guessing the byte order.
+ final List components = new ArrayList(buffers.length);
+ for (ByteBuffer b: buffers) {
+ if (b == null) {
+ break;
+ }
+ if (b.remaining() > 0) {
+ components.add(wrappedBuffer(b.order(BIG_ENDIAN)));
+ }
+ }
+
+ if (!components.isEmpty()) {
+ return new CompositeByteBuf(ALLOC, false, maxNumComponents, components);
+ }
+ }
+
+ return EMPTY_BUFFER;
+ }
+
+ /**
+ * Returns a new big-endian composite buffer with no components.
+ */
+ public static CompositeByteBuf compositeBuffer() {
+ return compositeBuffer(16);
+ }
+
+ /**
+ * Returns a new big-endian composite buffer with no components.
+ */
+ public static CompositeByteBuf compositeBuffer(int maxNumComponents) {
+ return new CompositeByteBuf(ALLOC, false, maxNumComponents);
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is a copy of the
+ * specified {@code array}. The new buffer's {@code readerIndex} and
+ * {@code writerIndex} are {@code 0} and {@code array.length} respectively.
+ */
+ public static ByteBuf copiedBuffer(byte[] array) {
+ if (array.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ return wrappedBuffer(array.clone());
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is a copy of the
+ * specified {@code array}'s sub-region. The new buffer's
+ * {@code readerIndex} and {@code writerIndex} are {@code 0} and
+ * the specified {@code length} respectively.
+ */
+ public static ByteBuf copiedBuffer(byte[] array, int offset, int length) {
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+ byte[] copy = new byte[length];
+ System.arraycopy(array, offset, copy, 0, length);
+ return wrappedBuffer(copy);
+ }
+
+ /**
+ * Creates a new buffer whose content is a copy of the specified
+ * {@code buffer}'s current slice. The new buffer's {@code readerIndex}
+ * and {@code writerIndex} are {@code 0} and {@code buffer.remaining}
+ * respectively.
+ */
+ public static ByteBuf copiedBuffer(ByteBuffer buffer) {
+ int length = buffer.remaining();
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+ byte[] copy = new byte[length];
+ int position = buffer.position();
+ try {
+ buffer.get(copy);
+ } finally {
+ buffer.position(position);
+ }
+ return wrappedBuffer(copy).order(buffer.order());
+ }
+
+ /**
+ * Creates a new buffer whose content is a copy of the specified
+ * {@code buffer}'s readable bytes. The new buffer's {@code readerIndex}
+ * and {@code writerIndex} are {@code 0} and {@code buffer.readableBytes}
+ * respectively.
+ */
+ public static ByteBuf copiedBuffer(ByteBuf buffer) {
+ int readable = buffer.readableBytes();
+ if (readable > 0) {
+ ByteBuf copy = buffer(readable);
+ copy.writeBytes(buffer, buffer.readerIndex(), readable);
+ return copy;
+ } else {
+ return EMPTY_BUFFER;
+ }
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is a merged copy of
+ * the specified {@code arrays}. The new buffer's {@code readerIndex}
+ * and {@code writerIndex} are {@code 0} and the sum of all arrays'
+ * {@code length} respectively.
+ */
+ public static ByteBuf copiedBuffer(byte[]... arrays) {
+ switch (arrays.length) {
+ case 0:
+ return EMPTY_BUFFER;
+ case 1:
+ if (arrays[0].length == 0) {
+ return EMPTY_BUFFER;
+ } else {
+ return copiedBuffer(arrays[0]);
+ }
+ }
+
+ // Merge the specified arrays into one array.
+ int length = 0;
+ for (byte[] a: arrays) {
+ if (Integer.MAX_VALUE - length < a.length) {
+ throw new IllegalArgumentException(
+ "The total length of the specified arrays is too big.");
+ }
+ length += a.length;
+ }
+
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+
+ byte[] mergedArray = new byte[length];
+ for (int i = 0, j = 0; i < arrays.length; i ++) {
+ byte[] a = arrays[i];
+ System.arraycopy(a, 0, mergedArray, j, a.length);
+ j += a.length;
+ }
+
+ return wrappedBuffer(mergedArray);
+ }
+
+ /**
+ * Creates a new buffer whose content is a merged copy of the specified
+ * {@code buffers}' readable bytes. The new buffer's {@code readerIndex}
+ * and {@code writerIndex} are {@code 0} and the sum of all buffers'
+ * {@code readableBytes} respectively.
+ *
+ * @throws IllegalArgumentException
+ * if the specified buffers' endianness are different from each
+ * other
+ */
+ public static ByteBuf copiedBuffer(ByteBuf... buffers) {
+ switch (buffers.length) {
+ case 0:
+ return EMPTY_BUFFER;
+ case 1:
+ return copiedBuffer(buffers[0]);
+ }
+
+ // Merge the specified buffers into one buffer.
+ ByteOrder order = null;
+ int length = 0;
+ for (ByteBuf b: buffers) {
+ int bLen = b.readableBytes();
+ if (bLen <= 0) {
+ continue;
+ }
+ if (Integer.MAX_VALUE - length < bLen) {
+ throw new IllegalArgumentException(
+ "The total length of the specified buffers is too big.");
+ }
+ length += bLen;
+ if (order != null) {
+ if (!order.equals(b.order())) {
+ throw new IllegalArgumentException("inconsistent byte order");
+ }
+ } else {
+ order = b.order();
+ }
+ }
+
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+
+ byte[] mergedArray = new byte[length];
+ for (int i = 0, j = 0; i < buffers.length; i ++) {
+ ByteBuf b = buffers[i];
+ int bLen = b.readableBytes();
+ b.getBytes(b.readerIndex(), mergedArray, j, bLen);
+ j += bLen;
+ }
+
+ return wrappedBuffer(mergedArray).order(order);
+ }
+
+ /**
+ * Creates a new buffer whose content is a merged copy of the specified
+ * {@code buffers}' slices. The new buffer's {@code readerIndex} and
+ * {@code writerIndex} are {@code 0} and the sum of all buffers'
+ * {@code remaining} respectively.
+ *
+ * @throws IllegalArgumentException
+ * if the specified buffers' endianness are different from each
+ * other
+ */
+ public static ByteBuf copiedBuffer(ByteBuffer... buffers) {
+ switch (buffers.length) {
+ case 0:
+ return EMPTY_BUFFER;
+ case 1:
+ return copiedBuffer(buffers[0]);
+ }
+
+ // Merge the specified buffers into one buffer.
+ ByteOrder order = null;
+ int length = 0;
+ for (ByteBuffer b: buffers) {
+ int bLen = b.remaining();
+ if (bLen <= 0) {
+ continue;
+ }
+ if (Integer.MAX_VALUE - length < bLen) {
+ throw new IllegalArgumentException(
+ "The total length of the specified buffers is too big.");
+ }
+ length += bLen;
+ if (order != null) {
+ if (!order.equals(b.order())) {
+ throw new IllegalArgumentException("inconsistent byte order");
+ }
+ } else {
+ order = b.order();
+ }
+ }
+
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+
+ byte[] mergedArray = new byte[length];
+ for (int i = 0, j = 0; i < buffers.length; i ++) {
+ ByteBuffer b = buffers[i];
+ int bLen = b.remaining();
+ int oldPos = b.position();
+ b.get(mergedArray, j, bLen);
+ b.position(oldPos);
+ j += bLen;
+ }
+
+ return wrappedBuffer(mergedArray).order(order);
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is the specified
+ * {@code string} encoded in the specified {@code charset}.
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0} and the length of the encoded string respectively.
+ */
+ public static ByteBuf copiedBuffer(CharSequence string, Charset charset) {
+ if (string == null) {
+ throw new NullPointerException("string");
+ }
+
+ if (string instanceof CharBuffer) {
+ return copiedBuffer((CharBuffer) string, charset);
+ }
+
+ return copiedBuffer(CharBuffer.wrap(string), charset);
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is a subregion of
+ * the specified {@code string} encoded in the specified {@code charset}.
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0} and the length of the encoded string respectively.
+ */
+ public static ByteBuf copiedBuffer(
+ CharSequence string, int offset, int length, Charset charset) {
+ if (string == null) {
+ throw new NullPointerException("string");
+ }
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+
+ if (string instanceof CharBuffer) {
+ CharBuffer buf = (CharBuffer) string;
+ if (buf.hasArray()) {
+ return copiedBuffer(
+ buf.array(),
+ buf.arrayOffset() + buf.position() + offset,
+ length, charset);
+ }
+
+ buf = buf.slice();
+ buf.limit(length);
+ buf.position(offset);
+ return copiedBuffer(buf, charset);
+ }
+
+ return copiedBuffer(CharBuffer.wrap(string, offset, offset + length), charset);
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is the specified
+ * {@code array} encoded in the specified {@code charset}.
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0} and the length of the encoded string respectively.
+ */
+ public static ByteBuf copiedBuffer(char[] array, Charset charset) {
+ if (array == null) {
+ throw new NullPointerException("array");
+ }
+ return copiedBuffer(array, 0, array.length, charset);
+ }
+
+ /**
+ * Creates a new big-endian buffer whose content is a subregion of
+ * the specified {@code array} encoded in the specified {@code charset}.
+ * The new buffer's {@code readerIndex} and {@code writerIndex} are
+ * {@code 0} and the length of the encoded string respectively.
+ */
+ public static ByteBuf copiedBuffer(char[] array, int offset, int length, Charset charset) {
+ if (array == null) {
+ throw new NullPointerException("array");
+ }
+ if (length == 0) {
+ return EMPTY_BUFFER;
+ }
+ return copiedBuffer(CharBuffer.wrap(array, offset, length), charset);
+ }
+
+ private static ByteBuf copiedBuffer(CharBuffer buffer, Charset charset) {
+ return ByteBufUtil.encodeString0(ALLOC, true, buffer, charset);
+ }
+
+ /**
+ * Creates a read-only buffer which disallows any modification operations
+ * on the specified {@code buffer}. The new buffer has the same
+ * {@code readerIndex} and {@code writerIndex} with the specified
+ * {@code buffer}.
+ */
+ public static ByteBuf unmodifiableBuffer(ByteBuf buffer) {
+ ByteOrder endianness = buffer.order();
+ if (endianness == BIG_ENDIAN) {
+ return new ReadOnlyByteBuf(buffer);
+ }
+
+ return new ReadOnlyByteBuf(buffer.order(BIG_ENDIAN)).order(LITTLE_ENDIAN);
+ }
+
+ /**
+ * Creates a new 4-byte big-endian buffer that holds the specified 32-bit integer.
+ */
+ public static ByteBuf copyInt(int value) {
+ ByteBuf buf = buffer(4);
+ buf.writeInt(value);
+ return buf;
+ }
+
+ /**
+ * Create a big-endian buffer that holds a sequence of the specified 32-bit integers.
+ */
+ public static ByteBuf copyInt(int... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 4);
+ for (int v: values) {
+ buffer.writeInt(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new 2-byte big-endian buffer that holds the specified 16-bit integer.
+ */
+ public static ByteBuf copyShort(int value) {
+ ByteBuf buf = buffer(2);
+ buf.writeShort(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 16-bit integers.
+ */
+ public static ByteBuf copyShort(short... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 2);
+ for (int v: values) {
+ buffer.writeShort(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 16-bit integers.
+ */
+ public static ByteBuf copyShort(int... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 2);
+ for (int v: values) {
+ buffer.writeShort(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new 3-byte big-endian buffer that holds the specified 24-bit integer.
+ */
+ public static ByteBuf copyMedium(int value) {
+ ByteBuf buf = buffer(3);
+ buf.writeMedium(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 24-bit integers.
+ */
+ public static ByteBuf copyMedium(int... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 3);
+ for (int v: values) {
+ buffer.writeMedium(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new 8-byte big-endian buffer that holds the specified 64-bit integer.
+ */
+ public static ByteBuf copyLong(long value) {
+ ByteBuf buf = buffer(8);
+ buf.writeLong(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 64-bit integers.
+ */
+ public static ByteBuf copyLong(long... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 8);
+ for (long v: values) {
+ buffer.writeLong(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new single-byte big-endian buffer that holds the specified boolean value.
+ */
+ public static ByteBuf copyBoolean(boolean value) {
+ ByteBuf buf = buffer(1);
+ buf.writeBoolean(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified boolean values.
+ */
+ public static ByteBuf copyBoolean(boolean... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length);
+ for (boolean v: values) {
+ buffer.writeBoolean(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new 4-byte big-endian buffer that holds the specified 32-bit floating point number.
+ */
+ public static ByteBuf copyFloat(float value) {
+ ByteBuf buf = buffer(4);
+ buf.writeFloat(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 32-bit floating point numbers.
+ */
+ public static ByteBuf copyFloat(float... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 4);
+ for (float v: values) {
+ buffer.writeFloat(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Creates a new 8-byte big-endian buffer that holds the specified 64-bit floating point number.
+ */
+ public static ByteBuf copyDouble(double value) {
+ ByteBuf buf = buffer(8);
+ buf.writeDouble(value);
+ return buf;
+ }
+
+ /**
+ * Create a new big-endian buffer that holds a sequence of the specified 64-bit floating point numbers.
+ */
+ public static ByteBuf copyDouble(double... values) {
+ if (values == null || values.length == 0) {
+ return EMPTY_BUFFER;
+ }
+ ByteBuf buffer = buffer(values.length * 8);
+ for (double v: values) {
+ buffer.writeDouble(v);
+ }
+ return buffer;
+ }
+
+ /**
+ * Return a unreleasable view on the given {@link ByteBuf} which will just ignore release and retain calls.
+ */
+ public static ByteBuf unreleasableBuffer(ByteBuf buf) {
+ return new UnreleasableByteBuf(buf);
+ }
+
+ private Unpooled() {
+ // Unused
+ }
+}
diff --git a/common/src/common/net/buffer/UnpooledByteBufAllocator.java b/common/src/common/net/buffer/UnpooledByteBufAllocator.java
new file mode 100644
index 0000000..8b6183d
--- /dev/null
+++ b/common/src/common/net/buffer/UnpooledByteBufAllocator.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import common.net.util.internal.PlatformDependent;
+
+/**
+ * Simplistic {@link ByteBufAllocator} implementation that does not pool anything.
+ */
+public final class UnpooledByteBufAllocator extends AbstractByteBufAllocator {
+
+ /**
+ * Default instance
+ */
+ public static final UnpooledByteBufAllocator DEFAULT =
+ new UnpooledByteBufAllocator(PlatformDependent.directBufferPreferred());
+
+ /**
+ * Create a new instance
+ *
+ * @param preferDirect {@code true} if {@link #buffer(int)} should try to allocate a direct buffer rather than
+ * a heap buffer
+ */
+ public UnpooledByteBufAllocator(boolean preferDirect) {
+ super(preferDirect);
+ }
+
+ @Override
+ protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
+ return new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity);
+ }
+
+ @Override
+ protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
+ ByteBuf buf;
+ if (PlatformDependent.hasUnsafe()) {
+ buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);
+ } else {
+ buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
+ }
+
+ return toLeakAwareBuffer(buf);
+ }
+
+ @Override
+ public boolean isDirectBufferPooled() {
+ return false;
+ }
+}
diff --git a/common/src/common/net/buffer/UnpooledDirectByteBuf.java b/common/src/common/net/buffer/UnpooledDirectByteBuf.java
new file mode 100644
index 0000000..76a738a
--- /dev/null
+++ b/common/src/common/net/buffer/UnpooledDirectByteBuf.java
@@ -0,0 +1,604 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.internal.PlatformDependent;
+
+/**
+ * A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)}
+ * and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the
+ * constructor explicitly.
+ */
+public class UnpooledDirectByteBuf extends AbstractReferenceCountedByteBuf {
+
+ private final ByteBufAllocator alloc;
+
+ private ByteBuffer buffer;
+ private ByteBuffer tmpNioBuf;
+ private int capacity;
+ private boolean doNotFree;
+
+ /**
+ * Creates a new direct buffer.
+ *
+ * @param initialCapacity the initial capacity of the underlying direct buffer
+ * @param maxCapacity the maximum capacity of the underlying direct buffer
+ */
+ protected UnpooledDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
+ super(maxCapacity);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (initialCapacity < 0) {
+ throw new IllegalArgumentException("initialCapacity: " + initialCapacity);
+ }
+ if (maxCapacity < 0) {
+ throw new IllegalArgumentException("maxCapacity: " + maxCapacity);
+ }
+ if (initialCapacity > maxCapacity) {
+ throw new IllegalArgumentException(String.format(
+ "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
+ }
+
+ this.alloc = alloc;
+ setByteBuffer(ByteBuffer.allocateDirect(initialCapacity));
+ }
+
+ /**
+ * Creates a new direct buffer by wrapping the specified initial buffer.
+ *
+ * @param maxCapacity the maximum capacity of the underlying direct buffer
+ */
+ protected UnpooledDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
+ super(maxCapacity);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (initialBuffer == null) {
+ throw new NullPointerException("initialBuffer");
+ }
+ if (!initialBuffer.isDirect()) {
+ throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
+ }
+ if (initialBuffer.isReadOnly()) {
+ throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
+ }
+
+ int initialCapacity = initialBuffer.remaining();
+ if (initialCapacity > maxCapacity) {
+ throw new IllegalArgumentException(String.format(
+ "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
+ }
+
+ this.alloc = alloc;
+ doNotFree = true;
+ setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN));
+ writerIndex(initialCapacity);
+ }
+
+ /**
+ * Allocate a new direct {@link ByteBuffer} with the given initialCapacity.
+ */
+ protected ByteBuffer allocateDirect(int initialCapacity) {
+ return ByteBuffer.allocateDirect(initialCapacity);
+ }
+
+ /**
+ * Free a direct {@link ByteBuffer}
+ */
+ protected void freeDirect(ByteBuffer buffer) {
+ PlatformDependent.freeDirectBuffer(buffer);
+ }
+
+ private void setByteBuffer(ByteBuffer buffer) {
+ ByteBuffer oldBuffer = this.buffer;
+ if (oldBuffer != null) {
+ if (doNotFree) {
+ doNotFree = false;
+ } else {
+ freeDirect(oldBuffer);
+ }
+ }
+
+ this.buffer = buffer;
+ tmpNioBuf = null;
+ capacity = buffer.remaining();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ public int capacity() {
+ return capacity;
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ ensureAccessible();
+ if (newCapacity < 0 || newCapacity > maxCapacity()) {
+ throw new IllegalArgumentException("newCapacity: " + newCapacity);
+ }
+
+ int readerIndex = readerIndex();
+ int writerIndex = writerIndex();
+
+ int oldCapacity = capacity;
+ if (newCapacity > oldCapacity) {
+ ByteBuffer oldBuffer = buffer;
+ ByteBuffer newBuffer = allocateDirect(newCapacity);
+ oldBuffer.position(0).limit(oldBuffer.capacity());
+ newBuffer.position(0).limit(oldBuffer.capacity());
+ newBuffer.put(oldBuffer);
+ newBuffer.clear();
+ setByteBuffer(newBuffer);
+ } else if (newCapacity < oldCapacity) {
+ ByteBuffer oldBuffer = buffer;
+ ByteBuffer newBuffer = allocateDirect(newCapacity);
+ if (readerIndex < newCapacity) {
+ if (writerIndex > newCapacity) {
+ writerIndex(writerIndex = newCapacity);
+ }
+ oldBuffer.position(readerIndex).limit(writerIndex);
+ newBuffer.position(readerIndex).limit(writerIndex);
+ newBuffer.put(oldBuffer);
+ newBuffer.clear();
+ } else {
+ setIndex(newCapacity, newCapacity);
+ }
+ setByteBuffer(newBuffer);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return alloc;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public int arrayOffset() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public byte getByte(int index) {
+ ensureAccessible();
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return buffer.get(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ ensureAccessible();
+ return _getShort(index);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return buffer.getShort(index);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ ensureAccessible();
+ return _getUnsignedMedium(index);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return (getByte(index) & 0xff) << 16 | (getByte(index + 1) & 0xff) << 8 | getByte(index + 2) & 0xff;
+ }
+
+ @Override
+ public int getInt(int index) {
+ ensureAccessible();
+ return _getInt(index);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return buffer.getInt(index);
+ }
+
+ @Override
+ public long getLong(int index) {
+ ensureAccessible();
+ return _getLong(index);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return buffer.getLong(index);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (dst.hasArray()) {
+ getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else if (dst.nioBufferCount() > 0) {
+ for (ByteBuffer bb: dst.nioBuffers(dstIndex, length)) {
+ int bbLen = bb.remaining();
+ getBytes(index, bb);
+ index += bbLen;
+ }
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ getBytes(index, dst, dstIndex, length, false);
+ return this;
+ }
+
+ private void getBytes(int index, byte[] dst, int dstIndex, int length, boolean internal) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+
+ if (dstIndex < 0 || dstIndex > dst.length - length) {
+ throw new IndexOutOfBoundsException(String.format(
+ "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
+ }
+
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index).limit(index + length);
+ tmpBuf.get(dst, dstIndex, length);
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, dstIndex, length, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ getBytes(index, dst, false);
+ return this;
+ }
+
+ private void getBytes(int index, ByteBuffer dst, boolean internal) {
+ checkIndex(index);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ int length = dst.remaining();
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ ensureAccessible();
+ _setByte(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ buffer.put(index, (byte) value);
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ ensureAccessible();
+ _setShort(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ buffer.putShort(index, (short) value);
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ ensureAccessible();
+ _setMedium(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ setByte(index, (byte) (value >>> 16));
+ setByte(index + 1, (byte) (value >>> 8));
+ setByte(index + 2, (byte) value);
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ ensureAccessible();
+ _setInt(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ buffer.putInt(index, value);
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ ensureAccessible();
+ _setLong(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ buffer.putLong(index, value);
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.capacity());
+ if (src.nioBufferCount() > 0) {
+ for (ByteBuffer bb: src.nioBuffers(srcIndex, length)) {
+ int bbLen = bb.remaining();
+ setBytes(index, bb);
+ index += bbLen;
+ }
+ } else {
+ src.getBytes(srcIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.length);
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + length);
+ tmpBuf.put(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ ensureAccessible();
+ ByteBuffer tmpBuf = internalNioBuffer();
+ if (src == tmpBuf) {
+ src = src.duplicate();
+ }
+
+ tmpBuf.clear().position(index).limit(index + src.remaining());
+ tmpBuf.put(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ getBytes(index, out, length, false);
+ return this;
+ }
+
+ private void getBytes(int index, OutputStream out, int length, boolean internal) throws IOException {
+ ensureAccessible();
+ if (length == 0) {
+ return;
+ }
+
+ if (buffer.hasArray()) {
+ out.write(buffer.array(), index + buffer.arrayOffset(), length);
+ } else {
+ byte[] tmp = new byte[length];
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index);
+ tmpBuf.get(tmp);
+ out.write(tmp);
+ }
+ }
+
+ @Override
+ public ByteBuf readBytes(OutputStream out, int length) throws IOException {
+ checkReadableBytes(length);
+ getBytes(readerIndex, out, length, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ ensureAccessible();
+ if (length == 0) {
+ return 0;
+ }
+
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index).limit(index + length);
+ return out.write(tmpBuf);
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ ensureAccessible();
+ if (buffer.hasArray()) {
+ return in.read(buffer.array(), buffer.arrayOffset() + index, length);
+ } else {
+ byte[] tmp = new byte[length];
+ int readBytes = in.read(tmp);
+ if (readBytes <= 0) {
+ return readBytes;
+ }
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index);
+ tmpBuf.put(tmp, 0, readBytes);
+ return readBytes;
+ }
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ ensureAccessible();
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + length);
+ try {
+ return in.read(tmpNioBuf);
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ ensureAccessible();
+ ByteBuffer src;
+ try {
+ src = (ByteBuffer) buffer.duplicate().clear().position(index).limit(index + length);
+ } catch (IllegalArgumentException ignored) {
+ throw new IndexOutOfBoundsException("Too many bytes to read - Need " + (index + length));
+ }
+
+ return alloc().directBuffer(length, maxCapacity()).writeBytes(src);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ private ByteBuffer internalNioBuffer() {
+ ByteBuffer tmpNioBuf = this.tmpNioBuf;
+ if (tmpNioBuf == null) {
+ this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
+ }
+ return tmpNioBuf;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice();
+ }
+
+ @Override
+ protected void deallocate() {
+ ByteBuffer buffer = this.buffer;
+ if (buffer == null) {
+ return;
+ }
+
+ this.buffer = null;
+
+ if (!doNotFree) {
+ freeDirect(buffer);
+ }
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+}
diff --git a/common/src/common/net/buffer/UnpooledHeapByteBuf.java b/common/src/common/net/buffer/UnpooledHeapByteBuf.java
new file mode 100644
index 0000000..e56951f
--- /dev/null
+++ b/common/src/common/net/buffer/UnpooledHeapByteBuf.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.internal.PlatformDependent;
+
+/**
+ * Big endian Java heap buffer implementation.
+ */
+public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
+
+ private final ByteBufAllocator alloc;
+ private byte[] array;
+ private ByteBuffer tmpNioBuf;
+
+ /**
+ * Creates a new heap buffer with a newly allocated byte array.
+ *
+ * @param initialCapacity the initial capacity of the underlying byte array
+ * @param maxCapacity the max capacity of the underlying byte array
+ */
+ protected UnpooledHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
+ this(alloc, new byte[initialCapacity], 0, 0, maxCapacity);
+ }
+
+ /**
+ * Creates a new heap buffer with an existing byte array.
+ *
+ * @param initialArray the initial underlying byte array
+ * @param maxCapacity the max capacity of the underlying byte array
+ */
+ protected UnpooledHeapByteBuf(ByteBufAllocator alloc, byte[] initialArray, int maxCapacity) {
+ this(alloc, initialArray, 0, initialArray.length, maxCapacity);
+ }
+
+ private UnpooledHeapByteBuf(
+ ByteBufAllocator alloc, byte[] initialArray, int readerIndex, int writerIndex, int maxCapacity) {
+
+ super(maxCapacity);
+
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (initialArray == null) {
+ throw new NullPointerException("initialArray");
+ }
+ if (initialArray.length > maxCapacity) {
+ throw new IllegalArgumentException(String.format(
+ "initialCapacity(%d) > maxCapacity(%d)", initialArray.length, maxCapacity));
+ }
+
+ this.alloc = alloc;
+ setArray(initialArray);
+ setIndex(readerIndex, writerIndex);
+ }
+
+ private void setArray(byte[] initialArray) {
+ array = initialArray;
+ tmpNioBuf = null;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return alloc;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ @Override
+ public boolean isDirect() {
+ return false;
+ }
+
+ @Override
+ public int capacity() {
+ ensureAccessible();
+ return array.length;
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ ensureAccessible();
+ if (newCapacity < 0 || newCapacity > maxCapacity()) {
+ throw new IllegalArgumentException("newCapacity: " + newCapacity);
+ }
+
+ int oldCapacity = array.length;
+ if (newCapacity > oldCapacity) {
+ byte[] newArray = new byte[newCapacity];
+ System.arraycopy(array, 0, newArray, 0, array.length);
+ setArray(newArray);
+ } else if (newCapacity < oldCapacity) {
+ byte[] newArray = new byte[newCapacity];
+ int readerIndex = readerIndex();
+ if (readerIndex < newCapacity) {
+ int writerIndex = writerIndex();
+ if (writerIndex > newCapacity) {
+ writerIndex(writerIndex = newCapacity);
+ }
+ System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
+ } else {
+ setIndex(newCapacity, newCapacity);
+ }
+ setArray(newArray);
+ }
+ return this;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return true;
+ }
+
+ @Override
+ public byte[] array() {
+ ensureAccessible();
+ return array;
+ }
+
+ @Override
+ public int arrayOffset() {
+ return 0;
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return false;
+ }
+
+ @Override
+ public long memoryAddress() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.capacity());
+ if (dst.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(array, index, dst.memoryAddress() + dstIndex, length);
+ } else if (dst.hasArray()) {
+ getBytes(index, dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else {
+ dst.setBytes(dstIndex, array, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkDstIndex(index, length, dstIndex, dst.length);
+ System.arraycopy(array, index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ ensureAccessible();
+ dst.put(array, index, Math.min(capacity() - index, dst.remaining()));
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ ensureAccessible();
+ out.write(array, index, length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ ensureAccessible();
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ ensureAccessible();
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = ByteBuffer.wrap(array);
+ }
+ return out.write((ByteBuffer) tmpBuf.clear().position(index).limit(index + length));
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.capacity());
+ if (src.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, array, index, length);
+ } else if (src.hasArray()) {
+ setBytes(index, src.array(), src.arrayOffset() + srcIndex, length);
+ } else {
+ src.getBytes(srcIndex, array, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkSrcIndex(index, length, srcIndex, src.length);
+ System.arraycopy(src, srcIndex, array, index, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ ensureAccessible();
+ src.get(array, index, src.remaining());
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ ensureAccessible();
+ return in.read(array, index, length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ ensureAccessible();
+ try {
+ return in.read((ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length));
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ ensureAccessible();
+ return ByteBuffer.wrap(array, index, length).slice();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ @Override
+ public byte getByte(int index) {
+ ensureAccessible();
+ return _getByte(index);
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return array[index];
+ }
+
+ @Override
+ public short getShort(int index) {
+ ensureAccessible();
+ return _getShort(index);
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ return (short) (array[index] << 8 | array[index + 1] & 0xFF);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ ensureAccessible();
+ return _getUnsignedMedium(index);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ return (array[index] & 0xff) << 16 |
+ (array[index + 1] & 0xff) << 8 |
+ array[index + 2] & 0xff;
+ }
+
+ @Override
+ public int getInt(int index) {
+ ensureAccessible();
+ return _getInt(index);
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ return (array[index] & 0xff) << 24 |
+ (array[index + 1] & 0xff) << 16 |
+ (array[index + 2] & 0xff) << 8 |
+ array[index + 3] & 0xff;
+ }
+
+ @Override
+ public long getLong(int index) {
+ ensureAccessible();
+ return _getLong(index);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ return ((long) array[index] & 0xff) << 56 |
+ ((long) array[index + 1] & 0xff) << 48 |
+ ((long) array[index + 2] & 0xff) << 40 |
+ ((long) array[index + 3] & 0xff) << 32 |
+ ((long) array[index + 4] & 0xff) << 24 |
+ ((long) array[index + 5] & 0xff) << 16 |
+ ((long) array[index + 6] & 0xff) << 8 |
+ (long) array[index + 7] & 0xff;
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ ensureAccessible();
+ _setByte(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ array[index] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ ensureAccessible();
+ _setShort(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ array[index] = (byte) (value >>> 8);
+ array[index + 1] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ ensureAccessible();
+ _setMedium(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ array[index] = (byte) (value >>> 16);
+ array[index + 1] = (byte) (value >>> 8);
+ array[index + 2] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ ensureAccessible();
+ _setInt(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ array[index] = (byte) (value >>> 24);
+ array[index + 1] = (byte) (value >>> 16);
+ array[index + 2] = (byte) (value >>> 8);
+ array[index + 3] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ ensureAccessible();
+ _setLong(index, value);
+ return this;
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ array[index] = (byte) (value >>> 56);
+ array[index + 1] = (byte) (value >>> 48);
+ array[index + 2] = (byte) (value >>> 40);
+ array[index + 3] = (byte) (value >>> 32);
+ array[index + 4] = (byte) (value >>> 24);
+ array[index + 5] = (byte) (value >>> 16);
+ array[index + 6] = (byte) (value >>> 8);
+ array[index + 7] = (byte) value;
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ byte[] copiedArray = new byte[length];
+ System.arraycopy(array, index, copiedArray, 0, length);
+ return new UnpooledHeapByteBuf(alloc(), copiedArray, maxCapacity());
+ }
+
+ private ByteBuffer internalNioBuffer() {
+ ByteBuffer tmpNioBuf = this.tmpNioBuf;
+ if (tmpNioBuf == null) {
+ this.tmpNioBuf = tmpNioBuf = ByteBuffer.wrap(array);
+ }
+ return tmpNioBuf;
+ }
+
+ @Override
+ protected void deallocate() {
+ array = null;
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+}
diff --git a/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java b/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java
new file mode 100644
index 0000000..a098a1a
--- /dev/null
+++ b/common/src/common/net/buffer/UnpooledUnsafeDirectByteBuf.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+
+import common.net.util.internal.PlatformDependent;
+
+/**
+ * A NIO {@link ByteBuffer} based buffer. It is recommended to use {@link Unpooled#directBuffer(int)}
+ * and {@link Unpooled#wrappedBuffer(ByteBuffer)} instead of calling the
+ * constructor explicitly.
+ */
+public class UnpooledUnsafeDirectByteBuf extends AbstractReferenceCountedByteBuf {
+
+ private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+
+ private final ByteBufAllocator alloc;
+
+ private long memoryAddress;
+ private ByteBuffer buffer;
+ private ByteBuffer tmpNioBuf;
+ private int capacity;
+ private boolean doNotFree;
+
+ /**
+ * Creates a new direct buffer.
+ *
+ * @param initialCapacity the initial capacity of the underlying direct buffer
+ * @param maxCapacity the maximum capacity of the underlying direct buffer
+ */
+ protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) {
+ super(maxCapacity);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (initialCapacity < 0) {
+ throw new IllegalArgumentException("initialCapacity: " + initialCapacity);
+ }
+ if (maxCapacity < 0) {
+ throw new IllegalArgumentException("maxCapacity: " + maxCapacity);
+ }
+ if (initialCapacity > maxCapacity) {
+ throw new IllegalArgumentException(String.format(
+ "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
+ }
+
+ this.alloc = alloc;
+ setByteBuffer(allocateDirect(initialCapacity));
+ }
+
+ /**
+ * Creates a new direct buffer by wrapping the specified initial buffer.
+ *
+ * @param maxCapacity the maximum capacity of the underlying direct buffer
+ */
+ protected UnpooledUnsafeDirectByteBuf(ByteBufAllocator alloc, ByteBuffer initialBuffer, int maxCapacity) {
+ super(maxCapacity);
+ if (alloc == null) {
+ throw new NullPointerException("alloc");
+ }
+ if (initialBuffer == null) {
+ throw new NullPointerException("initialBuffer");
+ }
+ if (!initialBuffer.isDirect()) {
+ throw new IllegalArgumentException("initialBuffer is not a direct buffer.");
+ }
+ if (initialBuffer.isReadOnly()) {
+ throw new IllegalArgumentException("initialBuffer is a read-only buffer.");
+ }
+
+ int initialCapacity = initialBuffer.remaining();
+ if (initialCapacity > maxCapacity) {
+ throw new IllegalArgumentException(String.format(
+ "initialCapacity(%d) > maxCapacity(%d)", initialCapacity, maxCapacity));
+ }
+
+ this.alloc = alloc;
+ doNotFree = true;
+ setByteBuffer(initialBuffer.slice().order(ByteOrder.BIG_ENDIAN));
+ writerIndex(initialCapacity);
+ }
+
+ /**
+ * Allocate a new direct {@link ByteBuffer} with the given initialCapacity.
+ */
+ protected ByteBuffer allocateDirect(int initialCapacity) {
+ return ByteBuffer.allocateDirect(initialCapacity);
+ }
+
+ /**
+ * Free a direct {@link ByteBuffer}
+ */
+ protected void freeDirect(ByteBuffer buffer) {
+ PlatformDependent.freeDirectBuffer(buffer);
+ }
+
+ private void setByteBuffer(ByteBuffer buffer) {
+ ByteBuffer oldBuffer = this.buffer;
+ if (oldBuffer != null) {
+ if (doNotFree) {
+ doNotFree = false;
+ } else {
+ freeDirect(oldBuffer);
+ }
+ }
+
+ this.buffer = buffer;
+ memoryAddress = PlatformDependent.directBufferAddress(buffer);
+ tmpNioBuf = null;
+ capacity = buffer.remaining();
+ }
+
+ @Override
+ public boolean isDirect() {
+ return true;
+ }
+
+ @Override
+ public int capacity() {
+ return capacity;
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ ensureAccessible();
+ if (newCapacity < 0 || newCapacity > maxCapacity()) {
+ throw new IllegalArgumentException("newCapacity: " + newCapacity);
+ }
+
+ int readerIndex = readerIndex();
+ int writerIndex = writerIndex();
+
+ int oldCapacity = capacity;
+ if (newCapacity > oldCapacity) {
+ ByteBuffer oldBuffer = buffer;
+ ByteBuffer newBuffer = allocateDirect(newCapacity);
+ oldBuffer.position(0).limit(oldBuffer.capacity());
+ newBuffer.position(0).limit(oldBuffer.capacity());
+ newBuffer.put(oldBuffer);
+ newBuffer.clear();
+ setByteBuffer(newBuffer);
+ } else if (newCapacity < oldCapacity) {
+ ByteBuffer oldBuffer = buffer;
+ ByteBuffer newBuffer = allocateDirect(newCapacity);
+ if (readerIndex < newCapacity) {
+ if (writerIndex > newCapacity) {
+ writerIndex(writerIndex = newCapacity);
+ }
+ oldBuffer.position(readerIndex).limit(writerIndex);
+ newBuffer.position(readerIndex).limit(writerIndex);
+ newBuffer.put(oldBuffer);
+ newBuffer.clear();
+ } else {
+ setIndex(newCapacity, newCapacity);
+ }
+ setByteBuffer(newBuffer);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return alloc;
+ }
+
+ @Override
+ public ByteOrder order() {
+ return ByteOrder.BIG_ENDIAN;
+ }
+
+ @Override
+ public boolean hasArray() {
+ return false;
+ }
+
+ @Override
+ public byte[] array() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public int arrayOffset() {
+ throw new UnsupportedOperationException("direct buffer");
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return true;
+ }
+
+ @Override
+ public long memoryAddress() {
+ return memoryAddress;
+ }
+
+ @Override
+ protected byte _getByte(int index) {
+ return PlatformDependent.getByte(addr(index));
+ }
+
+ @Override
+ protected short _getShort(int index) {
+ short v = PlatformDependent.getShort(addr(index));
+ return NATIVE_ORDER? v : Short.reverseBytes(v);
+ }
+
+ @Override
+ protected int _getUnsignedMedium(int index) {
+ long addr = addr(index);
+ return (PlatformDependent.getByte(addr) & 0xff) << 16 |
+ (PlatformDependent.getByte(addr + 1) & 0xff) << 8 |
+ PlatformDependent.getByte(addr + 2) & 0xff;
+ }
+
+ @Override
+ protected int _getInt(int index) {
+ int v = PlatformDependent.getInt(addr(index));
+ return NATIVE_ORDER? v : Integer.reverseBytes(v);
+ }
+
+ @Override
+ protected long _getLong(int index) {
+ long v = PlatformDependent.getLong(addr(index));
+ return NATIVE_ORDER? v : Long.reverseBytes(v);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.capacity() - length) {
+ throw new IndexOutOfBoundsException("dstIndex: " + dstIndex);
+ }
+
+ if (dst.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), dst.memoryAddress() + dstIndex, length);
+ } else if (dst.hasArray()) {
+ PlatformDependent.copyMemory(addr(index), dst.array(), dst.arrayOffset() + dstIndex, length);
+ } else {
+ dst.setBytes(dstIndex, this, index, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ checkIndex(index, length);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+ if (dstIndex < 0 || dstIndex > dst.length - length) {
+ throw new IndexOutOfBoundsException(String.format(
+ "dstIndex: %d, length: %d (expected: range(0, %d))", dstIndex, length, dst.length));
+ }
+
+ if (length != 0) {
+ PlatformDependent.copyMemory(addr(index), dst, dstIndex, length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ getBytes(index, dst, false);
+ return this;
+ }
+
+ private void getBytes(int index, ByteBuffer dst, boolean internal) {
+ checkIndex(index);
+ if (dst == null) {
+ throw new NullPointerException("dst");
+ }
+
+ int bytesToCopy = Math.min(capacity() - index, dst.remaining());
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index).limit(index + bytesToCopy);
+ dst.put(tmpBuf);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ int length = dst.remaining();
+ checkReadableBytes(length);
+ getBytes(readerIndex, dst, true);
+ readerIndex += length;
+ return this;
+ }
+
+ @Override
+ protected void _setByte(int index, int value) {
+ PlatformDependent.putByte(addr(index), (byte) value);
+ }
+
+ @Override
+ protected void _setShort(int index, int value) {
+ PlatformDependent.putShort(addr(index), NATIVE_ORDER ? (short) value : Short.reverseBytes((short) value));
+ }
+
+ @Override
+ protected void _setMedium(int index, int value) {
+ long addr = addr(index);
+ PlatformDependent.putByte(addr, (byte) (value >>> 16));
+ PlatformDependent.putByte(addr + 1, (byte) (value >>> 8));
+ PlatformDependent.putByte(addr + 2, (byte) value);
+ }
+
+ @Override
+ protected void _setInt(int index, int value) {
+ PlatformDependent.putInt(addr(index), NATIVE_ORDER ? value : Integer.reverseBytes(value));
+ }
+
+ @Override
+ protected void _setLong(int index, long value) {
+ PlatformDependent.putLong(addr(index), NATIVE_ORDER ? value : Long.reverseBytes(value));
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ checkIndex(index, length);
+ if (src == null) {
+ throw new NullPointerException("src");
+ }
+ if (srcIndex < 0 || srcIndex > src.capacity() - length) {
+ throw new IndexOutOfBoundsException("srcIndex: " + srcIndex);
+ }
+
+ if (length != 0) {
+ if (src.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(src.memoryAddress() + srcIndex, addr(index), length);
+ } else if (src.hasArray()) {
+ PlatformDependent.copyMemory(src.array(), src.arrayOffset() + srcIndex, addr(index), length);
+ } else {
+ src.getBytes(srcIndex, this, index, length);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ checkIndex(index, length);
+ if (length != 0) {
+ PlatformDependent.copyMemory(src, srcIndex, addr(index), length);
+ }
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ ensureAccessible();
+ ByteBuffer tmpBuf = internalNioBuffer();
+ if (src == tmpBuf) {
+ src = src.duplicate();
+ }
+
+ tmpBuf.clear().position(index).limit(index + src.remaining());
+ tmpBuf.put(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ ensureAccessible();
+ if (length != 0) {
+ byte[] tmp = new byte[length];
+ PlatformDependent.copyMemory(addr(index), tmp, 0, length);
+ out.write(tmp);
+ }
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return getBytes(index, out, length, false);
+ }
+
+ private int getBytes(int index, GatheringByteChannel out, int length, boolean internal) throws IOException {
+ ensureAccessible();
+ if (length == 0) {
+ return 0;
+ }
+
+ ByteBuffer tmpBuf;
+ if (internal) {
+ tmpBuf = internalNioBuffer();
+ } else {
+ tmpBuf = buffer.duplicate();
+ }
+ tmpBuf.clear().position(index).limit(index + length);
+ return out.write(tmpBuf);
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ checkReadableBytes(length);
+ int readBytes = getBytes(readerIndex, out, length, true);
+ readerIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ checkIndex(index, length);
+ byte[] tmp = new byte[length];
+ int readBytes = in.read(tmp);
+ if (readBytes > 0) {
+ PlatformDependent.copyMemory(tmp, 0, addr(index), readBytes);
+ }
+ return readBytes;
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ ensureAccessible();
+ ByteBuffer tmpBuf = internalNioBuffer();
+ tmpBuf.clear().position(index).limit(index + length);
+ try {
+ return in.read(tmpBuf);
+ } catch (ClosedChannelException ignored) {
+ return -1;
+ }
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return new ByteBuffer[] { nioBuffer(index, length) };
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ checkIndex(index, length);
+ ByteBuf copy = alloc().directBuffer(length, maxCapacity());
+ if (length != 0) {
+ if (copy.hasMemoryAddress()) {
+ PlatformDependent.copyMemory(addr(index), copy.memoryAddress(), length);
+ copy.setIndex(0, length);
+ } else {
+ copy.writeBytes(this, index, length);
+ }
+ }
+ return copy;
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return (ByteBuffer) internalNioBuffer().clear().position(index).limit(index + length);
+ }
+
+ private ByteBuffer internalNioBuffer() {
+ ByteBuffer tmpNioBuf = this.tmpNioBuf;
+ if (tmpNioBuf == null) {
+ this.tmpNioBuf = tmpNioBuf = buffer.duplicate();
+ }
+ return tmpNioBuf;
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ checkIndex(index, length);
+ return ((ByteBuffer) buffer.duplicate().position(index).limit(index + length)).slice();
+ }
+
+ @Override
+ protected void deallocate() {
+ ByteBuffer buffer = this.buffer;
+ if (buffer == null) {
+ return;
+ }
+
+ this.buffer = null;
+
+ if (!doNotFree) {
+ freeDirect(buffer);
+ }
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return null;
+ }
+
+ long addr(int index) {
+ return memoryAddress + index;
+ }
+
+ @Override
+ protected SwappedByteBuf newSwappedByteBuf() {
+ return new UnsafeDirectSwappedByteBuf(this);
+ }
+}
diff --git a/common/src/common/net/buffer/UnreleasableByteBuf.java b/common/src/common/net/buffer/UnreleasableByteBuf.java
new file mode 100644
index 0000000..3a677ba
--- /dev/null
+++ b/common/src/common/net/buffer/UnreleasableByteBuf.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.buffer;
+
+import java.nio.ByteOrder;
+
+/**
+ * A {@link ByteBuf} implementation that wraps another buffer to prevent a user from increasing or decreasing the
+ * wrapped buffer's reference count.
+ */
+final class UnreleasableByteBuf extends WrappedByteBuf {
+
+ private SwappedByteBuf swappedBuf;
+
+ UnreleasableByteBuf(ByteBuf buf) {
+ super(buf);
+ }
+
+ @Override
+ public ByteBuf order(ByteOrder endianness) {
+ if (endianness == null) {
+ throw new NullPointerException("endianness");
+ }
+ if (endianness == order()) {
+ return this;
+ }
+
+ SwappedByteBuf swappedBuf = this.swappedBuf;
+ if (swappedBuf == null) {
+ this.swappedBuf = swappedBuf = new SwappedByteBuf(this);
+ }
+ return swappedBuf;
+ }
+
+ @Override
+ public ByteBuf readSlice(int length) {
+ return new UnreleasableByteBuf(buf.readSlice(length));
+ }
+
+ @Override
+ public ByteBuf slice() {
+ return new UnreleasableByteBuf(buf.slice());
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return new UnreleasableByteBuf(buf.slice(index, length));
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return new UnreleasableByteBuf(buf.duplicate());
+ }
+
+ @Override
+ public ByteBuf retain(int increment) {
+ return this;
+ }
+
+ @Override
+ public ByteBuf retain() {
+ return this;
+ }
+
+ @Override
+ public boolean release() {
+ return false;
+ }
+
+ @Override
+ public boolean release(int decrement) {
+ return false;
+ }
+}
diff --git a/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java b/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java
new file mode 100644
index 0000000..8d5f55c
--- /dev/null
+++ b/common/src/common/net/buffer/UnsafeDirectSwappedByteBuf.java
@@ -0,0 +1,186 @@
+/*
+* Copyright 2014 The Netty Project
+*
+* The Netty Project licenses this file to you under the Apache License,
+* version 2.0 (the "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at:
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+* License for the specific language governing permissions and limitations
+* under the License.
+*/
+
+package common.net.buffer;
+
+import java.nio.ByteOrder;
+
+import common.net.util.internal.PlatformDependent;
+
+/**
+ * Special {@link SwappedByteBuf} for {@link ByteBuf}s that are backed by a {@code memoryAddress}.
+ */
+final class UnsafeDirectSwappedByteBuf extends SwappedByteBuf {
+ private static final boolean NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
+ private final boolean nativeByteOrder;
+ private final AbstractByteBuf wrapped;
+
+ UnsafeDirectSwappedByteBuf(AbstractByteBuf buf) {
+ super(buf);
+ wrapped = buf;
+ nativeByteOrder = NATIVE_ORDER == (order() == ByteOrder.BIG_ENDIAN);
+ }
+
+ private long addr(int index) {
+ // We need to call wrapped.memoryAddress() everytime and NOT cache it as it may change if the buffer expand.
+ // See:
+ // - https://github.com/netty/netty/issues/2587
+ // - https://github.com/netty/netty/issues/2580
+ return wrapped.memoryAddress() + index;
+ }
+
+ @Override
+ public long getLong(int index) {
+ wrapped.checkIndex(index, 8);
+ long v = PlatformDependent.getLong(addr(index));
+ return nativeByteOrder? v : Long.reverseBytes(v);
+ }
+
+ @Override
+ public float getFloat(int index) {
+ return Float.intBitsToFloat(getInt(index));
+ }
+
+ @Override
+ public double getDouble(int index) {
+ return Double.longBitsToDouble(getLong(index));
+ }
+
+ @Override
+ public char getChar(int index) {
+ return (char) getShort(index);
+ }
+
+ @Override
+ public long getUnsignedInt(int index) {
+ return getInt(index) & 0xFFFFFFFFL;
+ }
+
+ @Override
+ public int getInt(int index) {
+ wrapped.checkIndex(index, 4);
+ int v = PlatformDependent.getInt(addr(index));
+ return nativeByteOrder? v : Integer.reverseBytes(v);
+ }
+
+ @Override
+ public int getUnsignedShort(int index) {
+ return getShort(index) & 0xFFFF;
+ }
+
+ @Override
+ public short getShort(int index) {
+ wrapped.checkIndex(index, 2);
+ short v = PlatformDependent.getShort(addr(index));
+ return nativeByteOrder? v : Short.reverseBytes(v);
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ wrapped.checkIndex(index, 2);
+ _setShort(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ wrapped.checkIndex(index, 4);
+ _setInt(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ wrapped.checkIndex(index, 8);
+ _setLong(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setChar(int index, int value) {
+ setShort(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setFloat(int index, float value) {
+ setInt(index, Float.floatToRawIntBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf setDouble(int index, double value) {
+ setLong(index, Double.doubleToRawLongBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeShort(int value) {
+ wrapped.ensureAccessible();
+ wrapped.ensureWritable(2);
+ _setShort(wrapped.writerIndex, value);
+ wrapped.writerIndex += 2;
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeInt(int value) {
+ wrapped.ensureAccessible();
+ wrapped.ensureWritable(4);
+ _setInt(wrapped.writerIndex, value);
+ wrapped.writerIndex += 4;
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeLong(long value) {
+ wrapped.ensureAccessible();
+ wrapped.ensureWritable(8);
+ _setLong(wrapped.writerIndex, value);
+ wrapped.writerIndex += 8;
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeChar(int value) {
+ writeShort(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeFloat(float value) {
+ writeInt(Float.floatToRawIntBits(value));
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeDouble(double value) {
+ writeLong(Double.doubleToRawLongBits(value));
+ return this;
+ }
+
+ private void _setShort(int index, int value) {
+ PlatformDependent.putShort(addr(index), nativeByteOrder ? (short) value : Short.reverseBytes((short) value));
+ }
+
+ private void _setInt(int index, int value) {
+ PlatformDependent.putInt(addr(index), nativeByteOrder ? value : Integer.reverseBytes(value));
+ }
+
+ private void _setLong(int index, long value) {
+ PlatformDependent.putLong(addr(index), nativeByteOrder ? value : Long.reverseBytes(value));
+ }
+}
diff --git a/common/src/common/net/buffer/WrappedByteBuf.java b/common/src/common/net/buffer/WrappedByteBuf.java
new file mode 100644
index 0000000..c135c77
--- /dev/null
+++ b/common/src/common/net/buffer/WrappedByteBuf.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright 2013 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package common.net.buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.ScatteringByteChannel;
+import java.nio.charset.Charset;
+
+import common.net.util.internal.StringUtil;
+
+class WrappedByteBuf extends ByteBuf {
+
+ protected final ByteBuf buf;
+
+ protected WrappedByteBuf(ByteBuf buf) {
+ if (buf == null) {
+ throw new NullPointerException("buf");
+ }
+ this.buf = buf;
+ }
+
+ @Override
+ public boolean hasMemoryAddress() {
+ return buf.hasMemoryAddress();
+ }
+
+ @Override
+ public long memoryAddress() {
+ return buf.memoryAddress();
+ }
+
+ @Override
+ public int capacity() {
+ return buf.capacity();
+ }
+
+ @Override
+ public ByteBuf capacity(int newCapacity) {
+ buf.capacity(newCapacity);
+ return this;
+ }
+
+ @Override
+ public int maxCapacity() {
+ return buf.maxCapacity();
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return buf.alloc();
+ }
+
+ @Override
+ public ByteOrder order() {
+ return buf.order();
+ }
+
+ @Override
+ public ByteBuf order(ByteOrder endianness) {
+ return buf.order(endianness);
+ }
+
+ @Override
+ public ByteBuf unwrap() {
+ return buf;
+ }
+
+ @Override
+ public boolean isDirect() {
+ return buf.isDirect();
+ }
+
+ @Override
+ public int readerIndex() {
+ return buf.readerIndex();
+ }
+
+ @Override
+ public ByteBuf readerIndex(int readerIndex) {
+ buf.readerIndex(readerIndex);
+ return this;
+ }
+
+ @Override
+ public int writerIndex() {
+ return buf.writerIndex();
+ }
+
+ @Override
+ public ByteBuf writerIndex(int writerIndex) {
+ buf.writerIndex(writerIndex);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setIndex(int readerIndex, int writerIndex) {
+ buf.setIndex(readerIndex, writerIndex);
+ return this;
+ }
+
+ @Override
+ public int readableBytes() {
+ return buf.readableBytes();
+ }
+
+ @Override
+ public int writableBytes() {
+ return buf.writableBytes();
+ }
+
+ @Override
+ public int maxWritableBytes() {
+ return buf.maxWritableBytes();
+ }
+
+ @Override
+ public boolean isReadable() {
+ return buf.isReadable();
+ }
+
+ @Override
+ public boolean isWritable() {
+ return buf.isWritable();
+ }
+
+ @Override
+ public ByteBuf clear() {
+ buf.clear();
+ return this;
+ }
+
+ @Override
+ public ByteBuf markReaderIndex() {
+ buf.markReaderIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetReaderIndex() {
+ buf.resetReaderIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf markWriterIndex() {
+ buf.markWriterIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf resetWriterIndex() {
+ buf.resetWriterIndex();
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardReadBytes() {
+ buf.discardReadBytes();
+ return this;
+ }
+
+ @Override
+ public ByteBuf discardSomeReadBytes() {
+ buf.discardSomeReadBytes();
+ return this;
+ }
+
+ @Override
+ public ByteBuf ensureWritable(int minWritableBytes) {
+ buf.ensureWritable(minWritableBytes);
+ return this;
+ }
+
+ @Override
+ public int ensureWritable(int minWritableBytes, boolean force) {
+ return buf.ensureWritable(minWritableBytes, force);
+ }
+
+ @Override
+ public boolean getBoolean(int index) {
+ return buf.getBoolean(index);
+ }
+
+ @Override
+ public byte getByte(int index) {
+ return buf.getByte(index);
+ }
+
+ @Override
+ public short getUnsignedByte(int index) {
+ return buf.getUnsignedByte(index);
+ }
+
+ @Override
+ public short getShort(int index) {
+ return buf.getShort(index);
+ }
+
+ @Override
+ public int getUnsignedShort(int index) {
+ return buf.getUnsignedShort(index);
+ }
+
+ @Override
+ public int getMedium(int index) {
+ return buf.getMedium(index);
+ }
+
+ @Override
+ public int getUnsignedMedium(int index) {
+ return buf.getUnsignedMedium(index);
+ }
+
+ @Override
+ public int getInt(int index) {
+ return buf.getInt(index);
+ }
+
+ @Override
+ public long getUnsignedInt(int index) {
+ return buf.getUnsignedInt(index);
+ }
+
+ @Override
+ public long getLong(int index) {
+ return buf.getLong(index);
+ }
+
+ @Override
+ public char getChar(int index) {
+ return buf.getChar(index);
+ }
+
+ @Override
+ public float getFloat(int index) {
+ return buf.getFloat(index);
+ }
+
+ @Override
+ public double getDouble(int index) {
+ return buf.getDouble(index);
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int length) {
+ buf.getBytes(index, dst, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
+ buf.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
+ buf.getBytes(index, dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, ByteBuffer dst) {
+ buf.getBytes(index, dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf getBytes(int index, OutputStream out, int length) throws IOException {
+ buf.getBytes(index, out, length);
+ return this;
+ }
+
+ @Override
+ public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
+ return buf.getBytes(index, out, length);
+ }
+
+ @Override
+ public ByteBuf setBoolean(int index, boolean value) {
+ buf.setBoolean(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setByte(int index, int value) {
+ buf.setByte(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setShort(int index, int value) {
+ buf.setShort(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setMedium(int index, int value) {
+ buf.setMedium(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setInt(int index, int value) {
+ buf.setInt(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setLong(int index, long value) {
+ buf.setLong(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setChar(int index, int value) {
+ buf.setChar(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setFloat(int index, float value) {
+ buf.setFloat(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setDouble(int index, double value) {
+ buf.setDouble(index, value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int length) {
+ buf.setBytes(index, src, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
+ buf.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
+ buf.setBytes(index, src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf setBytes(int index, ByteBuffer src) {
+ buf.setBytes(index, src);
+ return this;
+ }
+
+ @Override
+ public int setBytes(int index, InputStream in, int length) throws IOException {
+ return buf.setBytes(index, in, length);
+ }
+
+ @Override
+ public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
+ return buf.setBytes(index, in, length);
+ }
+
+ @Override
+ public ByteBuf setZero(int index, int length) {
+ buf.setZero(index, length);
+ return this;
+ }
+
+ @Override
+ public boolean readBoolean() {
+ return buf.readBoolean();
+ }
+
+ @Override
+ public byte readByte() {
+ return buf.readByte();
+ }
+
+ @Override
+ public short readUnsignedByte() {
+ return buf.readUnsignedByte();
+ }
+
+ @Override
+ public short readShort() {
+ return buf.readShort();
+ }
+
+ @Override
+ public int readUnsignedShort() {
+ return buf.readUnsignedShort();
+ }
+
+ @Override
+ public int readMedium() {
+ return buf.readMedium();
+ }
+
+ @Override
+ public int readUnsignedMedium() {
+ return buf.readUnsignedMedium();
+ }
+
+ @Override
+ public int readInt() {
+ return buf.readInt();
+ }
+
+ @Override
+ public long readUnsignedInt() {
+ return buf.readUnsignedInt();
+ }
+
+ @Override
+ public long readLong() {
+ return buf.readLong();
+ }
+
+ @Override
+ public char readChar() {
+ return buf.readChar();
+ }
+
+ @Override
+ public float readFloat() {
+ return buf.readFloat();
+ }
+
+ @Override
+ public double readDouble() {
+ return buf.readDouble();
+ }
+
+ @Override
+ public ByteBuf readBytes(int length) {
+ return buf.readBytes(length);
+ }
+
+ @Override
+ public ByteBuf readSlice(int length) {
+ return buf.readSlice(length);
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int length) {
+ buf.readBytes(dst, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
+ buf.readBytes(dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
+ buf.readBytes(dst, dstIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(ByteBuffer dst) {
+ buf.readBytes(dst);
+ return this;
+ }
+
+ @Override
+ public ByteBuf readBytes(OutputStream out, int length) throws IOException {
+ buf.readBytes(out, length);
+ return this;
+ }
+
+ @Override
+ public int readBytes(GatheringByteChannel out, int length) throws IOException {
+ return buf.readBytes(out, length);
+ }
+
+ @Override
+ public ByteBuf skipBytes(int length) {
+ buf.skipBytes(length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBoolean(boolean value) {
+ buf.writeBoolean(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeByte(int value) {
+ buf.writeByte(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeShort(int value) {
+ buf.writeShort(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeMedium(int value) {
+ buf.writeMedium(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeInt(int value) {
+ buf.writeInt(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeLong(long value) {
+ buf.writeLong(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeChar(int value) {
+ buf.writeChar(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeFloat(float value) {
+ buf.writeFloat(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeDouble(double value) {
+ buf.writeDouble(value);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int length) {
+ buf.writeBytes(src, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
+ buf.writeBytes(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
+ buf.writeBytes(src, srcIndex, length);
+ return this;
+ }
+
+ @Override
+ public ByteBuf writeBytes(ByteBuffer src) {
+ buf.writeBytes(src);
+ return this;
+ }
+
+ @Override
+ public int writeBytes(InputStream in, int length) throws IOException {
+ return buf.writeBytes(in, length);
+ }
+
+ @Override
+ public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
+ return buf.writeBytes(in, length);
+ }
+
+ @Override
+ public ByteBuf writeZero(int length) {
+ buf.writeZero(length);
+ return this;
+ }
+
+ @Override
+ public int indexOf(int fromIndex, int toIndex, byte value) {
+ return buf.indexOf(fromIndex, toIndex, value);
+ }
+
+ @Override
+ public int bytesBefore(byte value) {
+ return buf.bytesBefore(value);
+ }
+
+ @Override
+ public int bytesBefore(int length, byte value) {
+ return buf.bytesBefore(length, value);
+ }
+
+ @Override
+ public int bytesBefore(int index, int length, byte value) {
+ return buf.bytesBefore(index, length, value);
+ }
+
+ @Override
+ public int forEachByte(ByteBufProcessor processor) {
+ return buf.forEachByte(processor);
+ }
+
+ @Override
+ public int forEachByte(int index, int length, ByteBufProcessor processor) {
+ return buf.forEachByte(index, length, processor);
+ }
+
+ @Override
+ public int forEachByteDesc(ByteBufProcessor processor) {
+ return buf.forEachByteDesc(processor);
+ }
+
+ @Override
+ public int forEachByteDesc(int index, int length, ByteBufProcessor processor) {
+ return buf.forEachByteDesc(index, length, processor);
+ }
+
+ @Override
+ public ByteBuf copy() {
+ return buf.copy();
+ }
+
+ @Override
+ public ByteBuf copy(int index, int length) {
+ return buf.copy(index, length);
+ }
+
+ @Override
+ public ByteBuf slice() {
+ return buf.slice();
+ }
+
+ @Override
+ public ByteBuf slice(int index, int length) {
+ return buf.slice(index, length);
+ }
+
+ @Override
+ public ByteBuf duplicate() {
+ return buf.duplicate();
+ }
+
+ @Override
+ public int nioBufferCount() {
+ return buf.nioBufferCount();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer() {
+ return buf.nioBuffer();
+ }
+
+ @Override
+ public ByteBuffer nioBuffer(int index, int length) {
+ return buf.nioBuffer(index, length);
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers() {
+ return buf.nioBuffers();
+ }
+
+ @Override
+ public ByteBuffer[] nioBuffers(int index, int length) {
+ return buf.nioBuffers(index, length);
+ }
+
+ @Override
+ public ByteBuffer internalNioBuffer(int index, int length) {
+ return buf.internalNioBuffer(index, length);
+ }
+
+ @Override
+ public boolean hasArray() {
+ return buf.hasArray();
+ }
+
+ @Override
+ public byte[] array() {
+ return buf.array();
+ }
+
+ @Override
+ public int arrayOffset() {
+ return buf.arrayOffset();
+ }
+
+ @Override
+ public String toString(Charset charset) {
+ return buf.toString(charset);
+ }
+
+ @Override
+ public String toString(int index, int length, Charset charset) {
+ return buf.toString(index, length, charset);
+ }
+
+ @Override
+ public int hashCode() {
+ return buf.hashCode();
+ }
+
+ @Override
+
+ public boolean equals(Object obj) {
+ return buf.equals(obj);
+ }
+
+ @Override
+ public int compareTo(ByteBuf buffer) {
+ return buf.compareTo(buffer);
+ }
+
+ @Override
+ public String toString() {
+ return StringUtil.simpleClassName(this) + '(' + buf.toString() + ')';
+ }
+
+ @Override
+ public ByteBuf retain(int increment) {
+ buf.retain(increment);
+ return this;
+ }
+
+ @Override
+ public ByteBuf retain() {
+ buf.retain();
+ return this;
+ }
+
+ @Override
+ public boolean isReadable(int size) {
+ return buf.isReadable(size);
+ }
+
+ @Override
+ public boolean isWritable(int size) {
+ return buf.isWritable(size);
+ }
+
+ @Override
+ public int refCnt() {
+ return buf.refCnt();
+ }
+
+ @Override
+ public boolean release() {
+ return buf.release();
+ }
+
+ @Override
+ public boolean release(int decrement) {
+ return buf.release(decrement);
+ }
+}
diff --git a/common/src/common/net/channel/AbstractChannel.java b/common/src/common/net/channel/AbstractChannel.java
new file mode 100644
index 0000000..031f273
--- /dev/null
+++ b/common/src/common/net/channel/AbstractChannel.java
@@ -0,0 +1,873 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.channel;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetConnectedException;
+import java.util.concurrent.RejectedExecutionException;
+
+import common.net.buffer.ByteBufAllocator;
+import common.net.util.DefaultAttributeMap;
+import common.net.util.ReferenceCountUtil;
+import common.net.util.internal.EmptyArrays;
+import common.net.util.internal.OneTimeTask;
+import common.net.util.internal.PlatformDependent;
+import common.net.util.internal.ThreadLocalRandom;
+import common.net.util.internal.logging.InternalLogger;
+import common.net.util.internal.logging.InternalLoggerFactory;
+
+/**
+ * A skeletal {@link Channel} implementation.
+ */
+public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
+
+ private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);
+
+ static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
+ static final NotYetConnectedException NOT_YET_CONNECTED_EXCEPTION = new NotYetConnectedException();
+
+ static {
+ CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
+ NOT_YET_CONNECTED_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
+ }
+
+ private MessageSizeEstimator.Handle estimatorHandle;
+
+ private final Channel parent;
+ private final long hashCode = ThreadLocalRandom.current().nextLong();
+ private final Unsafe unsafe;
+ private final DefaultChannelPipeline pipeline;
+ private final ChannelFuture succeededFuture = new SucceededChannelFuture(this, null);
+ private final VoidChannelPromise voidPromise = new VoidChannelPromise(this, true);
+ private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
+ private final CloseFuture closeFuture = new CloseFuture(this);
+
+ private volatile SocketAddress localAddress;
+ private volatile SocketAddress remoteAddress;
+ private volatile EventLoop eventLoop;
+ private volatile boolean registered;
+
+ /** Cache for the string representation of this channel */
+ private boolean strValActive;
+ private String strVal;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param parent
+ * the parent of this channel. {@code null} if there's no parent.
+ */
+ protected AbstractChannel(Channel parent) {
+ this.parent = parent;
+ unsafe = newUnsafe();
+ pipeline = new DefaultChannelPipeline(this);
+ }
+
+ @Override
+ public boolean isWritable() {
+ ChannelOutboundBuffer buf = unsafe.outboundBuffer();
+ return buf != null && buf.isWritable();
+ }
+
+ @Override
+ public Channel parent() {
+ return parent;
+ }
+
+ @Override
+ public ChannelPipeline pipeline() {
+ return pipeline;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return config().getAllocator();
+ }
+
+ @Override
+ public EventLoop eventLoop() {
+ EventLoop eventLoop = this.eventLoop;
+ if (eventLoop == null) {
+ throw new IllegalStateException("channel not registered to an event loop");
+ }
+ return eventLoop;
+ }
+
+ @Override
+ public SocketAddress localAddress() {
+ SocketAddress localAddress = this.localAddress;
+ if (localAddress == null) {
+ try {
+ this.localAddress = localAddress = unsafe().localAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return localAddress;
+ }
+
+ protected void invalidateLocalAddress() {
+ localAddress = null;
+ }
+
+ @Override
+ public SocketAddress remoteAddress() {
+ SocketAddress remoteAddress = this.remoteAddress;
+ if (remoteAddress == null) {
+ try {
+ this.remoteAddress = remoteAddress = unsafe().remoteAddress();
+ } catch (Throwable t) {
+ // Sometimes fails on a closed socket in Windows.
+ return null;
+ }
+ }
+ return remoteAddress;
+ }
+
+ /**
+ * Reset the stored remoteAddress
+ */
+ protected void invalidateRemoteAddress() {
+ remoteAddress = null;
+ }
+
+ @Override
+ public boolean isRegistered() {
+ return registered;
+ }
+
+ @Override
+ public ChannelFuture bind(SocketAddress localAddress) {
+ return pipeline.bind(localAddress);
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress) {
+ return pipeline.connect(remoteAddress);
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
+ return pipeline.connect(remoteAddress, localAddress);
+ }
+
+ @Override
+ public ChannelFuture disconnect() {
+ return pipeline.disconnect();
+ }
+
+ @Override
+ public ChannelFuture close() {
+ return pipeline.close();
+ }
+
+ @Override
+ public ChannelFuture deregister() {
+ return pipeline.deregister();
+ }
+
+ @Override
+ public Channel flush() {
+ pipeline.flush();
+ return this;
+ }
+
+ @Override
+ public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
+ return pipeline.bind(localAddress, promise);
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
+ return pipeline.connect(remoteAddress, promise);
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
+ return pipeline.connect(remoteAddress, localAddress, promise);
+ }
+
+ @Override
+ public ChannelFuture disconnect(ChannelPromise promise) {
+ return pipeline.disconnect(promise);
+ }
+
+ @Override
+ public ChannelFuture close(ChannelPromise promise) {
+ return pipeline.close(promise);
+ }
+
+ @Override
+ public ChannelFuture deregister(ChannelPromise promise) {
+ return pipeline.deregister(promise);
+ }
+
+ @Override
+ public Channel read() {
+ pipeline.read();
+ return this;
+ }
+
+ @Override
+ public ChannelFuture write(Object msg) {
+ return pipeline.write(msg);
+ }
+
+ @Override
+ public ChannelFuture write(Object msg, ChannelPromise promise) {
+ return pipeline.write(msg, promise);
+ }
+
+ @Override
+ public ChannelFuture writeAndFlush(Object msg) {
+ return pipeline.writeAndFlush(msg);
+ }
+
+ @Override
+ public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
+ return pipeline.writeAndFlush(msg, promise);
+ }
+
+ @Override
+ public ChannelPromise newPromise() {
+ return new DefaultChannelPromise(this);
+ }
+
+// @Override
+// public ChannelProgressivePromise newProgressivePromise() {
+// return new DefaultChannelProgressivePromise(this);
+// }
+
+ @Override
+ public ChannelFuture newSucceededFuture() {
+ return succeededFuture;
+ }
+
+ @Override
+ public ChannelFuture newFailedFuture(Throwable cause) {
+ return new FailedChannelFuture(this, null, cause);
+ }
+
+ @Override
+ public ChannelFuture closeFuture() {
+ return closeFuture;
+ }
+
+ @Override
+ public Unsafe unsafe() {
+ return unsafe;
+ }
+
+ /**
+ * Create a new {@link AbstractUnsafe} instance which will be used for the life-time of the {@link Channel}
+ */
+ protected abstract AbstractUnsafe newUnsafe();
+
+ /**
+ * Returns the ID of this channel.
+ */
+ @Override
+ public final int hashCode() {
+ return (int) hashCode;
+ }
+
+ /**
+ * Returns {@code true} if and only if the specified object is identical
+ * with this channel (i.e: {@code this == o}).
+ */
+ @Override
+ public final boolean equals(Object o) {
+ return this == o;
+ }
+
+ @Override
+ public final int compareTo(Channel o) {
+ if (this == o) {
+ return 0;
+ }
+
+ long ret = hashCode - o.hashCode();
+ if (ret > 0) {
+ return 1;
+ }
+ if (ret < 0) {
+ return -1;
+ }
+
+ ret = System.identityHashCode(this) - System.identityHashCode(o);
+ if (ret != 0) {
+ return (int) ret;
+ }
+
+ // Jackpot! - different objects with same hashes
+ throw new Error();
+ }
+
+ /**
+ * Returns the {@link String} representation of this channel. The returned
+ * string contains the {@linkplain #hashCode()} ID}, {@linkplain #localAddress() local address},
+ * and {@linkplain #remoteAddress() remote address} of this channel for
+ * easier identification.
+ */
+ @Override
+ public String toString() {
+ boolean active = isActive();
+ if (strValActive == active && strVal != null) {
+ return strVal;
+ }
+
+ SocketAddress remoteAddr = remoteAddress();
+ SocketAddress localAddr = localAddress();
+ if (remoteAddr != null) {
+ SocketAddress srcAddr;
+ SocketAddress dstAddr;
+ if (parent == null) {
+ srcAddr = localAddr;
+ dstAddr = remoteAddr;
+ } else {
+ srcAddr = remoteAddr;
+ dstAddr = localAddr;
+ }
+ strVal = String.format("[id: 0x%08x, %s %s %s]", (int) hashCode, srcAddr, active? "=>" : ":>", dstAddr);
+ } else if (localAddr != null) {
+ strVal = String.format("[id: 0x%08x, %s]", (int) hashCode, localAddr);
+ } else {
+ strVal = String.format("[id: 0x%08x]", (int) hashCode);
+ }
+
+ strValActive = active;
+ return strVal;
+ }
+
+ @Override
+ public final ChannelPromise voidPromise() {
+ return voidPromise;
+ }
+
+ final MessageSizeEstimator.Handle estimatorHandle() {
+ if (estimatorHandle == null) {
+ estimatorHandle = config().getMessageSizeEstimator().newHandle();
+ }
+ return estimatorHandle;
+ }
+
+ /**
+ * {@link Unsafe} implementation which sub-classes must extend and use.
+ */
+ protected abstract class AbstractUnsafe implements Unsafe {
+
+ private ChannelOutboundBuffer outboundBuffer = new ChannelOutboundBuffer(AbstractChannel.this);
+ private boolean inFlush0;
+
+ @Override
+ public final ChannelOutboundBuffer outboundBuffer() {
+ return outboundBuffer;
+ }
+
+ @Override
+ public final SocketAddress localAddress() {
+ return localAddress0();
+ }
+
+ @Override
+ public final SocketAddress remoteAddress() {
+ return remoteAddress0();
+ }
+
+ @Override
+ public final void register(EventLoop eventLoop, final ChannelPromise promise) {
+ if (eventLoop == null) {
+ throw new NullPointerException("eventLoop");
+ }
+ if (isRegistered()) {
+ promise.setFailure(new IllegalStateException("registered to an event loop already"));
+ return;
+ }
+ if (!isCompatible(eventLoop)) {
+ promise.setFailure(
+ new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
+ return;
+ }
+
+ AbstractChannel.this.eventLoop = eventLoop;
+
+ if (eventLoop.inEventLoop()) {
+ register0(promise);
+ } else {
+ try {
+ eventLoop.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ register0(promise);
+ }
+ });
+ } catch (Throwable t) {
+ logger.warn(
+ "Force-closing a channel whose registration task was not accepted by an event loop: {}",
+ AbstractChannel.this, t);
+ closeForcibly();
+ closeFuture.setClosed();
+ safeSetFailure(promise, t);
+ }
+ }
+ }
+
+ private void register0(ChannelPromise promise) {
+ try {
+ // check if the channel is still open as it could be closed in the mean time when the register
+ // call was outside of the eventLoop
+ if (!promise.setUncancellable() || !ensureOpen(promise)) {
+ return;
+ }
+ doRegister();
+ registered = true;
+ safeSetSuccess(promise);
+ pipeline.fireChannelRegistered();
+ if (isActive()) {
+ pipeline.fireChannelActive();
+ }
+ } catch (Throwable t) {
+ // Close the channel directly to avoid FD leak.
+ closeForcibly();
+ closeFuture.setClosed();
+ safeSetFailure(promise, t);
+ }
+ }
+
+ @Override
+ public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
+ if (!promise.setUncancellable() || !ensureOpen(promise)) {
+ return;
+ }
+
+ // See: https://github.com/netty/netty/issues/576
+ if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
+ Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
+ localAddress instanceof InetSocketAddress &&
+ !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
+ // Warn a user about the fact that a non-root user can't receive a
+ // broadcast packet on *nix if the socket is bound on non-wildcard address.
+ logger.warn(
+ "A non-root user can't receive a broadcast packet if the socket " +
+ "is not bound to a wildcard address; binding to a non-wildcard " +
+ "address (" + localAddress + ") anyway as requested.");
+ }
+
+ boolean wasActive = isActive();
+ try {
+ doBind(localAddress);
+ } catch (Throwable t) {
+ safeSetFailure(promise, t);
+ closeIfClosed();
+ return;
+ }
+
+ if (!wasActive && isActive()) {
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ pipeline.fireChannelActive();
+ }
+ });
+ }
+
+ safeSetSuccess(promise);
+ }
+
+ @Override
+ public final void disconnect(final ChannelPromise promise) {
+ if (!promise.setUncancellable()) {
+ return;
+ }
+
+ boolean wasActive = isActive();
+ try {
+ doDisconnect();
+ } catch (Throwable t) {
+ safeSetFailure(promise, t);
+ closeIfClosed();
+ return;
+ }
+
+ if (wasActive && !isActive()) {
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ pipeline.fireChannelInactive();
+ }
+ });
+ }
+
+ safeSetSuccess(promise);
+ closeIfClosed(); // doDisconnect() might have closed the channel
+ }
+
+ @Override
+ public final void close(final ChannelPromise promise) {
+ if (!promise.setUncancellable()) {
+ return;
+ }
+
+ if (inFlush0) {
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ close(promise);
+ }
+ });
+ return;
+ }
+
+ if (closeFuture.isDone()) {
+ // Closed already.
+ safeSetSuccess(promise);
+ return;
+ }
+
+ boolean wasActive = isActive();
+ ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
+ this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
+
+ try {
+ doClose();
+ closeFuture.setClosed();
+ safeSetSuccess(promise);
+ } catch (Throwable t) {
+ closeFuture.setClosed();
+ safeSetFailure(promise, t);
+ }
+
+ // Fail all the queued messages
+ try {
+ outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION);
+ outboundBuffer.close(CLOSED_CHANNEL_EXCEPTION);
+ } finally {
+
+ if (wasActive && !isActive()) {
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ pipeline.fireChannelInactive();
+ }
+ });
+ }
+
+ deregister(voidPromise());
+ }
+ }
+
+ @Override
+ public final void closeForcibly() {
+ try {
+ doClose();
+ } catch (Exception e) {
+ logger.warn("Failed to close a channel.", e);
+ }
+ }
+
+ @Override
+ public final void deregister(final ChannelPromise promise) {
+ if (!promise.setUncancellable()) {
+ return;
+ }
+
+ if (!registered) {
+ safeSetSuccess(promise);
+ return;
+ }
+
+ try {
+ doDeregister();
+ } catch (Throwable t) {
+ logger.warn("Unexpected exception occurred while deregistering a channel.", t);
+ } finally {
+ if (registered) {
+ registered = false;
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ pipeline.fireChannelUnregistered();
+ }
+ });
+ safeSetSuccess(promise);
+ } else {
+ // Some transports like local and AIO does not allow the deregistration of
+ // an open channel. Their doDeregister() calls close(). Consequently,
+ // close() calls deregister() again - no need to fire channelUnregistered.
+ safeSetSuccess(promise);
+ }
+ }
+ }
+
+ @Override
+ public final void beginRead() {
+ if (!isActive()) {
+ return;
+ }
+
+ try {
+ doBeginRead();
+ } catch (final Exception e) {
+ invokeLater(new OneTimeTask() {
+ @Override
+ public void run() {
+ pipeline.fireExceptionCaught(e);
+ }
+ });
+ close(voidPromise());
+ }
+ }
+
+ @Override
+ public final void write(Object msg, ChannelPromise promise) {
+ ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
+ if (outboundBuffer == null) {
+ // If the outboundBuffer is null we know the channel was closed and so
+ // need to fail the future right away. If it is not null the handling of the rest
+ // will be done in flush0()
+ // See https://github.com/netty/netty/issues/2362
+ safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);
+ // release message now to prevent resource-leak
+ ReferenceCountUtil.release(msg);
+ return;
+ }
+
+ int size;
+ try {
+ msg = filterOutboundMessage(msg);
+ size = estimatorHandle().size(msg);
+ if (size < 0) {
+ size = 0;
+ }
+ } catch (Throwable t) {
+ safeSetFailure(promise, t);
+ ReferenceCountUtil.release(msg);
+ return;
+ }
+
+ outboundBuffer.addMessage(msg, size, promise);
+ }
+
+ @Override
+ public final void flush() {
+ ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
+ if (outboundBuffer == null) {
+ return;
+ }
+
+ outboundBuffer.addFlush();
+ flush0();
+ }
+
+ protected void flush0() {
+ if (inFlush0) {
+ // Avoid re-entrance
+ return;
+ }
+
+ final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
+ if (outboundBuffer == null || outboundBuffer.isEmpty()) {
+ return;
+ }
+
+ inFlush0 = true;
+
+ // Mark all pending write requests as failure if the channel is inactive.
+ if (!isActive()) {
+ try {
+ if (isOpen()) {
+ outboundBuffer.failFlushed(NOT_YET_CONNECTED_EXCEPTION);
+ } else {
+ outboundBuffer.failFlushed(CLOSED_CHANNEL_EXCEPTION);
+ }
+ } finally {
+ inFlush0 = false;
+ }
+ return;
+ }
+
+ try {
+ doWrite(outboundBuffer);
+ } catch (Throwable t) {
+ outboundBuffer.failFlushed(t);
+ if (t instanceof IOException && config().isAutoClose()) {
+ close(voidPromise());
+ }
+ } finally {
+ inFlush0 = false;
+ }
+ }
+
+ @Override
+ public final ChannelPromise voidPromise() {
+ return unsafeVoidPromise;
+ }
+
+ protected final boolean ensureOpen(ChannelPromise promise) {
+ if (isOpen()) {
+ return true;
+ }
+
+ safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);
+ return false;
+ }
+
+ /**
+ * Marks the specified {@code promise} as success. If the {@code promise} is done already, log a message.
+ */
+ protected final void safeSetSuccess(ChannelPromise promise) {
+ if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) {
+ logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
+ }
+ }
+
+ /**
+ * Marks the specified {@code promise} as failure. If the {@code promise} is done already, log a message.
+ */
+ protected final void safeSetFailure(ChannelPromise promise, Throwable cause) {
+ if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) {
+ logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause);
+ }
+ }
+
+ protected final void closeIfClosed() {
+ if (isOpen()) {
+ return;
+ }
+ close(voidPromise());
+ }
+
+ private void invokeLater(Runnable task) {
+ try {
+ // This method is used by outbound operation implementations to trigger an inbound event later.
+ // They do not trigger an inbound event immediately because an outbound operation might have been
+ // triggered by another inbound event handler method. If fired immediately, the call stack
+ // will look like this for example:
+ //
+ // handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
+ // -> handlerA.ctx.close()
+ // -> channel.unsafe.close()
+ // -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
+ //
+ // which means the execution of two inbound handler methods of the same handler overlap undesirably.
+ eventLoop().execute(task);
+ } catch (RejectedExecutionException e) {
+ logger.warn("Can't invoke task later as EventLoop rejected it", e);
+ }
+ }
+ }
+
+ /**
+ * Return {@code true} if the given {@link EventLoop} is compatible with this instance.
+ */
+ protected abstract boolean isCompatible(EventLoop loop);
+
+ /**
+ * Returns the {@link SocketAddress} which is bound locally.
+ */
+ protected abstract SocketAddress localAddress0();
+
+ /**
+ * Return the {@link SocketAddress} which the {@link Channel} is connected to.
+ */
+ protected abstract SocketAddress remoteAddress0();
+
+ /**
+ * Is called after the {@link Channel} is registered with its {@link EventLoop} as part of the register process.
+ *
+ * Sub-classes may override this method
+ */
+ protected void doRegister() throws Exception {
+ // NOOP
+ }
+
+ /**
+ * Bind the {@link Channel} to the {@link SocketAddress}
+ */
+ protected abstract void doBind(SocketAddress localAddress) throws Exception;
+
+ /**
+ * Disconnect this {@link Channel} from its remote peer
+ */
+ protected abstract void doDisconnect() throws Exception;
+
+ /**
+ * Close the {@link Channel}
+ */
+ protected abstract void doClose() throws Exception;
+
+ /**
+ * Deregister the {@link Channel} from its {@link EventLoop}.
+ *
+ * Sub-classes may override this method
+ */
+ protected void doDeregister() throws Exception {
+ // NOOP
+ }
+
+ /**
+ * Schedule a read operation.
+ */
+ protected abstract void doBeginRead() throws Exception;
+
+ /**
+ * Flush the content of the given buffer to the remote peer.
+ */
+ protected abstract void doWrite(ChannelOutboundBuffer in) throws Exception;
+
+ /**
+ * Invoked when a new message is added to a {@link ChannelOutboundBuffer} of this {@link AbstractChannel}, so that
+ * the {@link Channel} implementation converts the message to another. (e.g. heap buffer -> direct buffer)
+ */
+ protected Object filterOutboundMessage(Object msg) throws Exception {
+ return msg;
+ }
+
+ static final class CloseFuture extends DefaultChannelPromise {
+
+ CloseFuture(AbstractChannel ch) {
+ super(ch);
+ }
+
+ @Override
+ public ChannelPromise setSuccess() {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public ChannelPromise setFailure(Throwable cause) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public boolean trySuccess() {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public boolean tryFailure(Throwable cause) {
+ throw new IllegalStateException();
+ }
+
+ boolean setClosed() {
+ return super.trySuccess();
+ }
+ }
+}
diff --git a/common/src/common/net/channel/AbstractChannelHandlerContext.java b/common/src/common/net/channel/AbstractChannelHandlerContext.java
new file mode 100644
index 0000000..204bd7a
--- /dev/null
+++ b/common/src/common/net/channel/AbstractChannelHandlerContext.java
@@ -0,0 +1,1000 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.channel;
+
+import static common.net.channel.DefaultChannelPipeline.logger;
+
+import java.net.SocketAddress;
+
+import common.net.buffer.ByteBufAllocator;
+import common.net.util.DefaultAttributeMap;
+import common.net.util.Recycler;
+import common.net.util.ReferenceCountUtil;
+import common.net.util.concurrent.EventExecutor;
+import common.net.util.concurrent.EventExecutorGroup;
+import common.net.util.internal.OneTimeTask;
+import common.net.util.internal.RecyclableMpscLinkedQueueNode;
+import common.net.util.internal.StringUtil;
+
+abstract class AbstractChannelHandlerContext extends DefaultAttributeMap implements ChannelHandlerContext {
+
+ volatile AbstractChannelHandlerContext next;
+ volatile AbstractChannelHandlerContext prev;
+
+ private final boolean inbound;
+ private final boolean outbound;
+ private final AbstractChannel channel;
+ private final DefaultChannelPipeline pipeline;
+ private final String name;
+ private boolean removed;
+
+ // Will be set to null if no child executor should be used, otherwise it will be set to the
+ // child executor.
+ final EventExecutor executor;
+ private ChannelFuture succeededFuture;
+
+ // Lazily instantiated tasks used to trigger events to a handler with different executor.
+ // These needs to be volatile as otherwise an other Thread may see an half initialized instance.
+ // See the JMM for more details
+ private volatile Runnable invokeChannelReadCompleteTask;
+ private volatile Runnable invokeReadTask;
+ private volatile Runnable invokeChannelWritableStateChangedTask;
+ private volatile Runnable invokeFlushTask;
+
+ AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutorGroup group, String name,
+ boolean inbound, boolean outbound) {
+
+ if (name == null) {
+ throw new NullPointerException("name");
+ }
+
+ channel = pipeline.channel;
+ this.pipeline = pipeline;
+ this.name = name;
+
+ if (group != null) {
+ // Pin one of the child executors once and remember it so that the same child executor
+ // is used to fire events for the same channel.
+ EventExecutor childExecutor = pipeline.childExecutors.get(group);
+ if (childExecutor == null) {
+ childExecutor = group.next();
+ pipeline.childExecutors.put(group, childExecutor);
+ }
+ executor = childExecutor;
+ } else {
+ executor = null;
+ }
+
+ this.inbound = inbound;
+ this.outbound = outbound;
+ }
+
+ /** Invocation initiated by {@link DefaultChannelPipeline#teardownAll()}}. */
+ void teardown() {
+ EventExecutor executor = executor();
+ if (executor.inEventLoop()) {
+ teardown0();
+ } else {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ teardown0();
+ }
+ });
+ }
+ }
+
+ private void teardown0() {
+ AbstractChannelHandlerContext prev = this.prev;
+ if (prev != null) {
+ synchronized (pipeline) {
+ pipeline.remove0(this);
+ }
+ prev.teardown();
+ }
+ }
+
+ @Override
+ public Channel channel() {
+ return channel;
+ }
+
+ @Override
+ public ChannelPipeline pipeline() {
+ return pipeline;
+ }
+
+ @Override
+ public ByteBufAllocator alloc() {
+ return channel().config().getAllocator();
+ }
+
+ @Override
+ public EventExecutor executor() {
+ if (executor == null) {
+ return channel().eventLoop();
+ } else {
+ return executor;
+ }
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelRegistered() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelRegistered();
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeChannelRegistered();
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeChannelRegistered() {
+ try {
+ ((ChannelInboundHandler) handler()).channelRegistered(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelUnregistered() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelUnregistered();
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeChannelUnregistered();
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeChannelUnregistered() {
+ try {
+ ((ChannelInboundHandler) handler()).channelUnregistered(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelActive() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelActive();
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeChannelActive();
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeChannelActive() {
+ try {
+ ((ChannelInboundHandler) handler()).channelActive(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelInactive() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelInactive();
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeChannelInactive();
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeChannelInactive() {
+ try {
+ ((ChannelInboundHandler) handler()).channelInactive(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireExceptionCaught(final Throwable cause) {
+ if (cause == null) {
+ throw new NullPointerException("cause");
+ }
+
+ final AbstractChannelHandlerContext next = this.next;
+
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeExceptionCaught(cause);
+ } else {
+ try {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeExceptionCaught(cause);
+ }
+ });
+ } catch (Throwable t) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Failed to submit an exceptionCaught() event.", t);
+ logger.warn("The exceptionCaught() event that was failed to submit was:", cause);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ private void invokeExceptionCaught(final Throwable cause) {
+ try {
+ handler().exceptionCaught(this, cause);
+ } catch (Throwable t) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(
+ "An exception was thrown by a user handler's " +
+ "exceptionCaught() method while handling the following exception:", cause);
+ }
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireUserEventTriggered(final Object event) {
+ if (event == null) {
+ throw new NullPointerException("event");
+ }
+
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeUserEventTriggered(event);
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeUserEventTriggered(event);
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeUserEventTriggered(Object event) {
+ try {
+ ((ChannelInboundHandler) handler()).userEventTriggered(this, event);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelRead(final Object msg) {
+ if (msg == null) {
+ throw new NullPointerException("msg");
+ }
+
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelRead(msg);
+ } else {
+ executor.execute(new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeChannelRead(msg);
+ }
+ });
+ }
+ return this;
+ }
+
+ private void invokeChannelRead(Object msg) {
+ try {
+ ((ChannelInboundHandler) handler()).channelRead(this, msg);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelReadComplete() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelReadComplete();
+ } else {
+ Runnable task = next.invokeChannelReadCompleteTask;
+ if (task == null) {
+ next.invokeChannelReadCompleteTask = task = new Runnable() {
+ @Override
+ public void run() {
+ next.invokeChannelReadComplete();
+ }
+ };
+ }
+ executor.execute(task);
+ }
+ return this;
+ }
+
+ private void invokeChannelReadComplete() {
+ try {
+ ((ChannelInboundHandler) handler()).channelReadComplete(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext fireChannelWritabilityChanged() {
+ final AbstractChannelHandlerContext next = findContextInbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeChannelWritabilityChanged();
+ } else {
+ Runnable task = next.invokeChannelWritableStateChangedTask;
+ if (task == null) {
+ next.invokeChannelWritableStateChangedTask = task = new Runnable() {
+ @Override
+ public void run() {
+ next.invokeChannelWritabilityChanged();
+ }
+ };
+ }
+ executor.execute(task);
+ }
+ return this;
+ }
+
+ private void invokeChannelWritabilityChanged() {
+ try {
+ ((ChannelInboundHandler) handler()).channelWritabilityChanged(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelFuture bind(SocketAddress localAddress) {
+ return bind(localAddress, newPromise());
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress) {
+ return connect(remoteAddress, newPromise());
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
+ return connect(remoteAddress, localAddress, newPromise());
+ }
+
+ @Override
+ public ChannelFuture disconnect() {
+ return disconnect(newPromise());
+ }
+
+ @Override
+ public ChannelFuture close() {
+ return close(newPromise());
+ }
+
+ @Override
+ public ChannelFuture deregister() {
+ return deregister(newPromise());
+ }
+
+ @Override
+ public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
+ if (localAddress == null) {
+ throw new NullPointerException("localAddress");
+ }
+ if (!validatePromise(promise, false)) {
+ // cancelled
+ return promise;
+ }
+
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeBind(localAddress, promise);
+ } else {
+ safeExecute(executor, new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeBind(localAddress, promise);
+ }
+ }, promise, null);
+ }
+
+ return promise;
+ }
+
+ private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
+ return connect(remoteAddress, null, promise);
+ }
+
+ @Override
+ public ChannelFuture connect(
+ final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
+
+ if (remoteAddress == null) {
+ throw new NullPointerException("remoteAddress");
+ }
+ if (!validatePromise(promise, false)) {
+ // cancelled
+ return promise;
+ }
+
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeConnect(remoteAddress, localAddress, promise);
+ } else {
+ safeExecute(executor, new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeConnect(remoteAddress, localAddress, promise);
+ }
+ }, promise, null);
+ }
+
+ return promise;
+ }
+
+ private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).connect(this, remoteAddress, localAddress, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelFuture disconnect(final ChannelPromise promise) {
+ if (!validatePromise(promise, false)) {
+ // cancelled
+ return promise;
+ }
+
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ // Translate disconnect to close if the channel has no notion of disconnect-reconnect.
+ // So far, UDP/IP is the only transport that has such behavior.
+ if (!channel().metadata().hasDisconnect()) {
+ next.invokeClose(promise);
+ } else {
+ next.invokeDisconnect(promise);
+ }
+ } else {
+ safeExecute(executor, new OneTimeTask() {
+ @Override
+ public void run() {
+ if (!channel().metadata().hasDisconnect()) {
+ next.invokeClose(promise);
+ } else {
+ next.invokeDisconnect(promise);
+ }
+ }
+ }, promise, null);
+ }
+
+ return promise;
+ }
+
+ private void invokeDisconnect(ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).disconnect(this, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelFuture close(final ChannelPromise promise) {
+ if (!validatePromise(promise, false)) {
+ // cancelled
+ return promise;
+ }
+
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeClose(promise);
+ } else {
+ safeExecute(executor, new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeClose(promise);
+ }
+ }, promise, null);
+ }
+
+ return promise;
+ }
+
+ private void invokeClose(ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).close(this, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelFuture deregister(final ChannelPromise promise) {
+ if (!validatePromise(promise, false)) {
+ // cancelled
+ return promise;
+ }
+
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeDeregister(promise);
+ } else {
+ safeExecute(executor, new OneTimeTask() {
+ @Override
+ public void run() {
+ next.invokeDeregister(promise);
+ }
+ }, promise, null);
+ }
+
+ return promise;
+ }
+
+ private void invokeDeregister(ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).deregister(this, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext read() {
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeRead();
+ } else {
+ Runnable task = next.invokeReadTask;
+ if (task == null) {
+ next.invokeReadTask = task = new Runnable() {
+ @Override
+ public void run() {
+ next.invokeRead();
+ }
+ };
+ }
+ executor.execute(task);
+ }
+
+ return this;
+ }
+
+ private void invokeRead() {
+ try {
+ ((ChannelOutboundHandler) handler()).read(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelFuture write(Object msg) {
+ return write(msg, newPromise());
+ }
+
+ @Override
+ public ChannelFuture write(final Object msg, final ChannelPromise promise) {
+ if (msg == null) {
+ throw new NullPointerException("msg");
+ }
+
+ if (!validatePromise(promise, true)) {
+ ReferenceCountUtil.release(msg);
+ // cancelled
+ return promise;
+ }
+ write(msg, false, promise);
+
+ return promise;
+ }
+
+ private void invokeWrite(Object msg, ChannelPromise promise) {
+ try {
+ ((ChannelOutboundHandler) handler()).write(this, msg, promise);
+ } catch (Throwable t) {
+ notifyOutboundHandlerException(t, promise);
+ }
+ }
+
+ @Override
+ public ChannelHandlerContext flush() {
+ final AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeFlush();
+ } else {
+ Runnable task = next.invokeFlushTask;
+ if (task == null) {
+ next.invokeFlushTask = task = new Runnable() {
+ @Override
+ public void run() {
+ next.invokeFlush();
+ }
+ };
+ }
+ safeExecute(executor, task, channel.voidPromise(), null);
+ }
+
+ return this;
+ }
+
+ private void invokeFlush() {
+ try {
+ ((ChannelOutboundHandler) handler()).flush(this);
+ } catch (Throwable t) {
+ notifyHandlerException(t);
+ }
+ }
+
+ @Override
+ public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
+ if (msg == null) {
+ throw new NullPointerException("msg");
+ }
+
+ if (!validatePromise(promise, true)) {
+ ReferenceCountUtil.release(msg);
+ // cancelled
+ return promise;
+ }
+
+ write(msg, true, promise);
+
+ return promise;
+ }
+
+ private void write(Object msg, boolean flush, ChannelPromise promise) {
+
+ AbstractChannelHandlerContext next = findContextOutbound();
+ EventExecutor executor = next.executor();
+ if (executor.inEventLoop()) {
+ next.invokeWrite(msg, promise);
+ if (flush) {
+ next.invokeFlush();
+ }
+ } else {
+ int size = channel.estimatorHandle().size(msg);
+ if (size > 0) {
+ ChannelOutboundBuffer buffer = channel.unsafe().outboundBuffer();
+ // Check for null as it may be set to null if the channel is closed already
+ if (buffer != null) {
+ buffer.incrementPendingOutboundBytes(size);
+ }
+ }
+ Runnable task;
+ if (flush) {
+ task = WriteAndFlushTask.newInstance(next, msg, size, promise);
+ } else {
+ task = WriteTask.newInstance(next, msg, size, promise);
+ }
+ safeExecute(executor, task, promise, msg);
+ }
+ }
+
+ @Override
+ public ChannelFuture writeAndFlush(Object msg) {
+ return writeAndFlush(msg, newPromise());
+ }
+
+ private static void notifyOutboundHandlerException(Throwable cause, ChannelPromise promise) {
+ // only try to fail the promise if its not a VoidChannelPromise, as
+ // the VoidChannelPromise would also fire the cause through the pipeline
+ if (promise instanceof VoidChannelPromise) {
+ return;
+ }
+
+ if (!promise.tryFailure(cause)) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Failed to fail the promise because it's done already: {}", promise, cause);
+ }
+ }
+ }
+
+ private void notifyHandlerException(Throwable cause) {
+ if (inExceptionCaught(cause)) {
+ if (logger.isWarnEnabled()) {
+ logger.warn(
+ "An exception was thrown by a user handler " +
+ "while handling an exceptionCaught event", cause);
+ }
+ return;
+ }
+
+ invokeExceptionCaught(cause);
+ }
+
+ private static boolean inExceptionCaught(Throwable cause) {
+ do {
+ StackTraceElement[] trace = cause.getStackTrace();
+ if (trace != null) {
+ for (StackTraceElement t : trace) {
+ if (t == null) {
+ break;
+ }
+ if ("exceptionCaught".equals(t.getMethodName())) {
+ return true;
+ }
+ }
+ }
+
+ cause = cause.getCause();
+ } while (cause != null);
+
+ return false;
+ }
+
+ @Override
+ public ChannelPromise newPromise() {
+ return new DefaultChannelPromise(channel(), executor());
+ }
+
+// @Override
+// public ChannelProgressivePromise newProgressivePromise() {
+// return new DefaultChannelProgressivePromise(channel(), executor());
+// }
+
+ @Override
+ public ChannelFuture newSucceededFuture() {
+ ChannelFuture succeededFuture = this.succeededFuture;
+ if (succeededFuture == null) {
+ this.succeededFuture = succeededFuture = new SucceededChannelFuture(channel(), executor());
+ }
+ return succeededFuture;
+ }
+
+ @Override
+ public ChannelFuture newFailedFuture(Throwable cause) {
+ return new FailedChannelFuture(channel(), executor(), cause);
+ }
+
+ private boolean validatePromise(ChannelPromise promise, boolean allowVoidPromise) {
+ if (promise == null) {
+ throw new NullPointerException("promise");
+ }
+
+ if (promise.isDone()) {
+ // Check if the promise was cancelled and if so signal that the processing of the operation
+ // should not be performed.
+ //
+ // See https://github.com/netty/netty/issues/2349
+ if (promise.isCancelled()) {
+ return false;
+ }
+ throw new IllegalArgumentException("promise already done: " + promise);
+ }
+
+ if (promise.channel() != channel()) {
+ throw new IllegalArgumentException(String.format(
+ "promise.channel does not match: %s (expected: %s)", promise.channel(), channel()));
+ }
+
+ if (promise.getClass() == DefaultChannelPromise.class) {
+ return true;
+ }
+
+ if (!allowVoidPromise && promise instanceof VoidChannelPromise) {
+ throw new IllegalArgumentException(
+ StringUtil.simpleClassName(VoidChannelPromise.class) + " not allowed for this operation");
+ }
+
+ if (promise instanceof AbstractChannel.CloseFuture) {
+ throw new IllegalArgumentException(
+ StringUtil.simpleClassName(AbstractChannel.CloseFuture.class) + " not allowed in a pipeline");
+ }
+ return true;
+ }
+
+ private AbstractChannelHandlerContext findContextInbound() {
+ AbstractChannelHandlerContext ctx = this;
+ do {
+ ctx = ctx.next;
+ } while (!ctx.inbound);
+ return ctx;
+ }
+
+ private AbstractChannelHandlerContext findContextOutbound() {
+ AbstractChannelHandlerContext ctx = this;
+ do {
+ ctx = ctx.prev;
+ } while (!ctx.outbound);
+ return ctx;
+ }
+
+ @Override
+ public ChannelPromise voidPromise() {
+ return channel.voidPromise();
+ }
+
+ void setRemoved() {
+ removed = true;
+ }
+
+ @Override
+ public boolean isRemoved() {
+ return removed;
+ }
+
+ private static void safeExecute(EventExecutor executor, Runnable runnable, ChannelPromise promise, Object msg) {
+ try {
+ executor.execute(runnable);
+ } catch (Throwable cause) {
+ try {
+ promise.setFailure(cause);
+ } finally {
+ if (msg != null) {
+ ReferenceCountUtil.release(msg);
+ }
+ }
+ }
+ }
+
+ abstract static class AbstractWriteTask extends RecyclableMpscLinkedQueueNode implements Runnable {
+ private AbstractChannelHandlerContext ctx;
+ private Object msg;
+ private ChannelPromise promise;
+ private int size;
+
+ private AbstractWriteTask(Recycler.Handle handle) {
+ super(handle);
+ }
+
+ protected static void init(AbstractWriteTask task, AbstractChannelHandlerContext ctx,
+ Object msg, int size, ChannelPromise promise) {
+ task.ctx = ctx;
+ task.msg = msg;
+ task.promise = promise;
+ task.size = size;
+ }
+
+ @Override
+ public final void run() {
+ try {
+ if (size > 0) {
+ ChannelOutboundBuffer buffer = ctx.channel.unsafe().outboundBuffer();
+ // Check for null as it may be set to null if the channel is closed already
+ if (buffer != null) {
+ buffer.decrementPendingOutboundBytes(size);
+ }
+ }
+ write(ctx, msg, promise);
+ } finally {
+ // Set to null so the GC can collect them directly
+ ctx = null;
+ msg = null;
+ promise = null;
+ }
+ }
+
+ @Override
+ public Runnable value() {
+ return this;
+ }
+
+ protected void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
+ ctx.invokeWrite(msg, promise);
+ }
+ }
+
+ static final class WriteTask extends AbstractWriteTask implements SingleThreadEventLoop.NonWakeupRunnable {
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected WriteTask newObject(Handle handle) {
+ return new WriteTask(handle);
+ }
+ };
+
+ private static WriteTask newInstance(
+ AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) {
+ WriteTask task = RECYCLER.get();
+ init(task, ctx, msg, size, promise);
+ return task;
+ }
+
+ private WriteTask(Recycler.Handle handle) {
+ super(handle);
+ }
+
+ @Override
+ protected void recycle(Recycler.Handle handle) {
+ RECYCLER.recycle(this, handle);
+ }
+ }
+
+ static final class WriteAndFlushTask extends AbstractWriteTask {
+
+ private static final Recycler RECYCLER = new Recycler() {
+ @Override
+ protected WriteAndFlushTask newObject(Handle handle) {
+ return new WriteAndFlushTask(handle);
+ }
+ };
+
+ private static WriteAndFlushTask newInstance(
+ AbstractChannelHandlerContext ctx, Object msg, int size, ChannelPromise promise) {
+ WriteAndFlushTask task = RECYCLER.get();
+ init(task, ctx, msg, size, promise);
+ return task;
+ }
+
+ private WriteAndFlushTask(Recycler.Handle handle) {
+ super(handle);
+ }
+
+ @Override
+ public void write(AbstractChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
+ super.write(ctx, msg, promise);
+ ctx.invokeFlush();
+ }
+
+ @Override
+ protected void recycle(Recycler.Handle handle) {
+ RECYCLER.recycle(this, handle);
+ }
+ }
+}
diff --git a/common/src/common/net/channel/AbstractServerChannel.java b/common/src/common/net/channel/AbstractServerChannel.java
new file mode 100644
index 0000000..ae1bd45
--- /dev/null
+++ b/common/src/common/net/channel/AbstractServerChannel.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.channel;
+
+import java.net.SocketAddress;
+
+/**
+ * A skeletal server-side {@link Channel} implementation. A server-side
+ * {@link Channel} does not allow the following operations:
+ *
+ * {@link #connect(SocketAddress, ChannelPromise)}
+ * {@link #disconnect(ChannelPromise)}
+ * {@link #write(Object, ChannelPromise)}
+ * {@link #flush()}
+ * and the shortcut methods which calls the methods mentioned above
+ *
+ */
+public abstract class AbstractServerChannel extends AbstractChannel implements ServerChannel {
+
+ private static final ChannelMetadata METADATA = new ChannelMetadata(false);
+
+ /**
+ * Creates a new instance.
+ */
+ protected AbstractServerChannel() {
+ super(null);
+ }
+
+ @Override
+ public ChannelMetadata metadata() {
+ return METADATA;
+ }
+
+ @Override
+ public SocketAddress remoteAddress() {
+ return null;
+ }
+
+ @Override
+ protected SocketAddress remoteAddress0() {
+ return null;
+ }
+
+ @Override
+ protected void doDisconnect() throws Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected AbstractUnsafe newUnsafe() {
+ return new DefaultServerUnsafe();
+ }
+
+ @Override
+ protected void doWrite(ChannelOutboundBuffer in) throws Exception {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected final Object filterOutboundMessage(Object msg) {
+ throw new UnsupportedOperationException();
+ }
+
+ private final class DefaultServerUnsafe extends AbstractUnsafe {
+ @Override
+ public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
+ safeSetFailure(promise, new UnsupportedOperationException());
+ }
+ }
+}
diff --git a/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java b/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java
new file mode 100644
index 0000000..f1eb52d
--- /dev/null
+++ b/common/src/common/net/channel/AdaptiveRecvByteBufAllocator.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.channel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import common.net.buffer.ByteBuf;
+import common.net.buffer.ByteBufAllocator;
+
+/**
+ * The {@link RecvByteBufAllocator} that automatically increases and
+ * decreases the predicted buffer size on feed back.
+ *
+ * It gradually increases the expected number of readable bytes if the previous
+ * read fully filled the allocated buffer. It gradually decreases the expected
+ * number of readable bytes if the read operation was not able to fill a certain
+ * amount of the allocated buffer two times consecutively. Otherwise, it keeps
+ * returning the same prediction.
+ */
+public class AdaptiveRecvByteBufAllocator implements RecvByteBufAllocator {
+
+ static final int DEFAULT_MINIMUM = 64;
+ static final int DEFAULT_INITIAL = 1024;
+ static final int DEFAULT_MAXIMUM = 65536;
+
+ private static final int INDEX_INCREMENT = 4;
+ private static final int INDEX_DECREMENT = 1;
+
+ private static final int[] SIZE_TABLE;
+
+ static {
+ List sizeTable = new ArrayList();
+ for (int i = 16; i < 512; i += 16) {
+ sizeTable.add(i);
+ }
+
+ for (int i = 512; i > 0; i <<= 1) {
+ sizeTable.add(i);
+ }
+
+ SIZE_TABLE = new int[sizeTable.size()];
+ for (int i = 0; i < SIZE_TABLE.length; i ++) {
+ SIZE_TABLE[i] = sizeTable.get(i);
+ }
+ }
+
+ public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();
+
+ private static int getSizeTableIndex(final int size) {
+ for (int low = 0, high = SIZE_TABLE.length - 1;;) {
+ if (high < low) {
+ return low;
+ }
+ if (high == low) {
+ return high;
+ }
+
+ int mid = low + high >>> 1;
+ int a = SIZE_TABLE[mid];
+ int b = SIZE_TABLE[mid + 1];
+ if (size > b) {
+ low = mid + 1;
+ } else if (size < a) {
+ high = mid - 1;
+ } else if (size == a) {
+ return mid;
+ } else {
+ return mid + 1;
+ }
+ }
+ }
+
+ private static final class HandleImpl implements Handle {
+ private final int minIndex;
+ private final int maxIndex;
+ private int index;
+ private int nextReceiveBufferSize;
+ private boolean decreaseNow;
+
+ HandleImpl(int minIndex, int maxIndex, int initial) {
+ this.minIndex = minIndex;
+ this.maxIndex = maxIndex;
+
+ index = getSizeTableIndex(initial);
+ nextReceiveBufferSize = SIZE_TABLE[index];
+ }
+
+ @Override
+ public ByteBuf allocate(ByteBufAllocator alloc) {
+ return alloc.ioBuffer(nextReceiveBufferSize);
+ }
+
+ @Override
+ public int guess() {
+ return nextReceiveBufferSize;
+ }
+
+ @Override
+ public void record(int actualReadBytes) {
+ if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {
+ if (decreaseNow) {
+ index = Math.max(index - INDEX_DECREMENT, minIndex);
+ nextReceiveBufferSize = SIZE_TABLE[index];
+ decreaseNow = false;
+ } else {
+ decreaseNow = true;
+ }
+ } else if (actualReadBytes >= nextReceiveBufferSize) {
+ index = Math.min(index + INDEX_INCREMENT, maxIndex);
+ nextReceiveBufferSize = SIZE_TABLE[index];
+ decreaseNow = false;
+ }
+ }
+ }
+
+ private final int minIndex;
+ private final int maxIndex;
+ private final int initial;
+
+ /**
+ * Creates a new predictor with the default parameters. With the default
+ * parameters, the expected buffer size starts from {@code 1024}, does not
+ * go down below {@code 64}, and does not go up above {@code 65536}.
+ */
+ private AdaptiveRecvByteBufAllocator() {
+ this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);
+ }
+
+ /**
+ * Creates a new predictor with the specified parameters.
+ *
+ * @param minimum the inclusive lower bound of the expected buffer size
+ * @param initial the initial buffer size when no feed back was received
+ * @param maximum the inclusive upper bound of the expected buffer size
+ */
+ public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {
+ if (minimum <= 0) {
+ throw new IllegalArgumentException("minimum: " + minimum);
+ }
+ if (initial < minimum) {
+ throw new IllegalArgumentException("initial: " + initial);
+ }
+ if (maximum < initial) {
+ throw new IllegalArgumentException("maximum: " + maximum);
+ }
+
+ int minIndex = getSizeTableIndex(minimum);
+ if (SIZE_TABLE[minIndex] < minimum) {
+ this.minIndex = minIndex + 1;
+ } else {
+ this.minIndex = minIndex;
+ }
+
+ int maxIndex = getSizeTableIndex(maximum);
+ if (SIZE_TABLE[maxIndex] > maximum) {
+ this.maxIndex = maxIndex - 1;
+ } else {
+ this.maxIndex = maxIndex;
+ }
+
+ this.initial = initial;
+ }
+
+ @Override
+ public Handle newHandle() {
+ return new HandleImpl(minIndex, maxIndex, initial);
+ }
+}
diff --git a/common/src/common/net/channel/Channel.java b/common/src/common/net/channel/Channel.java
new file mode 100644
index 0000000..464bbff
--- /dev/null
+++ b/common/src/common/net/channel/Channel.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright 2012 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package common.net.channel;
+
+import java.net.SocketAddress;
+
+import common.net.buffer.ByteBufAllocator;
+import common.net.util.AttributeMap;
+
+
+/**
+ * A nexus to a network socket or a component which is capable of I/O
+ * operations such as read, write, connect, and bind.
+ *
+ * A channel provides a user:
+ *
+ * the current state of the channel (e.g. is it open? is it connected?),
+ * the {@linkplain ChannelConfig configuration parameters} of the channel (e.g. receive buffer size),
+ * the I/O operations that the channel supports (e.g. read, write, connect, and bind), and
+ * the {@link ChannelPipeline} which handles all I/O events and requests
+ * associated with the channel.
+ *
+ *
+ * All I/O operations are asynchronous.
+ *
+ * All I/O operations in Netty are asynchronous. It means any I/O calls will
+ * return immediately with no guarantee that the requested I/O operation has
+ * been completed at the end of the call. Instead, you will be returned with
+ * a {@link ChannelFuture} instance which will notify you when the requested I/O
+ * operation has succeeded, failed, or canceled.
+ *
+ *
Channels are hierarchical
+ *
+ * A {@link Channel} can have a {@linkplain #parent() parent} depending on
+ * how it was created. For instance, a {@link SocketChannel}, that was accepted
+ * by {@link ServerSocketChannel}, will return the {@link ServerSocketChannel}
+ * as its parent on {@link #parent()}.
+ *
+ * The semantics of the hierarchical structure depends on the transport
+ * implementation where the {@link Channel} belongs to. For example, you could
+ * write a new {@link Channel} implementation that creates the sub-channels that
+ * share one socket connection, as BEEP and
+ * SSH do.
+ *
+ *
Downcast to access transport-specific operations
+ *
+ * Some transports exposes additional operations that is specific to the
+ * transport. Down-cast the {@link Channel} to sub-type to invoke such
+ * operations. For example, with the old I/O datagram transport, multicast
+ * join / leave operations are provided by {@link DatagramChannel}.
+ *
+ *
Release resources
+ *
+ * It is important to call {@link #close()} or {@link #close(ChannelPromise)} to release all
+ * resources once you are done with the {@link Channel}. This ensures all resources are
+ * released in a proper way, i.e. filehandles.
+ */
+public interface Channel extends AttributeMap, Comparable {
+
+ /**
+ * Return the {@link EventLoop} this {@link Channel} was registered too.
+ */
+ EventLoop eventLoop();
+
+ /**
+ * Returns the parent of this channel.
+ *
+ * @return the parent channel.
+ * {@code null} if this channel does not have a parent channel.
+ */
+ Channel parent();
+
+ /**
+ * Returns the configuration of this channel.
+ */
+ ChannelConfig config();
+
+ /**
+ * Returns {@code true} if the {@link Channel} is open an may get active later
+ */
+ boolean isOpen();
+
+ /**
+ * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}.
+ */
+ boolean isRegistered();
+
+ /**
+ * Return {@code true} if the {@link Channel} is active and so connected.
+ */
+ boolean isActive();
+
+ /**
+ * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}.
+ */
+ ChannelMetadata metadata();
+
+ /**
+ * Returns the local address where this channel is bound to. The returned
+ * {@link SocketAddress} is supposed to be down-cast into more concrete
+ * type such as {@link InetSocketAddress} to retrieve the detailed
+ * information.
+ *
+ * @return the local address of this channel.
+ * {@code null} if this channel is not bound.
+ */
+ SocketAddress localAddress();
+
+ /**
+ * Returns the remote address where this channel is connected to. The
+ * returned {@link SocketAddress} is supposed to be down-cast into more
+ * concrete type such as {@link InetSocketAddress} to retrieve the detailed
+ * information.
+ *
+ * @return the remote address of this channel.
+ * {@code null} if this channel is not connected.
+ * If this channel is not connected but it can receive messages
+ * from arbitrary remote addresses (e.g. {@link DatagramChannel},
+ * use {@link DatagramPacket#recipient()} to determine
+ * the origination of the received message as this method will
+ * return {@code null}.
+ */
+ SocketAddress remoteAddress();
+
+ /**
+ * Returns the {@link ChannelFuture} which will be notified when this
+ * channel is closed. This method always returns the same future instance.
+ */
+ ChannelFuture closeFuture();
+
+ /**
+ * Returns {@code true} if and only if the I/O thread will perform the
+ * requested write operation immediately. Any write requests made when
+ * this method returns {@code false} are queued until the I/O thread is
+ * ready to process the queued write requests.
+ */
+ boolean isWritable();
+
+ /**
+ * Returns an internal-use-only object that provides unsafe operations.
+ */
+ Unsafe unsafe();
+
+ /**
+ * Return the assigned {@link ChannelPipeline}
+ */
+ ChannelPipeline pipeline();
+
+ /**
+ * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s.
+ */
+ ByteBufAllocator alloc();
+
+ /**
+ * Return a new {@link ChannelPromise}.
+ */
+ ChannelPromise newPromise();
+
+ /**
+ * Return an new {@link ChannelProgressivePromise}
+ */
+// ChannelProgressivePromise newProgressivePromise();
+
+ /**
+ * Create a new {@link ChannelFuture} which is marked as succeeded already. So {@link ChannelFuture#isSuccess()}
+ * will return {@code true}. All {@link FutureListener} added to it will be notified directly. Also
+ * every call of blocking methods will just return without blocking.
+ */
+ ChannelFuture newSucceededFuture();
+
+ /**
+ * Create a new {@link ChannelFuture} which is marked as failed already. So {@link ChannelFuture#isSuccess()}
+ * will return {@code false}. All {@link FutureListener} added to it will be notified directly. Also
+ * every call of blocking methods will just return without blocking.
+ */
+ ChannelFuture newFailedFuture(Throwable cause);
+
+ /**
+ * Return a special ChannelPromise which can be reused for different operations.
+ *
+ * It's only supported to use
+ * it for {@link Channel#write(Object, ChannelPromise)}.
+ *
+ *
+ * Be aware that the returned {@link ChannelPromise} will not support most operations and should only be used
+ * if you want to save an object allocation for every write operation. You will not be able to detect if the
+ * operation was complete, only if it failed as the implementation will call
+ * {@link ChannelPipeline#fireExceptionCaught(Throwable)} in this case.
+ *
+ * Be aware this is an expert feature and should be used with care!
+ */
+ ChannelPromise voidPromise();
+
+ /**
+ * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
+ * completes, either because the operation was successful or because of an error.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
+ * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture bind(SocketAddress localAddress);
+
+ /**
+ * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
+ * completes, either because the operation was successful or because of an error.
+ *
+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
+ * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
+ * will be used.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture connect(SocketAddress remoteAddress);
+
+ /**
+ * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
+ * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
+ * an error.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress);
+
+ /**
+ * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
+ * either because the operation was successful or because of an error.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture disconnect();
+
+ /**
+ * Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
+ * either because the operation was successful or because of
+ * an error.
+ *
+ * After it is closed it is not possible to reuse it again.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture close();
+
+ /**
+ * Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the
+ * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
+ * an error.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ *
+ */
+ ChannelFuture deregister();
+
+ /**
+ * Request to bind to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
+ * completes, either because the operation was successful or because of an error.
+ *
+ * The given {@link ChannelPromise} will be notified.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#bind(ChannelHandlerContext, SocketAddress, ChannelPromise)} method
+ * called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
+
+ /**
+ * Request to connect to the given {@link SocketAddress} and notify the {@link ChannelFuture} once the operation
+ * completes, either because the operation was successful or because of an error.
+ *
+ * The given {@link ChannelFuture} will be notified.
+ *
+ *
+ * If the connection fails because of a connection timeout, the {@link ChannelFuture} will get failed with
+ * a {@link ConnectTimeoutException}. If it fails because of connection refused a {@link ConnectException}
+ * will be used.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
+
+ /**
+ * Request to connect to the given {@link SocketAddress} while bind to the localAddress and notify the
+ * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
+ * an error.
+ *
+ * The given {@link ChannelPromise} will be notified and also returned.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#connect(ChannelHandlerContext, SocketAddress, SocketAddress, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
+
+ /**
+ * Request to disconnect from the remote peer and notify the {@link ChannelFuture} once the operation completes,
+ * either because the operation was successful or because of an error.
+ *
+ * The given {@link ChannelPromise} will be notified.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#disconnect(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture disconnect(ChannelPromise promise);
+
+ /**
+ * Request to close this {@link Channel} and notify the {@link ChannelFuture} once the operation completes,
+ * either because the operation was successful or because of
+ * an error.
+ *
+ * After it is closed it is not possible to reuse it again.
+ * The given {@link ChannelPromise} will be notified.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#close(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture close(ChannelPromise promise);
+
+ /**
+ * Request to deregister this {@link Channel} from the previous assigned {@link EventExecutor} and notify the
+ * {@link ChannelFuture} once the operation completes, either because the operation was successful or because of
+ * an error.
+ *
+ * The given {@link ChannelPromise} will be notified.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#deregister(ChannelHandlerContext, ChannelPromise)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ ChannelFuture deregister(ChannelPromise promise);
+
+ /**
+ * Request to Read data from the {@link Channel} into the first inbound buffer, triggers an
+ * {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)} event if data was
+ * read, and triggers a
+ * {@link ChannelInboundHandler#channelReadComplete(ChannelHandlerContext) channelReadComplete} event so the
+ * handler can decide to continue reading. If there's a pending read operation already, this method does nothing.
+ *
+ * This will result in having the
+ * {@link ChannelOutboundHandler#read(ChannelHandlerContext)}
+ * method called of the next {@link ChannelOutboundHandler} contained in the {@link ChannelPipeline} of the
+ * {@link Channel}.
+ */
+ Channel read();
+
+ /**
+ * Request to write a message via this {@link Channel} through the {@link ChannelPipeline}.
+ * This method will not request to actual flush, so be sure to call {@link #flush()}
+ * once you want to request to flush all pending data to the actual transport.
+ */
+ ChannelFuture write(Object msg);
+
+ /**
+ * Request to write a message via this {@link Channel} through the {@link ChannelPipeline}.
+ * This method will not request to actual flush, so be sure to call {@link #flush()}
+ * once you want to request to flush all pending data to the actual transport.
+ */
+ ChannelFuture write(Object msg, ChannelPromise promise);
+
+ /**
+ * Request to flush all pending messages.
+ */
+ Channel flush();
+
+ /**
+ * Shortcut for call {@link #write(Object, ChannelPromise)} and {@link #flush()}.
+ */
+ ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);
+
+ /**
+ * Shortcut for call {@link #write(Object)} and {@link #flush()}.
+ */
+ ChannelFuture writeAndFlush(Object msg);
+
+ /**
+ *