All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.buffer.ByteBufUtil Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * 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:
 *
 *   https://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 io.netty.buffer;

import io.netty.util.AsciiString;
import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import io.netty.util.IllegalReferenceCountException;
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.internal.MathUtil;
import io.netty.util.internal.ObjectPool;
import io.netty.util.internal.ObjectPool.Handle;
import io.netty.util.internal.ObjectPool.ObjectCreator;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
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 io.netty.util.internal.MathUtil.isOutOfBounds;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
import static io.netty.util.internal.StringUtil.NEWLINE;
import static 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 BYTE_ARRAYS = new FastThreadLocal() {
        @Override
        protected byte[] initialValue() throws Exception {
            return PlatformDependent.allocateUninitializedArray(MAX_TL_ARRAY_LEN);
        }
    };

    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 int WRITE_CHUNK_SIZE = 8192;
    static final ByteBufAllocator DEFAULT_ALLOCATOR;

    static {
        String allocType = SystemPropertyUtil.get(
                "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("io.netty.threadLocalDirectBufferSize", 0);
        logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);

        MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
        logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
    }

    static final int MAX_TL_ARRAY_LEN = 1024;

    /**
     * Allocates a new array if minLength > {@link ByteBufUtil#MAX_TL_ARRAY_LEN}
     */
    static byte[] threadLocalTempArray(int minLength) {
        return minLength <= MAX_TL_ARRAY_LEN ? BYTE_ARRAYS.get()
            : PlatformDependent.allocateUninitializedArray(minLength);
    }

    /**
     * @return whether the specified buffer has a nonzero ref count
     */
    public static boolean isAccessible(ByteBuf buffer) {
        return buffer.isAccessible();
    }

    /**
     * @throws IllegalReferenceCountException if the buffer has a zero ref count
     * @return the passed in buffer
     */
    public static ByteBuf ensureAccessible(ByteBuf buffer) {
        if (!buffer.isAccessible()) {
            throw new IllegalReferenceCountException(buffer.refCnt());
        }
        return buffer;
    }

    /**
     * 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 = EmptyByteBuf.EMPTY_BYTE_BUF_HASH_CODE;
        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.
     * This method uses the Two-Way
     * string matching algorithm, which yields O(1) space complexity and excellent performance.
     */
    public static int indexOf(ByteBuf needle, ByteBuf haystack) {
        if (haystack == null || needle == null) {
            return -1;
        }

        if (needle.readableBytes() > haystack.readableBytes()) {
            return -1;
        }

        int n = haystack.readableBytes();
        int m = needle.readableBytes();
        if (m == 0) {
            return 0;
        }

        // When the needle has only one byte that can be read,
        // the ByteBuf.indexOf() can be used
        if (m == 1) {
            return haystack.indexOf(haystack.readerIndex(), haystack.writerIndex(),
                          needle.getByte(needle.readerIndex()));
        }

        int i;
        int j = 0;
        int aStartIndex = needle.readerIndex();
        int bStartIndex = haystack.readerIndex();
        long suffixes =  maxSuf(needle, m, aStartIndex, true);
        long prefixes = maxSuf(needle, m, aStartIndex, false);
        int ell = Math.max((int) (suffixes >> 32), (int) (prefixes >> 32));
        int per = Math.max((int) suffixes, (int) prefixes);
        int memory;
        int length = Math.min(m - per, ell + 1);

        if (equals(needle, aStartIndex, needle, aStartIndex + per,  length)) {
            memory = -1;
            while (j <= n - m) {
                i = Math.max(ell, memory) + 1;
                while (i < m && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                    ++i;
                }
                if (i > n) {
                    return -1;
                }
                if (i >= m) {
                    i = ell;
                    while (i > memory && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                        --i;
                    }
                    if (i <= memory) {
                        return j + bStartIndex;
                    }
                    j += per;
                    memory = m - per - 1;
                } else {
                    j += i - ell;
                    memory = -1;
                }
            }
        } else {
            per = Math.max(ell + 1, m - ell - 1) + 1;
            while (j <= n - m) {
                i = ell + 1;
                while (i < m && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                    ++i;
                }
                if (i > n) {
                    return -1;
                }
                if (i >= m) {
                    i = ell;
                    while (i >= 0 && needle.getByte(i + aStartIndex) == haystack.getByte(i + j + bStartIndex)) {
                        --i;
                    }
                    if (i < 0) {
                        return j + bStartIndex;
                    }
                    j += per;
                } else {
                    j += i - ell;
                }
            }
        }
        return -1;
    }

    private static long maxSuf(ByteBuf x, int m, int start, boolean isSuffix) {
        int p = 1;
        int ms = -1;
        int j = start;
        int k = 1;
        byte a;
        byte b;
        while (j + k < m) {
            a = x.getByte(j + k);
            b = x.getByte(ms + k);
            boolean suffix = isSuffix ? a < b : a > b;
            if (suffix) {
                j += k;
                k = 1;
                p = j - ms;
            } else if (a == b) {
                if (k != p) {
                    ++k;
                } else {
                    j += p;
                    k = 1;
                }
            } else {
                ms = j;
                j = ms + 1;
                k = p = 1;
            }
        }
        return ((long) ms << 32) + p;
    }

    /**
     * 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) { checkNotNull(a, "a"); checkNotNull(b, "b"); // All indexes and lengths must be non-negative checkPositiveOrZero(aStartIndex, "aStartIndex"); checkPositiveOrZero(bStartIndex, "bStartIndex"); checkPositiveOrZero(length, "length"); 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) { if (bufferA == bufferB) { return true; } 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) { if (bufferA == bufferB) { return 0; } 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 = uintFromLE(bufferA.getUnsignedIntLE(aIndex)) - uintFromLE(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 a = bufferA.getUnsignedInt(aIndex); long b = uintFromLE(bufferB.getUnsignedIntLE(bIndex)); long comp = a - b; 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 a = uintFromLE(bufferA.getUnsignedIntLE(aIndex)); long b = bufferB.getUnsignedInt(bIndex); long comp = a - b; if (comp != 0) { return comp; } } return 0; } private static long uintFromLE(long value) { return Long.reverseBytes(value) >>> Integer.SIZE; } private static final class SWARByteSearch { private static long compilePattern(byte byteToFind) { return (byteToFind & 0xFFL) * 0x101010101010101L; } private static int firstAnyPattern(long word, long pattern, boolean leading) { long input = word ^ pattern; long tmp = (input & 0x7F7F7F7F7F7F7F7FL) + 0x7F7F7F7F7F7F7F7FL; tmp = ~(tmp | input | 0x7F7F7F7F7F7F7F7FL); final int binaryPosition = leading? Long.numberOfLeadingZeros(tmp) : Long.numberOfTrailingZeros(tmp); return binaryPosition >>> 3; } } private static int unrolledFirstIndexOf(AbstractByteBuf buffer, int fromIndex, int byteCount, byte value) { assert byteCount > 0 && byteCount < 8; if (buffer._getByte(fromIndex) == value) { return fromIndex; } if (byteCount == 1) { return -1; } if (buffer._getByte(fromIndex + 1) == value) { return fromIndex + 1; } if (byteCount == 2) { return -1; } if (buffer._getByte(fromIndex + 2) == value) { return fromIndex + 2; } if (byteCount == 3) { return -1; } if (buffer._getByte(fromIndex + 3) == value) { return fromIndex + 3; } if (byteCount == 4) { return -1; } if (buffer._getByte(fromIndex + 4) == value) { return fromIndex + 4; } if (byteCount == 5) { return -1; } if (buffer._getByte(fromIndex + 5) == value) { return fromIndex + 5; } if (byteCount == 6) { return -1; } if (buffer._getByte(fromIndex + 6) == value) { return fromIndex + 6; } return -1; } /** * This is using a SWAR (SIMD Within A Register) batch read technique to minimize bound-checks and improve memory * usage while searching for {@code value}. */ static int firstIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte value) { fromIndex = Math.max(fromIndex, 0); if (fromIndex >= toIndex || buffer.capacity() == 0) { return -1; } final int length = toIndex - fromIndex; buffer.checkIndex(fromIndex, length); if (!PlatformDependent.isUnaligned()) { return linearFirstIndexOf(buffer, fromIndex, toIndex, value); } assert PlatformDependent.isUnaligned(); int offset = fromIndex; final int byteCount = length & 7; if (byteCount > 0) { final int index = unrolledFirstIndexOf(buffer, fromIndex, byteCount, value); if (index != -1) { return index; } offset += byteCount; if (offset == toIndex) { return -1; } } final int longCount = length >>> 3; final ByteOrder nativeOrder = ByteOrder.nativeOrder(); final boolean isNative = nativeOrder == buffer.order(); final boolean useLE = nativeOrder == ByteOrder.LITTLE_ENDIAN; final long pattern = SWARByteSearch.compilePattern(value); for (int i = 0; i < longCount; i++) { // use the faster available getLong final long word = useLE? buffer._getLongLE(offset) : buffer._getLong(offset); int index = SWARByteSearch.firstAnyPattern(word, pattern, isNative); if (index < Long.BYTES) { return offset + index; } offset += Long.BYTES; } return -1; } private static int linearFirstIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte value) { for (int i = fromIndex; i < toIndex; i++) { if (buffer._getByte(i) == value) { return i; } } return -1; } /** * 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) { return buffer.indexOf(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.writeShort(swapShort((short) 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.setShort(index, swapShort((short) 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.writeMedium(swapMedium(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(); } } } static int lastIndexOf(AbstractByteBuf buffer, int fromIndex, int toIndex, byte value) { assert fromIndex > toIndex; final int capacity = buffer.capacity(); fromIndex = Math.min(fromIndex, capacity); if (fromIndex < 0 || capacity == 0) { return -1; } buffer.checkIndex(toIndex, fromIndex - toIndex); for (int i = fromIndex - 1; i >= toIndex; i--) { if (buffer._getByte(i) == value) { return i; } } return -1; } private static CharSequence checkCharSequenceBounds(CharSequence seq, int start, int end) { if (MathUtil.isOutOfBounds(start, end - start, seq.length())) { throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= seq.length(" + seq.length() + ')'); } return seq; } /** * 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}. *

* It behaves like {@link #reserveAndWriteUtf8(ByteBuf, CharSequence, int)} with {@code reserveBytes} * computed by {@link #utf8MaxBytes(CharSequence)}.
* This method returns the actual number of bytes written. */ public static int writeUtf8(ByteBuf buf, CharSequence seq) { int seqLength = seq.length(); return reserveAndWriteUtf8Seq(buf, seq, 0, seqLength, utf8MaxBytes(seqLength)); } /** * Equivalent to {@link #writeUtf8(ByteBuf, CharSequence) writeUtf8(buf, seq.subSequence(start, end))} * but avoids subsequence object allocation. */ public static int writeUtf8(ByteBuf buf, CharSequence seq, int start, int end) { checkCharSequenceBounds(seq, start, end); return reserveAndWriteUtf8Seq(buf, seq, start, end, utf8MaxBytes(end - start)); } /** * Encode a {@link CharSequence} in UTF-8 and write * it into {@code reserveBytes} of a {@link ByteBuf}. *

* The {@code reserveBytes} must be computed (ie eagerly using {@link #utf8MaxBytes(CharSequence)} * or exactly with {@link #utf8Bytes(CharSequence)}) to ensure this method to not fail: for performance reasons * the index checks will be performed using just {@code reserveBytes}.
* This method returns the actual number of bytes written. */ public static int reserveAndWriteUtf8(ByteBuf buf, CharSequence seq, int reserveBytes) { return reserveAndWriteUtf8Seq(buf, seq, 0, seq.length(), reserveBytes); } /** * Equivalent to {@link #reserveAndWriteUtf8(ByteBuf, CharSequence, int) * reserveAndWriteUtf8(buf, seq.subSequence(start, end), reserveBytes)} but avoids * subsequence object allocation if possible. * * @return actual number of bytes written */ public static int reserveAndWriteUtf8(ByteBuf buf, CharSequence seq, int start, int end, int reserveBytes) { return reserveAndWriteUtf8Seq(buf, checkCharSequenceBounds(seq, start, end), start, end, reserveBytes); } private static int reserveAndWriteUtf8Seq(ByteBuf buf, CharSequence seq, int start, int end, int reserveBytes) { for (;;) { if (buf instanceof WrappedCompositeByteBuf) { // WrappedCompositeByteBuf is a sub-class of AbstractByteBuf so it needs special handling. buf = buf.unwrap(); } else if (buf instanceof AbstractByteBuf) { AbstractByteBuf byteBuf = (AbstractByteBuf) buf; byteBuf.ensureWritable0(reserveBytes); int written = writeUtf8(byteBuf, byteBuf.writerIndex, reserveBytes, seq, start, end); 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.subSequence(start, end).toString().getBytes(CharsetUtil.UTF_8); buf.writeBytes(bytes); return bytes.length; } } } static int writeUtf8(AbstractByteBuf buffer, int writerIndex, int reservedBytes, CharSequence seq, int len) { return writeUtf8(buffer, writerIndex, reservedBytes, seq, 0, len); } // Fast-Path implementation static int writeUtf8(AbstractByteBuf buffer, int writerIndex, int reservedBytes, CharSequence seq, int start, int end) { if (seq instanceof AsciiString) { writeAsciiString(buffer, writerIndex, (AsciiString) seq, start, end); return end - start; } if (PlatformDependent.hasUnsafe()) { if (buffer.hasArray()) { return unsafeWriteUtf8(buffer.array(), PlatformDependent.byteArrayBaseOffset(), buffer.arrayOffset() + writerIndex, seq, start, end); } if (buffer.hasMemoryAddress()) { return unsafeWriteUtf8(null, buffer.memoryAddress(), writerIndex, seq, start, end); } } else { if (buffer.hasArray()) { return safeArrayWriteUtf8(buffer.array(), buffer.arrayOffset() + writerIndex, seq, start, end); } if (buffer.isDirect()) { assert buffer.nioBufferCount() == 1; final ByteBuffer internalDirectBuffer = buffer.internalNioBuffer(writerIndex, reservedBytes); final int bufferPosition = internalDirectBuffer.position(); return safeDirectWriteUtf8(internalDirectBuffer, bufferPosition, seq, start, end); } } return safeWriteUtf8(buffer, writerIndex, seq, start, end); } // AsciiString Fast-Path implementation - no explicit bound-checks static void writeAsciiString(AbstractByteBuf buffer, int writerIndex, AsciiString seq, int start, int end) { final int begin = seq.arrayOffset() + start; final int length = end - start; if (PlatformDependent.hasUnsafe()) { if (buffer.hasArray()) { PlatformDependent.copyMemory(seq.array(), begin, buffer.array(), buffer.arrayOffset() + writerIndex, length); return; } if (buffer.hasMemoryAddress()) { PlatformDependent.copyMemory(seq.array(), begin, buffer.memoryAddress() + writerIndex, length); return; } } if (buffer.hasArray()) { System.arraycopy(seq.array(), begin, buffer.array(), buffer.arrayOffset() + writerIndex, length); return; } buffer.setBytes(writerIndex, seq.array(), begin, length); } // Safe off-heap Fast-Path implementation private static int safeDirectWriteUtf8(ByteBuffer buffer, int writerIndex, CharSequence seq, int start, int end) { assert !(seq instanceof AsciiString); 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 = start; i < end; i++) { char c = seq.charAt(i); if (c < 0x80) { buffer.put(writerIndex++, (byte) c); } else if (c < 0x800) { buffer.put(writerIndex++, (byte) (0xc0 | (c >> 6))); buffer.put(writerIndex++, (byte) (0x80 | (c & 0x3f))); } else if (isSurrogate(c)) { if (!Character.isHighSurrogate(c)) { buffer.put(writerIndex++, WRITE_UTF_UNKNOWN); continue; } // Surrogate Pair consumes 2 characters. if (++i == end) { buffer.put(writerIndex++, WRITE_UTF_UNKNOWN); break; } // Extra method is copied here to NOT allow inlining of writeUtf8 // and increase the chance to inline CharSequence::charAt instead char c2 = seq.charAt(i); if (!Character.isLowSurrogate(c2)) { buffer.put(writerIndex++, WRITE_UTF_UNKNOWN); buffer.put(writerIndex++, Character.isHighSurrogate(c2)? WRITE_UTF_UNKNOWN : (byte) c2); } else { int codePoint = Character.toCodePoint(c, c2); // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. buffer.put(writerIndex++, (byte) (0xf0 | (codePoint >> 18))); buffer.put(writerIndex++, (byte) (0x80 | ((codePoint >> 12) & 0x3f))); buffer.put(writerIndex++, (byte) (0x80 | ((codePoint >> 6) & 0x3f))); buffer.put(writerIndex++, (byte) (0x80 | (codePoint & 0x3f))); } } else { buffer.put(writerIndex++, (byte) (0xe0 | (c >> 12))); buffer.put(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f))); buffer.put(writerIndex++, (byte) (0x80 | (c & 0x3f))); } } return writerIndex - oldWriterIndex; } // Safe off-heap Fast-Path implementation private static int safeWriteUtf8(AbstractByteBuf buffer, int writerIndex, CharSequence seq, int start, int end) { assert !(seq instanceof AsciiString); 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 = start; i < end; 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; } // Surrogate Pair consumes 2 characters. if (++i == end) { buffer._setByte(writerIndex++, WRITE_UTF_UNKNOWN); break; } // Extra method is copied here to NOT allow inlining of writeUtf8 // and increase the chance to inline CharSequence::charAt instead char c2 = seq.charAt(i); if (!Character.isLowSurrogate(c2)) { buffer._setByte(writerIndex++, WRITE_UTF_UNKNOWN); buffer._setByte(writerIndex++, Character.isHighSurrogate(c2)? WRITE_UTF_UNKNOWN : c2); } else { int codePoint = Character.toCodePoint(c, c2); // See https://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; } // safe byte[] Fast-Path implementation private static int safeArrayWriteUtf8(byte[] buffer, int writerIndex, CharSequence seq, int start, int end) { int oldWriterIndex = writerIndex; for (int i = start; i < end; i++) { char c = seq.charAt(i); if (c < 0x80) { buffer[writerIndex++] = (byte) c; } else if (c < 0x800) { buffer[writerIndex++] = (byte) (0xc0 | (c >> 6)); buffer[writerIndex++] = (byte) (0x80 | (c & 0x3f)); } else if (isSurrogate(c)) { if (!Character.isHighSurrogate(c)) { buffer[writerIndex++] = WRITE_UTF_UNKNOWN; continue; } // Surrogate Pair consumes 2 characters. if (++i == end) { buffer[writerIndex++] = WRITE_UTF_UNKNOWN; break; } char c2 = seq.charAt(i); // Extra method is copied here to NOT allow inlining of writeUtf8 // and increase the chance to inline CharSequence::charAt instead if (!Character.isLowSurrogate(c2)) { buffer[writerIndex++] = WRITE_UTF_UNKNOWN; buffer[writerIndex++] = (byte) (Character.isHighSurrogate(c2)? WRITE_UTF_UNKNOWN : c2); } else { int codePoint = Character.toCodePoint(c, c2); // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. buffer[writerIndex++] = (byte) (0xf0 | (codePoint >> 18)); buffer[writerIndex++] = (byte) (0x80 | ((codePoint >> 12) & 0x3f)); buffer[writerIndex++] = (byte) (0x80 | ((codePoint >> 6) & 0x3f)); buffer[writerIndex++] = (byte) (0x80 | (codePoint & 0x3f)); } } else { buffer[writerIndex++] = (byte) (0xe0 | (c >> 12)); buffer[writerIndex++] = (byte) (0x80 | ((c >> 6) & 0x3f)); buffer[writerIndex++] = (byte) (0x80 | (c & 0x3f)); } } return writerIndex - oldWriterIndex; } // unsafe Fast-Path implementation private static int unsafeWriteUtf8(byte[] buffer, long memoryOffset, int writerIndex, CharSequence seq, int start, int end) { assert !(seq instanceof AsciiString); long writerOffset = memoryOffset + writerIndex; final long oldWriterOffset = writerOffset; for (int i = start; i < end; i++) { char c = seq.charAt(i); if (c < 0x80) { PlatformDependent.putByte(buffer, writerOffset++, (byte) c); } else if (c < 0x800) { PlatformDependent.putByte(buffer, writerOffset++, (byte) (0xc0 | (c >> 6))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | (c & 0x3f))); } else if (isSurrogate(c)) { if (!Character.isHighSurrogate(c)) { PlatformDependent.putByte(buffer, writerOffset++, WRITE_UTF_UNKNOWN); continue; } // Surrogate Pair consumes 2 characters. if (++i == end) { PlatformDependent.putByte(buffer, writerOffset++, WRITE_UTF_UNKNOWN); break; } char c2 = seq.charAt(i); // Extra method is copied here to NOT allow inlining of writeUtf8 // and increase the chance to inline CharSequence::charAt instead if (!Character.isLowSurrogate(c2)) { PlatformDependent.putByte(buffer, writerOffset++, WRITE_UTF_UNKNOWN); PlatformDependent.putByte(buffer, writerOffset++, (byte) (Character.isHighSurrogate(c2)? WRITE_UTF_UNKNOWN : c2)); } else { int codePoint = Character.toCodePoint(c, c2); // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. PlatformDependent.putByte(buffer, writerOffset++, (byte) (0xf0 | (codePoint >> 18))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | ((codePoint >> 12) & 0x3f))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | ((codePoint >> 6) & 0x3f))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | (codePoint & 0x3f))); } } else { PlatformDependent.putByte(buffer, writerOffset++, (byte) (0xe0 | (c >> 12))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | ((c >> 6) & 0x3f))); PlatformDependent.putByte(buffer, writerOffset++, (byte) (0x80 | (c & 0x3f))); } } return (int) (writerOffset - oldWriterOffset); } /** * Returns max bytes length of UTF8 character sequence of the given length. */ public static int utf8MaxBytes(final int seqLength) { return seqLength * MAX_BYTES_PER_CHAR_UTF8; } /** * Returns max bytes length of UTF8 character sequence. *

* It behaves like {@link #utf8MaxBytes(int)} applied to {@code seq} {@link CharSequence#length()}. */ public static int utf8MaxBytes(CharSequence seq) { return utf8MaxBytes(seq.length()); } /** * Returns the exact bytes length of UTF8 character sequence. *

* This method is producing the exact length according to {@link #writeUtf8(ByteBuf, CharSequence)}. */ public static int utf8Bytes(final CharSequence seq) { return utf8ByteCount(seq, 0, seq.length()); } /** * Equivalent to {@link #utf8Bytes(CharSequence) utf8Bytes(seq.subSequence(start, end))} * but avoids subsequence object allocation. *

* This method is producing the exact length according to {@link #writeUtf8(ByteBuf, CharSequence, int, int)}. */ public static int utf8Bytes(final CharSequence seq, int start, int end) { return utf8ByteCount(checkCharSequenceBounds(seq, start, end), start, end); } private static int utf8ByteCount(final CharSequence seq, int start, int end) { if (seq instanceof AsciiString) { return end - start; } int i = start; // ASCII fast path while (i < end && seq.charAt(i) < 0x80) { ++i; } // !ASCII is packed in a separate method to let the ASCII case be smaller return i < end ? (i - start) + utf8BytesNonAscii(seq, i, end) : i - start; } private static int utf8BytesNonAscii(final CharSequence seq, final int start, final int end) { int encodedLength = 0; for (int i = start; i < end; i++) { final char c = seq.charAt(i); // making it 100% branchless isn't rewarding due to the many bit operations necessary! if (c < 0x800) { // branchless version of: (c <= 127 ? 0:1) + 1 encodedLength += ((0x7f - c) >>> 31) + 1; } else if (isSurrogate(c)) { if (!Character.isHighSurrogate(c)) { encodedLength++; // WRITE_UTF_UNKNOWN continue; } // Surrogate Pair consumes 2 characters. if (++i == end) { encodedLength++; // WRITE_UTF_UNKNOWN break; } if (!Character.isLowSurrogate(seq.charAt(i))) { // WRITE_UTF_UNKNOWN + (Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2) encodedLength += 2; continue; } // See https://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. encodedLength += 4; } else { encodedLength += 3; } } return encodedLength; } /** * 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 for (;;) { if (buf instanceof WrappedCompositeByteBuf) { // WrappedCompositeByteBuf is a sub-class of AbstractByteBuf so it needs special handling. buf = buf.unwrap(); } else if (buf instanceof AbstractByteBuf) { final int len = seq.length(); AbstractByteBuf byteBuf = (AbstractByteBuf) buf; byteBuf.ensureWritable0(len); if (seq instanceof AsciiString) { writeAsciiString(byteBuf, byteBuf.writerIndex, (AsciiString) seq, 0, len); } else { final int written = writeAscii(byteBuf, byteBuf.writerIndex, seq, len); assert written == len; } byteBuf.writerIndex += len; return len; } 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; } } } // 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(); } } } @SuppressWarnings("deprecation") static String decodeString(ByteBuf src, int readerIndex, int len, Charset charset) { if (len == 0) { return StringUtil.EMPTY_STRING; } final byte[] array; final int offset; if (src.hasArray()) { array = src.array(); offset = src.arrayOffset() + readerIndex; } else { array = threadLocalTempArray(len); offset = 0; src.getBytes(readerIndex, array, 0, len); } if (CharsetUtil.US_ASCII.equals(charset)) { // Fast-path for US-ASCII which is used frequently. return new String(array, 0, offset, len); } return new String(array, offset, len, charset); } /** * 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) { int capacity = buf.capacity(); if (isOutOfBounds(start, length, capacity)) { throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length + ") <= " + "buf.capacity(" + capacity + ')'); } if (buf.hasArray()) { int baseOffset = buf.arrayOffset() + start; byte[] bytes = buf.array(); if (copy || baseOffset != 0 || length != bytes.length) { return Arrays.copyOfRange(bytes, baseOffset, baseOffset + length); } else { return bytes; } } byte[] bytes = PlatformDependent.allocateUninitializedArray(length); buf.getBytes(start, bytes); return bytes; } /** * 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) { checkPositiveOrZero(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) { checkPositiveOrZero(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 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) + offset; // 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) + offset; 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 ObjectPool RECYCLER = ObjectPool.newPool(new ObjectCreator() { @Override public ThreadLocalUnsafeDirectByteBuf newObject(Handle handle) { return new ThreadLocalUnsafeDirectByteBuf(handle); } }); static ThreadLocalUnsafeDirectByteBuf newInstance() { ThreadLocalUnsafeDirectByteBuf buf = RECYCLER.get(); buf.resetRefCnt(); 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 ObjectPool RECYCLER = ObjectPool.newPool( new ObjectCreator() { @Override public ThreadLocalDirectByteBuf newObject(Handle handle) { return new ThreadLocalDirectByteBuf(handle); } }); static ThreadLocalDirectByteBuf newInstance() { ThreadLocalDirectByteBuf buf = RECYCLER.get(); buf.resetRefCnt(); 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.nioBuffer(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; } /** * Read bytes from the given {@link ByteBuffer} into the given {@link OutputStream} using the {@code position} and * {@code length}. The position and limit of the given {@link ByteBuffer} may be adjusted. */ static void readBytes(ByteBufAllocator allocator, ByteBuffer buffer, int position, int length, OutputStream out) throws IOException { if (buffer.hasArray()) { out.write(buffer.array(), position + buffer.arrayOffset(), length); } else { int chunkLen = Math.min(length, WRITE_CHUNK_SIZE); buffer.clear().position(position); if (length <= MAX_TL_ARRAY_LEN || !allocator.isDirectBufferPooled()) { getBytes(buffer, threadLocalTempArray(chunkLen), 0, chunkLen, out, length); } else { // if direct buffers are pooled chances are good that heap buffers are pooled as well. ByteBuf tmpBuf = allocator.heapBuffer(chunkLen); try { byte[] tmp = tmpBuf.array(); int offset = tmpBuf.arrayOffset(); getBytes(buffer, tmp, offset, chunkLen, out, length); } finally { tmpBuf.release(); } } } } private static void getBytes(ByteBuffer inBuffer, byte[] in, int inOffset, int inLen, OutputStream out, int outLen) throws IOException { do { int len = Math.min(inLen, outLen); inBuffer.get(in, inOffset, len); out.write(in, inOffset, len); outLen -= len; } while (outLen > 0); } private ByteBufUtil() { } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy