nl.topicus.jdbc.shaded.io.netty.buffer.ByteBufUtil Maven / Gradle / Ivy
Show all versions of spanner-jdbc Show documentation
/*
* 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 nl.topicus.jdbc.shaded.io.netty.buffer;
import nl.topicus.jdbc.shaded.io.netty.util.AsciiString;
import nl.topicus.jdbc.shaded.io.netty.util.ByteProcessor;
import nl.topicus.jdbc.shaded.io.netty.util.CharsetUtil;
import nl.topicus.jdbc.shaded.io.netty.util.Recycler;
import nl.topicus.jdbc.shaded.io.netty.util.Recycler.Handle;
import nl.topicus.jdbc.shaded.io.netty.util.concurrent.FastThreadLocal;
import nl.topicus.jdbc.shaded.io.netty.util.internal.PlatformDependent;
import nl.topicus.jdbc.shaded.io.netty.util.internal.StringUtil;
import nl.topicus.jdbc.shaded.io.netty.util.internal.SystemPropertyUtil;
import nl.topicus.jdbc.shaded.io.netty.util.internal.logging.InternalLogger;
import nl.topicus.jdbc.shaded.io.netty.util.internal.logging.InternalLoggerFactory;
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.nio.charset.CodingErrorAction;
import java.util.Arrays;
import java.util.Locale;
import static nl.topicus.jdbc.shaded.io.netty.util.internal.MathUtil.isOutOfBounds;
import static nl.topicus.jdbc.shaded.io.netty.util.internal.ObjectUtil.checkNotNull;
import static nl.topicus.jdbc.shaded.io.netty.util.internal.StringUtil.NEWLINE;
import static nl.topicus.jdbc.shaded.io.netty.util.internal.StringUtil.isSurrogate;
/**
* A collection of utility methods that is related with handling {@link ByteBuf},
* such as the generation of hex dump and swapping an integer's byte order.
*/
public final class ByteBufUtil {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ByteBufUtil.class);
private static final FastThreadLocal CHAR_BUFFERS = new FastThreadLocal() {
@Override
protected CharBuffer initialValue() throws Exception {
return CharBuffer.allocate(1024);
}
};
private static final byte WRITE_UTF_UNKNOWN = (byte) '?';
private static final int MAX_CHAR_BUFFER_SIZE;
private static final int THREAD_LOCAL_BUFFER_SIZE;
private static final int MAX_BYTES_PER_CHAR_UTF8 =
(int) CharsetUtil.encoder(CharsetUtil.UTF_8).maxBytesPerChar();
static final ByteBufAllocator DEFAULT_ALLOCATOR;
static {
String allocType = SystemPropertyUtil.get(
"nl.topicus.jdbc.shaded.io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
allocType = allocType.toLowerCase(Locale.US).trim();
ByteBufAllocator alloc;
if ("unpooled".equals(allocType)) {
alloc = UnpooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else if ("pooled".equals(allocType)) {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: {}", allocType);
} else {
alloc = PooledByteBufAllocator.DEFAULT;
logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
}
DEFAULT_ALLOCATOR = alloc;
THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("nl.topicus.jdbc.shaded.io.netty.threadLocalDirectBufferSize", 64 * 1024);
logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);
MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("nl.topicus.jdbc.shaded.io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_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) {
return HexUtil.hexDump(buffer, fromIndex, length);
}
/**
* Returns a hex dump
* of the specified byte array.
*/
public static String hexDump(byte[] array) {
return hexDump(array, 0, array.length);
}
/**
* Returns a hex dump
* of the specified byte array's sub-region.
*/
public static String hexDump(byte[] array, int fromIndex, int length) {
return HexUtil.hexDump(array, fromIndex, length);
}
/**
* Decode a 2-digit hex byte from within a string.
*/
public static byte decodeHexByte(CharSequence s, int pos) {
return StringUtil.decodeHexByte(s, pos);
}
/**
* Decodes a string generated by {@link #hexDump(byte[])}
*/
public static byte[] decodeHexDump(CharSequence hexDump) {
return StringUtil.decodeHexDump(hexDump, 0, hexDump.length());
}
/**
* Decodes part of a string generated by {@link #hexDump(byte[])}
*/
public static byte[] decodeHexDump(CharSequence hexDump, int fromIndex, int length) {
return StringUtil.decodeHexDump(hexDump, fromIndex, length);
}
/**
* Used to determine if the return value of {@link ByteBuf#ensureWritable(int, boolean)} means that there is
* adequate space and a write operation will succeed.
* @param ensureWritableResult The return value from {@link ByteBuf#ensureWritable(int, boolean)}.
* @return {@code true} if {@code ensureWritableResult} means that there is adequate space and a write operation
* will succeed.
*/
public static boolean ensureWritableSuccess(int ensureWritableResult) {
return ensureWritableResult == 0 || ensureWritableResult == 2;
}
/**
* 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 the reader index of needle in haystack, or -1 if needle is not in haystack.
*/
public static int indexOf(ByteBuf needle, ByteBuf haystack) {
// TODO: maybe use Boyer Moore for efficiency.
int attempts = haystack.readableBytes() - needle.readableBytes() + 1;
for (int i = 0; i < attempts; i++) {
if (equals(needle, needle.readerIndex(),
haystack, haystack.readerIndex() + i,
needle.readableBytes())) {
return haystack.readerIndex() + i;
}
}
return -1;
}
/**
* Returns {@code true} if and only if the two specified buffers are
* identical to each other for {@code length} bytes starting at {@code aStartIndex}
* index for the {@code a} buffer and {@code bStartIndex} index for the {@code b} buffer.
* A more compact way to express this is:
*
* {@code a[aStartIndex : aStartIndex + length] == b[bStartIndex : bStartIndex + length]}
*/
public static boolean equals(ByteBuf a, int aStartIndex, ByteBuf b, int bStartIndex, int length) {
if (aStartIndex < 0 || bStartIndex < 0 || length < 0) {
throw new IllegalArgumentException("All indexes and lengths must be non-negative");
}
if (a.writerIndex() - length < aStartIndex || b.writerIndex() - length < bStartIndex) {
return false;
}
final int longCount = length >>> 3;
final int byteCount = length & 7;
if (a.order() == b.order()) {
for (int i = longCount; i > 0; i --) {
if (a.getLong(aStartIndex) != b.getLong(bStartIndex)) {
return false;
}
aStartIndex += 8;
bStartIndex += 8;
}
} else {
for (int i = longCount; i > 0; i --) {
if (a.getLong(aStartIndex) != swapLong(b.getLong(bStartIndex))) {
return false;
}
aStartIndex += 8;
bStartIndex += 8;
}
}
for (int i = byteCount; i > 0; i --) {
if (a.getByte(aStartIndex) != b.getByte(bStartIndex)) {
return false;
}
aStartIndex ++;
bStartIndex ++;
}
return true;
}
/**
* Returns {@code true} if and only if the two specified buffers are
* identical to each other as described in {@link ByteBuf#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;
}
return equals(bufferA, bufferA.readerIndex(), bufferB, bufferB.readerIndex(), aLen);
}
/**
* 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 (uintCount > 0) {
boolean bufferAIsBigEndian = bufferA.order() == ByteOrder.BIG_ENDIAN;
final long res;
int uintCountIncrement = uintCount << 2;
if (bufferA.order() == bufferB.order()) {
res = bufferAIsBigEndian ? compareUintBigEndian(bufferA, bufferB, aIndex, bIndex, uintCountIncrement) :
compareUintLittleEndian(bufferA, bufferB, aIndex, bIndex, uintCountIncrement);
} else {
res = bufferAIsBigEndian ? compareUintBigEndianA(bufferA, bufferB, aIndex, bIndex, uintCountIncrement) :
compareUintBigEndianB(bufferA, bufferB, aIndex, bIndex, uintCountIncrement);
}
if (res != 0) {
// Ensure we not overflow when cast
return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, res));
}
aIndex += uintCountIncrement;
bIndex += uintCountIncrement;
}
for (int aEnd = aIndex + byteCount; aIndex < aEnd; ++aIndex, ++bIndex) {
int comp = bufferA.getUnsignedByte(aIndex) - bufferB.getUnsignedByte(bIndex);
if (comp != 0) {
return comp;
}
}
return aLen - bLen;
}
private static long compareUintBigEndian(
ByteBuf bufferA, ByteBuf bufferB, int aIndex, int bIndex, int uintCountIncrement) {
for (int aEnd = aIndex + uintCountIncrement; aIndex < aEnd; aIndex += 4, bIndex += 4) {
long comp = bufferA.getUnsignedInt(aIndex) - bufferB.getUnsignedInt(bIndex);
if (comp != 0) {
return comp;
}
}
return 0;
}
private static long compareUintLittleEndian(
ByteBuf bufferA, ByteBuf bufferB, int aIndex, int bIndex, int uintCountIncrement) {
for (int aEnd = aIndex + uintCountIncrement; aIndex < aEnd; aIndex += 4, bIndex += 4) {
long comp = bufferA.getUnsignedIntLE(aIndex) - bufferB.getUnsignedIntLE(bIndex);
if (comp != 0) {
return comp;
}
}
return 0;
}
private static long compareUintBigEndianA(
ByteBuf bufferA, ByteBuf bufferB, int aIndex, int bIndex, int uintCountIncrement) {
for (int aEnd = aIndex + uintCountIncrement; aIndex < aEnd; aIndex += 4, bIndex += 4) {
long comp = bufferA.getUnsignedInt(aIndex) - bufferB.getUnsignedIntLE(bIndex);
if (comp != 0) {
return comp;
}
}
return 0;
}
private static long compareUintBigEndianB(
ByteBuf bufferA, ByteBuf bufferB, int aIndex, int bIndex, int uintCountIncrement) {
for (int aEnd = aIndex + uintCountIncrement; aIndex < aEnd; aIndex += 4, bIndex += 4) {
long comp = bufferA.getUnsignedIntLE(aIndex) - bufferB.getUnsignedInt(bIndex);
if (comp != 0) {
return comp;
}
}
return 0;
}
/**
* 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);
}
/**
* Writes a big-endian 16-bit short integer to the buffer.
*/
@SuppressWarnings("deprecation")
public static ByteBuf writeShortBE(ByteBuf buf, int shortValue) {
return buf.order() == ByteOrder.BIG_ENDIAN? buf.writeShort(shortValue) : buf.writeShortLE(shortValue);
}
/**
* Sets a big-endian 16-bit short integer to the buffer.
*/
@SuppressWarnings("deprecation")
public static ByteBuf setShortBE(ByteBuf buf, int index, int shortValue) {
return buf.order() == ByteOrder.BIG_ENDIAN? buf.setShort(index, shortValue) : buf.setShortLE(index, shortValue);
}
/**
* Writes a big-endian 24-bit medium integer to the buffer.
*/
@SuppressWarnings("deprecation")
public static ByteBuf writeMediumBE(ByteBuf buf, int mediumValue) {
return buf.order() == ByteOrder.BIG_ENDIAN? buf.writeMedium(mediumValue) : buf.writeMediumLE(mediumValue);
}
/**
* 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;
}
return buffer.forEachByte(fromIndex, toIndex - fromIndex, new ByteProcessor.IndexOfProcessor(value));
}
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;
}
return buffer.forEachByteDesc(toIndex, fromIndex - toIndex, new ByteProcessor.IndexOfProcessor(value));
}
/**
* Encode a {@link CharSequence} in UTF-8 and write
* it to a {@link ByteBuf} allocated with {@code alloc}.
* @param alloc The allocator used to allocate a new {@link ByteBuf}.
* @param seq The characters to write into a buffer.
* @return The {@link ByteBuf} which contains the UTF-8 encoded
* result.
*/
public static ByteBuf writeUtf8(ByteBufAllocator alloc, CharSequence seq) {
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
ByteBuf buf = alloc.buffer(utf8MaxBytes(seq));
writeUtf8(buf, seq);
return buf;
}
/**
* Encode a {@link CharSequence} in UTF-8 and write
* it to a {@link ByteBuf}.
*
* This method returns the actual number of bytes written.
*/
public static int writeUtf8(ByteBuf buf, CharSequence seq) {
for (;;) {
if (buf instanceof AbstractByteBuf) {
AbstractByteBuf byteBuf = (AbstractByteBuf) buf;
byteBuf.ensureWritable0(utf8MaxBytes(seq));
int written = writeUtf8(byteBuf, byteBuf.writerIndex, seq, seq.length());
byteBuf.writerIndex += written;
return written;
} else if (buf instanceof WrappedByteBuf) {
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.unwrap();
} else {
byte[] bytes = seq.toString().getBytes(CharsetUtil.UTF_8);
buf.writeBytes(bytes);
return bytes.length;
}
}
}
// Fast-Path implementation
static int writeUtf8(AbstractByteBuf buffer, int writerIndex, CharSequence seq, int len) {
int oldWriterIndex = writerIndex;
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
char c = seq.charAt(i);
if (c < 0x80) {
buffer._setByte(writerIndex++, (byte) c);
} else if (c < 0x800) {
buffer._setByte(writerIndex++, (byte) (0xc0 | (c >> 6)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
} else if (isSurrogate(c)) {
if (!Character.isHighSurrogate(c)) {
buffer._setByte(writerIndex++, WRITE_UTF_UNKNOWN);
continue;
}
final char c2;
try {
// Surrogate Pair consumes 2 characters. Optimistically try to get the next character to avoid
// duplicate bounds checking with charAt. If an IndexOutOfBoundsException is thrown we will
// re-throw a more informative exception describing the problem.
c2 = seq.charAt(++i);
} catch (IndexOutOfBoundsException e) {
buffer._setByte(writerIndex++, WRITE_UTF_UNKNOWN);
break;
}
if (!Character.isLowSurrogate(c2)) {
buffer._setByte(writerIndex++, WRITE_UTF_UNKNOWN);
buffer._setByte(writerIndex++, Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2);
continue;
}
int codePoint = Character.toCodePoint(c, c2);
// See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630.
buffer._setByte(writerIndex++, (byte) (0xf0 | (codePoint >> 18)));
buffer._setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 12) & 0x3f)));
buffer._setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 6) & 0x3f)));
buffer._setByte(writerIndex++, (byte) (0x80 | (codePoint & 0x3f)));
} else {
buffer._setByte(writerIndex++, (byte) (0xe0 | (c >> 12)));
buffer._setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f)));
buffer._setByte(writerIndex++, (byte) (0x80 | (c & 0x3f)));
}
}
return writerIndex - oldWriterIndex;
}
/**
* Returns max bytes length of UTF8 character sequence.
*/
public static int utf8MaxBytes(CharSequence seq) {
return seq.length() * MAX_BYTES_PER_CHAR_UTF8;
}
/**
* Encode a {@link CharSequence} in ASCII and write
* it to a {@link ByteBuf} allocated with {@code alloc}.
* @param alloc The allocator used to allocate a new {@link ByteBuf}.
* @param seq The characters to write into a buffer.
* @return The {@link ByteBuf} which contains the ASCII encoded
* result.
*/
public static ByteBuf writeAscii(ByteBufAllocator alloc, CharSequence seq) {
// ASCII uses 1 byte per char
ByteBuf buf = alloc.buffer(seq.length());
writeAscii(buf, seq);
return buf;
}
/**
* Encode a {@link CharSequence} in ASCII and write it
* to a {@link ByteBuf}.
*
* This method returns the actual number of bytes written.
*/
public static int writeAscii(ByteBuf buf, CharSequence seq) {
// ASCII uses 1 byte per char
final int len = seq.length();
if (seq instanceof AsciiString) {
AsciiString asciiString = (AsciiString) seq;
buf.writeBytes(asciiString.array(), asciiString.arrayOffset(), len);
} else {
for (;;) {
if (buf instanceof AbstractByteBuf) {
AbstractByteBuf byteBuf = (AbstractByteBuf) buf;
byteBuf.ensureWritable0(len);
int written = writeAscii(byteBuf, byteBuf.writerIndex, seq, len);
byteBuf.writerIndex += written;
return written;
} else if (buf instanceof WrappedByteBuf) {
// Unwrap as the wrapped buffer may be an AbstractByteBuf and so we can use fast-path.
buf = buf.unwrap();
} else {
byte[] bytes = seq.toString().getBytes(CharsetUtil.US_ASCII);
buf.writeBytes(bytes);
return bytes.length;
}
}
}
return len;
}
// Fast-Path implementation
static int writeAscii(AbstractByteBuf buffer, int writerIndex, CharSequence seq, int len) {
// We can use the _set methods as these not need to do any index checks and reference checks.
// This is possible as we called ensureWritable(...) before.
for (int i = 0; i < len; i++) {
buffer._setByte(writerIndex++, AsciiString.c2b(seq.charAt(i)));
}
return len;
}
/**
* 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, 0);
}
/**
* Encode the given {@link CharBuffer} using the given {@link Charset} into a new {@link ByteBuf} which
* is allocated via the {@link ByteBufAllocator}.
*
* @param alloc The {@link ByteBufAllocator} to allocate {@link ByteBuf}.
* @param src The {@link CharBuffer} to encode.
* @param charset The specified {@link Charset}.
* @param extraCapacity the extra capacity to alloc except the space for decoding.
*/
public static ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset, int extraCapacity) {
return encodeString0(alloc, false, src, charset, extraCapacity);
}
static ByteBuf encodeString0(ByteBufAllocator alloc, boolean enforceHeap, CharBuffer src, Charset charset,
int extraCapacity) {
final CharsetEncoder encoder = CharsetUtil.encoder(charset);
int length = (int) ((double) src.remaining() * encoder.maxBytesPerChar()) + extraCapacity;
boolean release = true;
final ByteBuf dst;
if (enforceHeap) {
dst = alloc.heapBuffer(length);
} else {
dst = alloc.buffer(length);
}
try {
final ByteBuffer dstBuf = dst.internalNioBuffer(dst.readerIndex(), 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(ByteBuf src, int readerIndex, int len, Charset charset) {
if (len == 0) {
return StringUtil.EMPTY_STRING;
}
final CharsetDecoder decoder = CharsetUtil.decoder(charset);
final int maxLength = (int) ((double) len * decoder.maxCharsPerByte());
CharBuffer dst = CHAR_BUFFERS.get();
if (dst.length() < maxLength) {
dst = CharBuffer.allocate(maxLength);
if (maxLength <= MAX_CHAR_BUFFER_SIZE) {
CHAR_BUFFERS.set(dst);
}
} else {
dst.clear();
}
if (src.nioBufferCount() == 1) {
// Use internalNioBuffer(...) to reduce object creation.
decodeString(decoder, src.internalNioBuffer(readerIndex, len), dst);
} else {
// We use a heap buffer as CharsetDecoder is most likely able to use a fast-path if src and dst buffers
// are both backed by a byte array.
ByteBuf buffer = src.alloc().heapBuffer(len);
try {
buffer.writeBytes(src, readerIndex, len);
// Use internalNioBuffer(...) to reduce object creation.
decodeString(decoder, buffer.internalNioBuffer(buffer.readerIndex(), len), dst);
} finally {
// Release the temporary buffer again.
buffer.release();
}
}
return dst.flip().toString();
}
private static void decodeString(CharsetDecoder decoder, ByteBuffer src, CharBuffer dst) {
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);
}
}
/**
* 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();
}
}
/**
* Create a copy of the underlying storage from {@code buf} into a byte array.
* The copy will start at {@link ByteBuf#readerIndex()} and copy {@link ByteBuf#readableBytes()} bytes.
*/
public static byte[] getBytes(ByteBuf buf) {
return getBytes(buf, buf.readerIndex(), buf.readableBytes());
}
/**
* Create a copy of the underlying storage from {@code buf} into a byte array.
* The copy will start at {@code start} and copy {@code length} bytes.
*/
public static byte[] getBytes(ByteBuf buf, int start, int length) {
return getBytes(buf, start, length, true);
}
/**
* Return an array of the underlying storage from {@code buf} into a byte array.
* The copy will start at {@code start} and copy {@code length} bytes.
* If {@code copy} is true a copy will be made of the memory.
* If {@code copy} is false the underlying storage will be shared, if possible.
*/
public static byte[] getBytes(ByteBuf buf, int start, int length, boolean copy) {
if (isOutOfBounds(start, length, buf.capacity())) {
throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length
+ ") <= " + "buf.capacity(" + buf.capacity() + ')');
}
if (buf.hasArray()) {
if (copy || start != 0 || length != buf.capacity()) {
int baseOffset = buf.arrayOffset() + start;
return Arrays.copyOfRange(buf.array(), baseOffset, baseOffset + length);
} else {
return buf.array();
}
}
byte[] v = new byte[length];
buf.getBytes(start, v);
return v;
}
/**
* Copies the all content of {@code src} to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}.
*
* @param src the source string to copy
* @param dst the destination buffer
*/
public static void copy(AsciiString src, ByteBuf dst) {
copy(src, 0, dst, src.length());
}
/**
* Copies the content of {@code src} to a {@link ByteBuf} using {@link ByteBuf#setBytes(int, byte[], int, int)}.
* Unlike the {@link #copy(AsciiString, ByteBuf)} and {@link #copy(AsciiString, int, ByteBuf, int)} methods,
* this method do not increase a {@code writerIndex} of {@code dst} buffer.
*
* @param src the source string to copy
* @param srcIdx the starting offset of characters to copy
* @param dst the destination buffer
* @param dstIdx the starting offset in the destination buffer
* @param length the number of characters to copy
*/
public static void copy(AsciiString src, int srcIdx, ByteBuf dst, int dstIdx, int length) {
if (isOutOfBounds(srcIdx, length, src.length())) {
throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
+ length + ") <= srcLen(" + src.length() + ')');
}
checkNotNull(dst, "dst").setBytes(dstIdx, src.array(), srcIdx + src.arrayOffset(), length);
}
/**
* Copies the content of {@code src} to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}.
*
* @param src the source string to copy
* @param srcIdx the starting offset of characters to copy
* @param dst the destination buffer
* @param length the number of characters to copy
*/
public static void copy(AsciiString src, int srcIdx, ByteBuf dst, int length) {
if (isOutOfBounds(srcIdx, length, src.length())) {
throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length("
+ length + ") <= srcLen(" + src.length() + ')');
}
checkNotNull(dst, "dst").writeBytes(src.array(), srcIdx + src.arrayOffset(), length);
}
/**
* Returns a multi-line hexadecimal dump of the specified {@link ByteBuf} that is easy to read by humans.
*/
public static String prettyHexDump(ByteBuf buffer) {
return prettyHexDump(buffer, buffer.readerIndex(), buffer.readableBytes());
}
/**
* Returns a multi-line hexadecimal dump of the specified {@link ByteBuf} that is easy to read by humans,
* starting at the given {@code offset} using the given {@code length}.
*/
public static String prettyHexDump(ByteBuf buffer, int offset, int length) {
return HexUtil.prettyHexDump(buffer, offset, length);
}
/**
* Appends the prettified multi-line hexadecimal dump of the specified {@link ByteBuf} to the specified
* {@link StringBuilder} that is easy to read by humans.
*/
public static void appendPrettyHexDump(StringBuilder dump, ByteBuf buf) {
appendPrettyHexDump(dump, buf, buf.readerIndex(), buf.readableBytes());
}
/**
* Appends the prettified multi-line hexadecimal dump of the specified {@link ByteBuf} to the specified
* {@link StringBuilder} that is easy to read by humans, starting at the given {@code offset} using
* the given {@code length}.
*/
public static void appendPrettyHexDump(StringBuilder dump, ByteBuf buf, int offset, int length) {
HexUtil.appendPrettyHexDump(dump, buf, offset, length);
}
/* Separate class so that the expensive static initialization is only done when needed */
private static final class HexUtil {
private static final char[] BYTE2CHAR = new char[256];
private static final char[] HEXDUMP_TABLE = new char[256 * 4];
private static final String[] HEXPADDING = new String[16];
private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
private static final String[] BYTE2HEX = new String[256];
private static final String[] BYTEPADDING = new String[16];
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];
}
int i;
// Generate the lookup table for hex dump paddings
for (i = 0; i < HEXPADDING.length; i ++) {
int padding = HEXPADDING.length - i;
StringBuilder buf = new StringBuilder(padding * 3);
for (int j = 0; j < padding; j ++) {
buf.append(" ");
}
HEXPADDING[i] = buf.toString();
}
// Generate the lookup table for the start-offset header in each row (up to 64KiB).
for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i ++) {
StringBuilder buf = new StringBuilder(12);
buf.append(NEWLINE);
buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
buf.setCharAt(buf.length() - 9, '|');
buf.append('|');
HEXDUMP_ROWPREFIXES[i] = buf.toString();
}
// Generate the lookup table for byte-to-hex-dump conversion
for (i = 0; i < BYTE2HEX.length; i ++) {
BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
}
// Generate the lookup table for byte dump paddings
for (i = 0; i < BYTEPADDING.length; i ++) {
int padding = BYTEPADDING.length - i;
StringBuilder buf = new StringBuilder(padding);
for (int j = 0; j < padding; j ++) {
buf.append(' ');
}
BYTEPADDING[i] = buf.toString();
}
// Generate the lookup table for byte-to-char conversion
for (i = 0; i < BYTE2CHAR.length; i ++) {
if (i <= 0x1f || i >= 0x7f) {
BYTE2CHAR[i] = '.';
} else {
BYTE2CHAR[i] = (char) i;
}
}
}
private 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);
}
private static String hexDump(byte[] array, 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, (array[srcIdx] & 0xFF) << 1,
buf, dstIdx, 2);
}
return new String(buf);
}
private static String prettyHexDump(ByteBuf buffer, int offset, int length) {
if (length == 0) {
return StringUtil.EMPTY_STRING;
} else {
int rows = length / 16 + (length % 15 == 0? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80);
appendPrettyHexDump(buf, buffer, offset, length);
return buf.toString();
}
}
private static void appendPrettyHexDump(StringBuilder dump, ByteBuf buf, int offset, int length) {
if (isOutOfBounds(offset, length, buf.capacity())) {
throw new IndexOutOfBoundsException(
"expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
+ ") <= " + "buf.capacity(" + buf.capacity() + ')');
}
if (length == 0) {
return;
}
dump.append(
" +-------------------------------------------------+" +
NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" +
NEWLINE + "+--------+-------------------------------------------------+----------------+");
final int startIndex = offset;
final int fullRows = length >>> 4;
final int remainder = length & 0xF;
// Dump the rows which have 16 bytes.
for (int row = 0; row < fullRows; row ++) {
int rowStartIndex = (row << 4) + startIndex;
// Per-row prefix.
appendHexDumpRowPrefix(dump, row, rowStartIndex);
// Hex dump
int rowEndIndex = rowStartIndex + 16;
for (int j = rowStartIndex; j < rowEndIndex; j ++) {
dump.append(BYTE2HEX[buf.getUnsignedByte(j)]);
}
dump.append(" |");
// ASCII dump
for (int j = rowStartIndex; j < rowEndIndex; j ++) {
dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
}
dump.append('|');
}
// Dump the last row which has less than 16 bytes.
if (remainder != 0) {
int rowStartIndex = (fullRows << 4) + startIndex;
appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
// Hex dump
int rowEndIndex = rowStartIndex + remainder;
for (int j = rowStartIndex; j < rowEndIndex; j ++) {
dump.append(BYTE2HEX[buf.getUnsignedByte(j)]);
}
dump.append(HEXPADDING[remainder]);
dump.append(" |");
// Ascii dump
for (int j = rowStartIndex; j < rowEndIndex; j ++) {
dump.append(BYTE2CHAR[buf.getUnsignedByte(j)]);
}
dump.append(BYTEPADDING[remainder]);
dump.append('|');
}
dump.append(NEWLINE +
"+--------+-------------------------------------------------+----------------+");
}
private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
if (row < HEXDUMP_ROWPREFIXES.length) {
dump.append(HEXDUMP_ROWPREFIXES[row]);
} else {
dump.append(NEWLINE);
dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
dump.setCharAt(dump.length() - 9, '|');
dump.append('|');
}
}
}
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();
handle.recycle(this);
}
}
}
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();
handle.recycle(this);
}
}
}
/**
* Returns {@code true} if the given {@link ByteBuf} is valid text using the given {@link Charset},
* otherwise return {@code false}.
*
* @param buf The given {@link ByteBuf}.
* @param charset The specified {@link Charset}.
*/
public static boolean isText(ByteBuf buf, Charset charset) {
return isText(buf, buf.readerIndex(), buf.readableBytes(), charset);
}
/**
* Returns {@code true} if the specified {@link ByteBuf} starting at {@code index} with {@code length} is valid
* text using the given {@link Charset}, otherwise return {@code false}.
*
* @param buf The given {@link ByteBuf}.
* @param index The start index of the specified buffer.
* @param length The length of the specified buffer.
* @param charset The specified {@link Charset}.
*
* @throws IndexOutOfBoundsException if {@code index} + {@code length} is greater than {@code buf.readableBytes}
*/
public static boolean isText(ByteBuf buf, int index, int length, Charset charset) {
checkNotNull(buf, "buf");
checkNotNull(charset, "charset");
final int maxIndex = buf.readerIndex() + buf.readableBytes();
if (index < 0 || length < 0 || index > maxIndex - length) {
throw new IndexOutOfBoundsException("index: " + index + " length: " + length);
}
if (charset.equals(CharsetUtil.UTF_8)) {
return isUtf8(buf, index, length);
} else if (charset.equals(CharsetUtil.US_ASCII)) {
return isAscii(buf, index, length);
} else {
CharsetDecoder decoder = CharsetUtil.decoder(charset, CodingErrorAction.REPORT, CodingErrorAction.REPORT);
try {
if (buf.nioBufferCount() == 1) {
decoder.decode(buf.internalNioBuffer(index, length));
} else {
ByteBuf heapBuffer = buf.alloc().heapBuffer(length);
try {
heapBuffer.writeBytes(buf, index, length);
decoder.decode(heapBuffer.internalNioBuffer(heapBuffer.readerIndex(), length));
} finally {
heapBuffer.release();
}
}
return true;
} catch (CharacterCodingException ignore) {
return false;
}
}
}
/**
* Aborts on a byte which is not a valid ASCII character.
*/
private static final ByteProcessor FIND_NON_ASCII = new ByteProcessor() {
@Override
public boolean process(byte value) {
return value >= 0;
}
};
/**
* Returns {@code true} if the specified {@link ByteBuf} starting at {@code index} with {@code length} is valid
* ASCII text, otherwise return {@code false}.
*
* @param buf The given {@link ByteBuf}.
* @param index The start index of the specified buffer.
* @param length The length of the specified buffer.
*/
private static boolean isAscii(ByteBuf buf, int index, int length) {
return buf.forEachByte(index, length, FIND_NON_ASCII) == -1;
}
/**
* Returns {@code true} if the specified {@link ByteBuf} starting at {@code index} with {@code length} is valid
* UTF8 text, otherwise return {@code false}.
*
* @param buf The given {@link ByteBuf}.
* @param index The start index of the specified buffer.
* @param length The length of the specified buffer.
*
* @see
* UTF-8 Definition
*
*
* 1. Bytes format of UTF-8
*
* The table below summarizes the format of these different octet types.
* The letter x indicates bits available for encoding bits of the character number.
*
* Char. number range | UTF-8 octet sequence
* (hexadecimal) | (binary)
* --------------------+---------------------------------------------
* 0000 0000-0000 007F | 0xxxxxxx
* 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
* 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
* 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*
*
*
* 2. Syntax of UTF-8 Byte Sequences
*
* UTF8-octets = *( UTF8-char )
* UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
* UTF8-1 = %x00-7F
* UTF8-2 = %xC2-DF UTF8-tail
* UTF8-3 = %xE0 %xA0-BF UTF8-tail /
* %xE1-EC 2( UTF8-tail ) /
* %xED %x80-9F UTF8-tail /
* %xEE-EF 2( UTF8-tail )
* UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) /
* %xF1-F3 3( UTF8-tail ) /
* %xF4 %x80-8F 2( UTF8-tail )
* UTF8-tail = %x80-BF
*
*/
private static boolean isUtf8(ByteBuf buf, int index, int length) {
final int endIndex = index + length;
while (index < endIndex) {
byte b1 = buf.getByte(index++);
byte b2, b3, b4;
if ((b1 & 0x80) == 0) {
// 1 byte
continue;
}
if ((b1 & 0xE0) == 0xC0) {
// 2 bytes
//
// Bit/Byte pattern
// 110xxxxx 10xxxxxx
// C2..DF 80..BF
if (index >= endIndex) { // no enough bytes
return false;
}
b2 = buf.getByte(index++);
if ((b2 & 0xC0) != 0x80) { // 2nd byte not starts with 10
return false;
}
if ((b1 & 0xFF) < 0xC2) { // out of lower bound
return false;
}
} else if ((b1 & 0xF0) == 0xE0) {
// 3 bytes
//
// Bit/Byte pattern
// 1110xxxx 10xxxxxx 10xxxxxx
// E0 A0..BF 80..BF
// E1..EC 80..BF 80..BF
// ED 80..9F 80..BF
// E1..EF 80..BF 80..BF
if (index > endIndex - 2) { // no enough bytes
return false;
}
b2 = buf.getByte(index++);
b3 = buf.getByte(index++);
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) { // 2nd or 3rd bytes not start with 10
return false;
}
if ((b1 & 0x0F) == 0x00 && (b2 & 0xFF) < 0xA0) { // out of lower bound
return false;
}
if ((b1 & 0x0F) == 0x0D && (b2 & 0xFF) > 0x9F) { // out of upper bound
return false;
}
} else if ((b1 & 0xF8) == 0xF0) {
// 4 bytes
//
// Bit/Byte pattern
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
// F0 90..BF 80..BF 80..BF
// F1..F3 80..BF 80..BF 80..BF
// F4 80..8F 80..BF 80..BF
if (index > endIndex - 3) { // no enough bytes
return false;
}
b2 = buf.getByte(index++);
b3 = buf.getByte(index++);
b4 = buf.getByte(index++);
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 || (b4 & 0xC0) != 0x80) {
// 2nd, 3rd or 4th bytes not start with 10
return false;
}
if ((b1 & 0xFF) > 0xF4 // b1 invalid
|| (b1 & 0xFF) == 0xF0 && (b2 & 0xFF) < 0x90 // b2 out of lower bound
|| (b1 & 0xFF) == 0xF4 && (b2 & 0xFF) > 0x8F) { // b2 out of upper bound
return false;
}
} else {
return false;
}
}
return true;
}
private ByteBufUtil() { }
}