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

shade.com.alibaba.fastjson2.JSONWriterJSONB Maven / Gradle / Ivy

There is a newer version: 1.3.7
Show newest version
package com.alibaba.fastjson2;

import com.alibaba.fastjson2.internal.trove.map.hash.TLongIntHashMap;
import com.alibaba.fastjson2.util.DateUtils;
import com.alibaba.fastjson2.util.Fnv;
import com.alibaba.fastjson2.util.IOUtils;
import com.alibaba.fastjson2.util.JDKUtils;
import com.alibaba.fastjson2.writer.ObjectWriter;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.*;
import java.util.*;

import static com.alibaba.fastjson2.JSONB.Constants.*;
import static com.alibaba.fastjson2.JSONFactory.*;
import static com.alibaba.fastjson2.JSONWriter.Feature.*;
import static com.alibaba.fastjson2.util.DateUtils.OFFSET_8_ZONE_ID_NAME;
import static com.alibaba.fastjson2.util.DateUtils.SHANGHAI_ZONE_ID_NAME;
import static com.alibaba.fastjson2.util.JDKUtils.*;
import static com.alibaba.fastjson2.util.TypeUtils.*;

final class JSONWriterJSONB
        extends JSONWriter {
    // optimize for write ZonedDateTime
    static final byte[] SHANGHAI_ZONE_ID_NAME_BYTES = JSONB.toBytes(SHANGHAI_ZONE_ID_NAME);
    static final byte[] OFFSET_8_ZONE_ID_NAME_BYTES = JSONB.toBytes(OFFSET_8_ZONE_ID_NAME);
    static final long WRITE_ENUM_USING_STRING_MASK = WriteEnumUsingToString.mask | WriteEnumsUsingName.mask;

    private final CacheItem cacheItem;
    private byte[] bytes;
    private TLongIntHashMap symbols;
    private int symbolIndex;

    private long rootTypeNameHash;

    JSONWriterJSONB(Context ctx, SymbolTable symbolTable) {
        super(ctx, symbolTable, true, StandardCharsets.UTF_8);
        cacheItem = CACHE_ITEMS[System.identityHashCode(Thread.currentThread()) & (CACHE_ITEMS.length - 1)];
        byte[] bytes = BYTES_UPDATER.getAndSet(cacheItem, null);
        if (bytes == null) {
            bytes = new byte[8192];
        }
        this.bytes = bytes;
    }

    @Override
    public void close() {
        final byte[] bytes = this.bytes;
        if (bytes.length < CACHE_THRESHOLD) {
            BYTES_UPDATER.lazySet(cacheItem, bytes);
        }
    }

    @Override
    public void writeAny(Object value) {
        if (value == null) {
            writeNull();
            return;
        }

        boolean fieldBased = (context.features & Feature.FieldBased.mask) != 0;

        Class valueClass = value.getClass();
        ObjectWriter objectWriter = context.provider.getObjectWriter(valueClass, valueClass, fieldBased);

        if (isBeanToArray()) {
            objectWriter.writeArrayMappingJSONB(this, value, null, null, 0);
        } else {
            objectWriter.writeJSONB(this, value, null, null, 0);
        }
    }

    @Override
    public void startObject() {
        if (level >= context.maxLevel) {
            throw new JSONException("level too large : " + level);
        }

        level++;
        int off = this.off;
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off] = BC_OBJECT;
        this.off = off + 1;
    }

    @Override
    public void endObject() {
        level--;
        int off = this.off;
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off] = BC_OBJECT_END;
        this.off = off + 1;
    }

    @Override
    public void startArray() {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void startArray(Object array, int size) {
        if (isWriteTypeInfo(array)) {
            writeTypeName(array.getClass().getName());
        }

        int off = this.off;
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }

        final byte[] bytes = this.bytes;
        boolean tinyInt = size <= ARRAY_FIX_LEN;
        bytes[off] = tinyInt ? (byte) (BC_ARRAY_FIX_MIN + size) : BC_ARRAY;
        this.off = off + 1;
        if (!tinyInt) {
            writeInt32(size);
        }
    }

    @Override
    public void startArray(int size) {
        int off = this.off;
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }

        final byte[] bytes = this.bytes;
        boolean tinyInt = size <= ARRAY_FIX_LEN;
        bytes[off] = tinyInt ? (byte) (BC_ARRAY_FIX_MIN + size) : BC_ARRAY;
        this.off = off + 1;
        if (!tinyInt) {
            writeInt32(size);
        }
    }

    @Override
    public void writeRaw(byte b) {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off++] = b;
    }

    @Override
    public void writeChar(char ch) {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off++] = BC_CHAR;
        writeInt32(ch);
    }

    @Override
    public void writeName(String name) {
        writeString(name);
    }

    @Override
    public void writeNull() {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off++] = BC_NULL;
    }

    @Override
    public void writeStringNull() {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off++] = BC_NULL;
    }

    @Override
    public void endArray() {
    }

    @Override
    public void writeComma() {
        throw new JSONException("unsupported operation");
    }

    @Override
    protected void write0(char ch) {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void writeString(char[] chars, int off, int len, boolean quote) {
        if (chars == null) {
            writeNull();
            return;
        }

        boolean ascii = true;
        for (int i = 0; i < len; ++i) {
            if (chars[i + off] > 0x00FF) {
                ascii = false;
                break;
            }
        }

        if (ascii) {
            if (len <= STR_ASCII_FIX_LEN) {
                bytes[this.off++] = (byte) (len + BC_STR_ASCII_FIX_MIN);
            } else {
                bytes[this.off++] = BC_STR_ASCII;
                writeInt32(len);
            }
            for (int i = 0; i < len; ++i) {
                bytes[this.off++] = (byte) chars[off + i];
            }
            return;
        }

        writeString(new String(chars, off, len));
    }

    public void writeStringLatin1(final byte[] value) {
        if (value == null) {
            writeStringNull();
            return;
        }

        int off = this.off;
        int strlen = value.length;
        int minCapacity = value.length
                + off
                + 5 /*max str len*/
                + 1;

        if (minCapacity - bytes.length > 0) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (strlen <= STR_ASCII_FIX_LEN) {
            bytes[off++] = (byte) (strlen + BC_STR_ASCII_FIX_MIN);
        } else if (strlen <= INT32_BYTE_MAX) {
            putStringSizeSmall(bytes, off, strlen);
            off += 3;
        } else {
            off += putStringSizeLarge(bytes, off, strlen);
        }
        System.arraycopy(value, 0, bytes, off, value.length);
        this.off = off + strlen;
    }

    private static void putStringSizeSmall(byte[] bytes, int off, int val) {
        bytes[off] = BC_STR_ASCII;
        bytes[off + 1] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
        bytes[off + 2] = (byte) (val);
    }

    private static int putStringSizeLarge(byte[] bytes, int off, int strlen) {
        if (strlen <= INT32_SHORT_MAX) {
            bytes[off] = BC_STR_ASCII;
            bytes[off + 1] = (byte) (BC_INT32_SHORT_ZERO + (strlen >> 16));
            bytes[off + 2] = (byte) (strlen >> 8);
            bytes[off + 3] = (byte) (strlen);
            return 4;
        }

        bytes[off] = BC_STR_ASCII;
        bytes[off + 1] = BC_INT32;
        UNSAFE.putInt(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 2,
                BIG_ENDIAN ? strlen : Integer.reverseBytes(strlen)
        );
        return 6;
    }

    @Override
    public void writeString(final char[] chars) {
        if (chars == null) {
            writeNull();
            return;
        }

        int off = this.off;
        boolean ascii = true;
        int strlen = chars.length;
        if (chars.length < STR_ASCII_FIX_LEN) {
            int minCapacity = off + 1 + strlen;
            if (minCapacity - bytes.length > 0) {
                ensureCapacity(minCapacity);
            }

            bytes[off++] = (byte) (strlen + BC_STR_ASCII_FIX_MIN);
            for (int i = 0; i < chars.length; i++) {
                char ch = chars[i];
                if (ch > 0x00FF) {
                    ascii = false;
                    break;
                }
                bytes[off++] = (byte) ch;
            }

            if (ascii) {
                this.off = off;
                return;
            } else {
                off = this.off;
            }
        }

        {
            int i = 0;
            int upperBound = chars.length & ~3;
            for (; i < upperBound; i += 4) {
                char c0 = chars[i];
                char c1 = chars[i + 1];
                char c2 = chars[i + 2];
                char c3 = chars[i + 3];
                if (c0 > 0x00FF || c1 > 0x00FF || c2 > 0x00FF || c3 > 0x00FF) {
                    ascii = false;
                    break;
                }
            }
            if (ascii) {
                for (; i < chars.length; ++i) {
                    if (chars[i] > 0x00FF) {
                        ascii = false;
                        break;
                    }
                }
            }
        }

        int minCapacity = (ascii ? strlen : strlen * 3)
                + off
                + 5 /*max str len*/
                + 1;

        if (minCapacity - bytes.length > 0) {
            ensureCapacity(minCapacity);
        }

        if (ascii) {
            if (strlen <= STR_ASCII_FIX_LEN) {
                bytes[off++] = (byte) (strlen + BC_STR_ASCII_FIX_MIN);
            } else if (strlen <= INT32_BYTE_MAX) {
                putStringSizeSmall(bytes, off, strlen);
                off += 3;
            } else {
                off += putStringSizeLarge(bytes, off, strlen);
            }
            for (int i = 0; i < chars.length; i++) {
                bytes[off++] = (byte) chars[i];
            }
        } else {
            int maxSize = chars.length * 3;
            int lenByteCnt = sizeOfInt(maxSize);
            ensureCapacity(off + maxSize + lenByteCnt + 1);
            int result = IOUtils.encodeUTF8(chars, 0, chars.length, bytes, off + lenByteCnt + 1);

            int utf8len = result - off - lenByteCnt - 1;
            int utf8lenByteCnt = sizeOfInt(utf8len);
            if (lenByteCnt != utf8lenByteCnt) {
                System.arraycopy(bytes, off + lenByteCnt + 1, bytes, off + utf8lenByteCnt + 1, utf8len);
            }
            final byte[] bytes = this.bytes;
            bytes[off++] = BC_STR_UTF8;
            if (utf8len >= BC_INT32_NUM_MIN && utf8len <= BC_INT32_NUM_MAX) {
                bytes[off++] = (byte) utf8len;
            } else if (utf8len >= INT32_BYTE_MIN && utf8len <= INT32_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (utf8len >> 8));
                bytes[off + 1] = (byte) (utf8len);
                off += 2;
            } else {
                off += writeInt32(bytes, off, utf8len);
            }
            off += utf8len;
        }
        this.off = off;
    }

    @Override
    public void writeString(final char[] chars, final int charsOff, final int len) {
        if (chars == null) {
            writeNull();
            return;
        }

        boolean ascii = true;

        if (len < STR_ASCII_FIX_LEN) {
            final int mark = this.off;

            int minCapacity = this.off + 1 + len;
            if (minCapacity - bytes.length > 0) {
                ensureCapacity(minCapacity);
            }

            bytes[this.off++] = (byte) (len + BC_STR_ASCII_FIX_MIN);
            for (int i = charsOff; i < len; i++) {
                char ch = chars[i];
                if (ch > 0x00FF) {
                    ascii = false;
                    break;
                }
                bytes[this.off++] = (byte) ch;
            }

            if (ascii) {
                return;
            }

            this.off = mark;
        }

        {
            int i = charsOff;
            int upperBound = chars.length & ~3;
            for (; i < upperBound; i += 4) {
                char c0 = chars[i];
                char c1 = chars[i + 1];
                char c2 = chars[i + 2];
                char c3 = chars[i + 3];
                if (c0 > 0x00FF || c1 > 0x00FF || c2 > 0x00FF || c3 > 0x00FF) {
                    ascii = false;
                    break;
                }
            }
            if (ascii) {
                for (; i < chars.length; ++i) {
                    if (chars[i] > 0x00FF) {
                        ascii = false;
                        break;
                    }
                }
            }
        }

        int minCapacity = (ascii ? len : len * 3)
                + this.off
                + 5 /*max str len*/
                + 1;

        if (minCapacity - bytes.length > 0) {
            ensureCapacity(minCapacity);
        }

        if (ascii) {
            byte[] bytes = this.bytes;
            if (len <= STR_ASCII_FIX_LEN) {
                bytes[this.off++] = (byte) (len + BC_STR_ASCII_FIX_MIN);
            } else if (len <= INT32_BYTE_MAX) {
                int off = this.off;
                bytes[off] = BC_STR_ASCII;
                bytes[off + 1] = (byte) (BC_INT32_BYTE_ZERO + (len >> 8));
                bytes[off + 2] = (byte) (len);
                this.off += 3;
            } else {
                bytes[this.off++] = BC_STR_ASCII;
                writeInt32(len);
            }
            for (int i = 0; i < chars.length; i++) {
                bytes[this.off++] = (byte) chars[i];
            }
        } else {
            int maxSize = chars.length * 3;
            int lenByteCnt = sizeOfInt(maxSize);
            ensureCapacity(this.off + maxSize + lenByteCnt + 1);
            int result = IOUtils.encodeUTF8(chars, 0, chars.length, bytes, this.off + lenByteCnt + 1);

            int utf8len = result - this.off - lenByteCnt - 1;
            int utf8lenByteCnt = sizeOfInt(utf8len);
            if (lenByteCnt != utf8lenByteCnt) {
                System.arraycopy(bytes, this.off + lenByteCnt + 1, bytes, this.off + utf8lenByteCnt + 1, utf8len);
            }
            bytes[this.off++] = BC_STR_UTF8;
            if (utf8len >= BC_INT32_NUM_MIN && utf8len <= BC_INT32_NUM_MAX) {
                bytes[this.off++] = (byte) utf8len;
            } else if (utf8len >= INT32_BYTE_MIN && utf8len <= INT32_BYTE_MAX) {
                bytes[this.off] = (byte) (BC_INT32_BYTE_ZERO + (utf8len >> 8));
                bytes[this.off + 1] = (byte) (utf8len);
                this.off += 2;
            } else {
                writeInt32(utf8len);
            }
            this.off += utf8len;
        }
    }

    public void writeString(String[] strings) {
        if (strings == null) {
            writeArrayNull();
            return;
        }

        startArray(strings.length);
        for (int i = 0; i < strings.length; i++) {
            String item = strings[i];
            if (item == null) {
                writeStringNull();
                continue;
            }
            writeString(item);
        }
    }

    @Override
    public void writeSymbol(String str) {
        if (str == null) {
            writeNull();
            return;
        }

        if (symbolTable != null) {
            int ordinal = symbolTable.getOrdinal(str);
            if (ordinal >= 0) {
                writeRaw(BC_SYMBOL);
                writeInt32(-ordinal);
                return;
            }
        }

        writeString(str);
    }

    @Override
    public void writeTypeName(String typeName) {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        this.bytes[off++] = BC_TYPED_ANY;

        long hash = Fnv.hashCode64(typeName);

        int symbol = -1;
        if (symbolTable != null) {
            symbol = symbolTable.getOrdinalByHashCode(hash);
            if (symbol == -1 && symbols != null) {
                symbol = symbols.get(hash);
            }
        } else if (symbols != null) {
            symbol = symbols.get(hash);
        }

        if (symbol == -1) {
            if (symbols == null) {
                symbols = new TLongIntHashMap();
            }
            symbols.put(hash, symbol = symbolIndex++);
        } else {
            if (off == bytes.length) {
                ensureCapacity(off + 1);
            }

            writeInt32(symbol);
            return;
        }

        writeString(typeName);
        writeInt32(symbol);
    }

    @Override
    public boolean writeTypeName(byte[] typeName, long hash) {
        if (symbolTable != null) {
            int symbol = symbolTable.getOrdinalByHashCode(hash);
            if (symbol != -1) {
                return writeTypeNameSymbol(symbol);
            }
        }

        boolean symbolExists = false;
        int symbol;
        if (rootTypeNameHash == hash) {
            symbolExists = true;
            symbol = 0;
        } else if (symbols != null) {
            symbol = symbols.putIfAbsent(hash, symbolIndex);
            if (symbol != symbolIndex) {
                symbolExists = true;
            } else {
                symbolIndex++;
            }
        } else {
            symbol = symbolIndex++;
            if (symbol == 0) {
                rootTypeNameHash = hash;
            }
            if (symbol != 0 || (context.features & WriteNameAsSymbol.mask) != 0) {
                symbols = new TLongIntHashMap(hash, symbol);
            }
        }

        if (symbolExists) {
            writeTypeNameSymbol(-symbol);
            return false;
        }

        int off = this.off;
        int minCapacity = off + 2 + typeName.length;
        if (minCapacity > bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        bytes[off++] = BC_TYPED_ANY;
        System.arraycopy(typeName, 0, bytes, off, typeName.length);
        off += typeName.length;
        if (symbol >= BC_INT32_NUM_MIN && symbol <= BC_INT32_NUM_MAX) {
            bytes[off] = (byte) symbol;
            this.off = off + 1;
        } else {
            this.off = off;
            writeInt32(symbol);
        }

        return false;
    }

    private boolean writeTypeNameSymbol(int symbol) {
        int off = this.off;
        if (off + 2 >= bytes.length) {
            ensureCapacity(off + 2);
        }

        this.bytes[off] = BC_TYPED_ANY;
        this.off = off + 1;
        writeInt32(-symbol);
        return false;
    }

    static int sizeOfInt(int i) {
        if (i >= BC_INT32_NUM_MIN && i <= BC_INT32_NUM_MAX) {
            return 1;
        }

        if (i >= INT32_BYTE_MIN && i <= INT32_BYTE_MAX) {
            return 2;
        }

        if (i >= INT32_SHORT_MIN && i <= INT32_SHORT_MAX) {
            return 3;
        }

        return 5;
    }

    public void writeString(List list) {
        if (list == null) {
            writeArrayNull();
            return;
        }

        final int size = list.size();
        startArray(size);

        if (STRING_VALUE != null && STRING_CODER != null) {
            int mark = off;
            final int LATIN = 0;
            boolean latinAll = true;
            for (int i = 0; i < list.size(); i++) {
                String str = list.get(i);
                if (str == null) {
                    writeNull();
                    continue;
                }
                int coder = STRING_CODER.applyAsInt(str);
                if (coder != LATIN) {
                    latinAll = false;
                    off = mark;
                    break;
                }
                int strlen = str.length();
                if (off + strlen + 6 > bytes.length) {
                    ensureCapacity(off + strlen + 6);
                }
                if (strlen <= STR_ASCII_FIX_LEN) {
                    bytes[off++] = (byte) (strlen + BC_STR_ASCII_FIX_MIN);
                } else if (strlen <= INT32_BYTE_MAX) {
                    int off = this.off;
                    bytes[off] = BC_STR_ASCII;
                    bytes[off + 1] = (byte) (BC_INT32_BYTE_ZERO + (strlen >> 8));
                    bytes[off + 2] = (byte) (strlen);
                    this.off += 3;
                } else {
                    bytes[off++] = BC_STR_ASCII;
                    writeInt32(strlen);
                }
                byte[] value = STRING_VALUE.apply(str);
                System.arraycopy(value, 0, bytes, off, value.length);
                off += strlen;
            }
            if (latinAll) {
                return;
            }
        }

        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            writeString(str);
        }
    }

    @Override
    public void writeString(String str) {
        if (str == null) {
            writeNull();
            return;
        }

        if (STRING_VALUE != null) {
            int coder = STRING_CODER.applyAsInt(str);
            byte[] value = STRING_VALUE.apply(str);

            if (coder == 0) {
                int off = this.off;
                int strlen = value.length;
                int minCapacity = value.length + off + 6;

                if (minCapacity - bytes.length > 0) {
                    ensureCapacity(minCapacity);
                }

                final byte[] bytes = this.bytes;
                if (strlen <= STR_ASCII_FIX_LEN) {
                    bytes[off++] = (byte) (strlen + BC_STR_ASCII_FIX_MIN);
                } else if (strlen <= INT32_BYTE_MAX) {
                    putStringSizeSmall(bytes, off, strlen);
                    off += 3;
                } else {
                    off += putStringSizeLarge(bytes, off, strlen);
                }
                System.arraycopy(value, 0, bytes, off, value.length);
                this.off = off + strlen;
                return;
            } else {
                if (tryWriteStringUTF16(value)) {
                    return;
                }
            }
        }

        writeString(
                JDKUtils.getCharArray(str)
        );
    }

    public void writeStringUTF16(byte[] value) {
        int off = this.off;
        final int strlen = value.length;
        int minCapacity = off + strlen + 6;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        bytes[off++] = JDKUtils.BIG_ENDIAN ? BC_STR_UTF16BE : BC_STR_UTF16LE;
        off += writeInt32(bytes, off, strlen);
        System.arraycopy(value, 0, bytes, off, strlen);
        this.off = off + strlen;
    }

    private boolean tryWriteStringUTF16(byte[] value) {
        int check_cnt = 128;
        if (check_cnt > value.length) {
            check_cnt = value.length;
        }
        if ((check_cnt & 1) == 1) {
            check_cnt -= 1;
        }

        int asciiCount = 0;
        for (int i = 0; i + 2 <= check_cnt; i += 2) {
            byte b0 = value[i];
            byte b1 = value[i + 1];
            if (b0 == 0 || b1 == 0) {
                asciiCount++;
            }
        }

        boolean utf16 = value.length != 0 && (asciiCount == 0 || (check_cnt >> 1) / asciiCount >= 3); // utf16字符占比>=1/3
        int off = this.off;
        int minCapacity = off + 6 + value.length * 2 + 1;
        if (minCapacity >= this.bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (!utf16) {
            int maxSize = value.length + (value.length >> 2);
            int lenByteCnt = sizeOfInt(maxSize);
            int result = IOUtils.encodeUTF8(value, 0, value.length, bytes, off + lenByteCnt + 1);
            int utf8len = result - off - lenByteCnt - 1;
            if (utf8len > value.length) {
                utf16 = true;
            } else if (result != -1) {
                this.off = off + writeUTF8(bytes, off, value, utf8len, asciiCount, lenByteCnt);
                return true;
            }
        }

        if (utf16) {
            this.off = off + writeUTF16(bytes, off, value);
            return true;
        }
        return false;
    }

    private static int writeUTF8(
            byte[] bytes,
            int off,
            byte[] value,
            int utf8len,
            int asciiCount,
            int lenByteCnt
    ) {
        final byte strtype;
        if (utf8len * 2 == value.length) {
            if (asciiCount <= STR_ASCII_FIX_LEN) {
                bytes[off] = (byte) (BC_STR_ASCII_FIX_MIN + utf8len);
                System.arraycopy(bytes, off + 1 + lenByteCnt, bytes, off + 1, utf8len);
                return utf8len + 1;
            }
            strtype = BC_STR_ASCII;
        } else {
            strtype = BC_STR_UTF8;
        }
        int utf8lenByteCnt = sizeOfInt(utf8len);
        if (lenByteCnt != utf8lenByteCnt) {
            System.arraycopy(bytes, off + lenByteCnt + 1, bytes, off + utf8lenByteCnt + 1, utf8len);
        }
        bytes[off] = strtype;
        return writeInt32(bytes, off + 1, utf8len) + utf8len + 1;
    }

    private static int writeUTF16(byte[] bytes, int off, byte[] value) {
        bytes[off] = JDKUtils.BIG_ENDIAN ? BC_STR_UTF16BE : BC_STR_UTF16LE;
        int size = writeInt32(bytes, off + 1, value.length);
        System.arraycopy(value, 0, bytes, off + size + 1, value.length);
        return value.length + size + 1;
    }

    void ensureCapacity(int minCapacity) {
        if (minCapacity >= bytes.length) {
            int oldCapacity = bytes.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0) {
                newCapacity = minCapacity;
            }
            if (newCapacity > maxArraySize) {
                throw new OutOfMemoryError();
            }

            // minCapacity is usually close to size, so this is a win:
            bytes = Arrays.copyOf(bytes, newCapacity);
        }
    }

    @Override
    public void writeMillis(long millis) {
        int off = this.off;
        int minCapacity = off + 9;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (millis % 1000 == 0) {
            long seconds = (millis / 1000);
            if (seconds >= Integer.MIN_VALUE && seconds <= Integer.MAX_VALUE) {
                int secondsInt = (int) seconds;

                bytes[off] = BC_TIMESTAMP_SECONDS;
                UNSAFE.putInt(
                        bytes,
                        ARRAY_BYTE_BASE_OFFSET + off + 1,
                        BIG_ENDIAN ? secondsInt : Integer.reverseBytes(secondsInt)
                );
                this.off = off + 5;
                return;
            }

            if (seconds % 60 == 0) {
                long minutes = seconds / 60;
                if (minutes >= Integer.MIN_VALUE && minutes <= Integer.MAX_VALUE) {
                    int minutesInt = (int) minutes;
                    bytes[off] = BC_TIMESTAMP_MINUTES;
                    UNSAFE.putInt(
                            bytes,
                            ARRAY_BYTE_BASE_OFFSET + off + 1,
                            BIG_ENDIAN ? minutesInt : Integer.reverseBytes(minutesInt)
                    );
                    this.off = off + 5;
                    return;
                }
            }
        }

        bytes[off] = BC_TIMESTAMP_MILLIS;
        UNSAFE.putLong(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 1,
                BIG_ENDIAN ? millis : Long.reverseBytes(millis)
        );
        this.off = off + 9;
    }

    static final long WRITE_NUM_NULL_MASK = Feature.NullAsDefaultValue.mask | Feature.WriteNullNumberAsZero.mask;

    @Override
    public void writeInt64(Long i) {
        int minCapacity = off + 9;
        if (minCapacity > bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int off = this.off;
        int size;
        if (i == null) {
            bytes[off] = (this.context.features & WRITE_NUM_NULL_MASK) == 0
                    ? BC_NULL
                    : (byte) (BC_INT64_NUM_MIN - INT64_NUM_LOW_VALUE);
            size = 1;
        } else {
            long val = i;
            if (val >= INT64_NUM_LOW_VALUE && val <= INT64_NUM_HIGH_VALUE) {
                bytes[off] = (byte) (BC_INT64_NUM_MIN + (val - INT64_NUM_LOW_VALUE));
                size = 1;
            } else if (val >= INT64_BYTE_MIN && val <= INT64_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT64_BYTE_ZERO + (val >> 8));
                bytes[off + 1] = (byte) (val);
                size = 2;
            } else if (val >= INT64_SHORT_MIN && val <= INT64_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT64_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                size = 3;
            } else if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
                bytes[off] = BC_INT64_INT;
                UNSAFE.putInt(
                        bytes,
                        ARRAY_BYTE_BASE_OFFSET + off + 1,
                        BIG_ENDIAN ? (int) val : Integer.reverseBytes((int) val)
                );
                size = 5;
            } else {
                size = writeInt64Large8(bytes, off, val);
            }
        }

        this.off = off + size;
    }

    @Override
    public void writeInt64(long val) {
        int minCapacity = off + 9;
        if (minCapacity > bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int off = this.off;
        int size;
        if (val >= INT64_NUM_LOW_VALUE && val <= INT64_NUM_HIGH_VALUE) {
            bytes[off] = (byte) (BC_INT64_NUM_MIN + (val - INT64_NUM_LOW_VALUE));
            size = 1;
        } else if (val >= INT64_BYTE_MIN && val <= INT64_BYTE_MAX) {
            bytes[off] = (byte) (BC_INT64_BYTE_ZERO + (val >> 8));
            bytes[off + 1] = (byte) (val);
            size = 2;
        } else if (val >= INT64_SHORT_MIN && val <= INT64_SHORT_MAX) {
            bytes[off] = (byte) (BC_INT64_SHORT_ZERO + (val >> 16));
            bytes[off + 1] = (byte) (val >> 8);
            bytes[off + 2] = (byte) (val);
            size = 3;
        } else if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
            bytes[off] = BC_INT64_INT;
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? (int) val : Integer.reverseBytes((int) val)
            );
            size = 5;
        } else {
            bytes[off] = BC_INT64;
            UNSAFE.putLong(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? val : Long.reverseBytes(val)
            );
            size = 9;
        }
        this.off = off + size;
    }

    @Override
    public void writeInt64(long[] value) {
        if (value == null) {
            writeArrayNull();
            return;
        }

        int size = value.length;

        int off = this.off;
        int minCapacity = off + size * 9 + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (size <= ARRAY_FIX_LEN) {
            bytes[off++] = (byte) (BC_ARRAY_FIX_MIN + size);
        } else {
            bytes[off] = BC_ARRAY;
            off += writeInt32(bytes, off + 1, size) + 1;
        }

        for (int i = 0; i < value.length; i++) {
            long val = value[i];
            if (val >= INT64_NUM_LOW_VALUE && val <= INT64_NUM_HIGH_VALUE) {
                bytes[off++] = (byte) (BC_INT64_NUM_MIN + (val - INT64_NUM_LOW_VALUE));
                continue;
            }

            if (val >= INT64_BYTE_MIN && val <= INT64_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT64_BYTE_ZERO + (val >> 8));
                bytes[off + 1] = (byte) (val);
                off += 2;
                continue;
            }

            if (val >= INT64_SHORT_MIN && val <= INT64_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT64_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                off += 3;
                continue;
            }

            if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
                bytes[off] = BC_INT64_INT;
                UNSAFE.putInt(
                        bytes,
                        ARRAY_BYTE_BASE_OFFSET + off + 1,
                        BIG_ENDIAN ? (int) val : Integer.reverseBytes((int) val)
                );
                off += 5;
                continue;
            }

            bytes[off] = BC_INT64;
            UNSAFE.putLong(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? val : Long.reverseBytes(val)
            );
            off += 9;
        }
        this.off = off;
    }

    @Override
    public void writeListInt64(List values) {
        if (values == null) {
            writeArrayNull();
            return;
        }

        int size = values.size();

        int off = this.off;
        int minCapacity = off + size * 9 + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (size <= ARRAY_FIX_LEN) {
            bytes[off++] = (byte) (BC_ARRAY_FIX_MIN + size);
        } else {
            bytes[off] = BC_ARRAY;
            off += writeInt32(bytes, off + 1, size) + 1;
        }

        for (int i = 0; i < size; i++) {
            Long item = values.get(i);
            if (item == null) {
                bytes[off++] = BC_NULL;
                continue;
            }

            long val = item;
            if (val >= INT64_NUM_LOW_VALUE && val <= INT64_NUM_HIGH_VALUE) {
                bytes[off++] = (byte) (BC_INT64_NUM_MIN + (val - INT64_NUM_LOW_VALUE));
                continue;
            }

            if (val >= INT64_BYTE_MIN && val <= INT64_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT64_BYTE_ZERO + (val >> 8));
                bytes[off + 1] = (byte) (val);
                off += 2;
                continue;
            }

            if (val >= INT64_SHORT_MIN && val <= INT64_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT64_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                off += 3;
                continue;
            }

            off += writeInt64Large(bytes, off, val);
        }
        this.off = off;
    }

    private static int writeInt64Large(byte[] bytes, int off, long val) {
        if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
            bytes[off] = BC_INT64_INT;
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? (int) val : Integer.reverseBytes((int) val)
            );
            return 5;
        }

        return writeInt64Large8(bytes, off, val);
    }

    private static int writeInt64Large8(byte[] bytes, int off, long val) {
        bytes[off] = BC_INT64;
        UNSAFE.putLong(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 1,
                BIG_ENDIAN ? val : Long.reverseBytes(val)
        );
        return 9;
    }

    @Override
    public void writeFloat(float value) {
        int off = this.off;
        int minCapacity = off + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int i = (int) value;
        if (i == value && value >= INT32_SHORT_MIN && value <= INT32_SHORT_MAX) {
            bytes[off] = BC_FLOAT_INT;
            off += writeInt32(bytes, off + 1, i) + 1;
        } else {
            bytes[off] = BC_FLOAT;
            i = Float.floatToIntBits(value);
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? i : Integer.reverseBytes(i)
            );
            off += 5;
        }
        this.off = off;
    }

    @Override
    public void writeFloat(float[] values) {
        if (values == null) {
            writeNull();
            return;
        }
        startArray(values.length);
        for (int i = 0; i < values.length; i++) {
            writeFloat(values[i]);
        }
        endArray();
    }

    @Override
    public void writeDouble(double value) {
        if (value == 0) {
            ensureCapacity(off + 1);
            bytes[off++] = BC_DOUBLE_NUM_0;
            return;
        }

        int off = this.off;
        if (value == 1) {
            ensureCapacity(off + 1);
            bytes[off] = BC_DOUBLE_NUM_1;
            this.off = off + 1;
            return;
        }

        if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
            long longValue = (long) value;
            if (longValue == value) {
                ensureCapacity(off + 1);
                bytes[off] = BC_DOUBLE_LONG;
                this.off = off + 1;
                writeInt64(longValue);
                return;
            }
        }

        ensureCapacity(off + 9);
        final byte[] bytes = this.bytes;
        bytes[off] = BC_DOUBLE;
        long i = Double.doubleToLongBits(value);
        UNSAFE.putLong(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 1,
                BIG_ENDIAN ? i : Long.reverseBytes(i)
        );
        this.off = off + 9;
    }

    @Override
    public void writeDouble(double[] values) {
        if (values == null) {
            writeNull();
            return;
        }
        startArray(values.length);
        for (int i = 0; i < values.length; i++) {
            writeDouble(values[i]);
        }
        endArray();
    }

    @Override
    public void writeInt16(short[] values) {
        if (values == null) {
            writeNull();
            return;
        }
        startArray(values.length);
        for (int i = 0; i < values.length; i++) {
            writeInt32(values[i]);
        }
        endArray();
    }

    @Override
    public void writeInt32(int[] values) {
        if (values == null) {
            writeArrayNull();
            return;
        }

        // inline startArray(value.length);
        int size = values.length;
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }

        if (size <= ARRAY_FIX_LEN) {
            bytes[off++] = (byte) (BC_ARRAY_FIX_MIN + size);
        } else {
            bytes[off++] = BC_ARRAY;
            writeInt32(size);
        }

        int off = this.off;
        int minCapacity = off + values.length * 5;
        if (minCapacity - bytes.length > 0) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        for (int i = 0; i < values.length; i++) {
            int val = values[i];
            if (val >= BC_INT32_NUM_MIN && val <= BC_INT32_NUM_MAX) {
                bytes[off++] = (byte) val;
                continue;
            }

            if (val >= INT32_BYTE_MIN && val <= INT32_BYTE_MAX) {
                bytes[off++] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
                bytes[off++] = (byte) (val);
                continue;
            }

            if (val >= INT32_SHORT_MIN && val <= INT32_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT32_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                off += 3;
                continue;
            }

            bytes[off] = BC_INT32;
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? val : Integer.reverseBytes(val)
            );
            off += 5;
        }
        this.off = off;
    }

    @Override
    public void writeInt8(byte val) {
        int off = this.off;
        int minCapacity = off + 2;
        if (minCapacity - bytes.length > 0) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        bytes[off] = BC_INT8;
        bytes[off + 1] = val;
        this.off = off + 2;
    }

    @Override
    public void writeInt16(short val) {
        int off = this.off;
        int minCapacity = off + 3;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }
        final byte[] bytes = this.bytes;
        bytes[off] = BC_INT16;
        bytes[off + 1] = (byte) (val >>> 8);
        bytes[off + 2] = (byte) val;
        this.off = off + 3;
    }

    @Override
    public void writeEnum(Enum e) {
        if (e == null) {
            writeNull();
            return;
        }

        if ((context.features & WRITE_ENUM_USING_STRING_MASK) != 0) {
            writeString(
                    (context.features & WriteEnumUsingToString.mask) != 0
                            ? e.toString()
                            : e.name()
            );
        } else {
            int val = e.ordinal();
            if (val <= BC_INT32_NUM_MAX) {
                if (off == bytes.length) {
                    ensureCapacity(off + 1);
                }

                bytes[off++] = (byte) val;
                return;
            }
            writeInt32(val);
        }
    }

    @Override
    public void writeInt32(Integer i) {
        int minCapacity = off + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int size;
        int off = this.off;
        if (i == null) {
            if ((this.context.features & (Feature.NullAsDefaultValue.mask | Feature.WriteNullNumberAsZero.mask)) == 0) {
                bytes[off] = BC_NULL;
            } else {
                bytes[off] = 0;
            }
            size = 1;
        } else {
            int val = i;
            if (val >= BC_INT32_NUM_MIN && val <= BC_INT32_NUM_MAX) {
                bytes[off] = (byte) val;
                size = 1;
            } else if (val >= INT32_BYTE_MIN && val <= INT32_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
                bytes[off + 1] = (byte) (val);
                size = 2;
            } else if (val >= INT32_SHORT_MIN && val <= INT32_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT32_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                size = 3;
            } else {
                bytes[off] = BC_INT32;
                UNSAFE.putInt(
                        bytes,
                        ARRAY_BYTE_BASE_OFFSET + off + 1,
                        BIG_ENDIAN ? val : Integer.reverseBytes(val)
                );
                size = 5;
            }
        }
        this.off += size;
    }

    @Override
    public void writeInt32(int val) {
        int minCapacity = off + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int size;
        int off = this.off;
        if (val >= BC_INT32_NUM_MIN && val <= BC_INT32_NUM_MAX) {
            bytes[off] = (byte) val;
            size = 1;
        } else if (val >= INT32_BYTE_MIN && val <= INT32_BYTE_MAX) {
            bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
            bytes[off + 1] = (byte) (val);
            size = 2;
        } else if (val >= INT32_SHORT_MIN && val <= INT32_SHORT_MAX) {
            bytes[off] = (byte) (BC_INT32_SHORT_ZERO + (val >> 16));
            bytes[off + 1] = (byte) (val >> 8);
            bytes[off + 2] = (byte) (val);
            size = 3;
        } else {
            bytes[off] = BC_INT32;
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? val : Integer.reverseBytes(val)
            );
            size = 5;
        }
        this.off += size;
    }

    @Override
    public void writeListInt32(List values) {
        if (values == null) {
            writeArrayNull();
            return;
        }

        int size = values.size();

        int off = this.off;
        int minCapacity = off + size * 5 + 5;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        if (size <= ARRAY_FIX_LEN) {
            bytes[off++] = (byte) (BC_ARRAY_FIX_MIN + size);
        } else {
            bytes[off] = BC_ARRAY;
            off += writeInt32(bytes, off + 1, size) + 1;
        }

        for (int i = 0; i < size; i++) {
            Number item = values.get(i);
            if (item == null) {
                bytes[off++] = BC_NULL;
                continue;
            }

            int val = item.intValue();
            if (val >= BC_INT32_NUM_MIN && val <= BC_INT32_NUM_MAX) {
                bytes[off++] = (byte) val;
            } else if (val >= INT32_BYTE_MIN && val <= INT32_BYTE_MAX) {
                bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
                bytes[off + 1] = (byte) (val);
                off += 2;
            } else if (val >= INT32_SHORT_MIN && val <= INT32_SHORT_MAX) {
                bytes[off] = (byte) (BC_INT32_SHORT_ZERO + (val >> 16));
                bytes[off + 1] = (byte) (val >> 8);
                bytes[off + 2] = (byte) (val);
                off += 3;
            } else {
                bytes[off] = BC_INT32;
                UNSAFE.putInt(
                        bytes,
                        ARRAY_BYTE_BASE_OFFSET + off + 1,
                        BIG_ENDIAN ? val : Integer.reverseBytes(val)
                );
                off += 5;
            }
        }
        this.off = off;
    }

    public static int writeInt32(byte[] bytes, int off, int val) {
        if (val >= BC_INT32_NUM_MIN && val <= BC_INT32_NUM_MAX) {
            bytes[off] = (byte) val;
            return 1;
        } else if (val >= INT32_BYTE_MIN && val <= INT32_BYTE_MAX) {
            bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (val >> 8));
            bytes[off + 1] = (byte) (val);
            return 2;
        } else if (val >= INT32_SHORT_MIN && val <= INT32_SHORT_MAX) {
            bytes[off] = (byte) (BC_INT32_SHORT_ZERO + (val >> 16));
            bytes[off + 1] = (byte) (val >> 8);
            bytes[off + 2] = (byte) (val);
            return 3;
        } else {
            bytes[off] = BC_INT32;
            UNSAFE.putInt(
                    bytes,
                    ARRAY_BYTE_BASE_OFFSET + off + 1,
                    BIG_ENDIAN ? val : Integer.reverseBytes(val)
            );
            return 5;
        }
    }

    @Override
    public void writeArrayNull() {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }

        bytes[off++] = (this.context.features & WRITE_ARRAY_NULL_MASK) != 0 ? BC_ARRAY_FIX_MIN : BC_NULL;
    }

    @Override
    public void writeRaw(String str) {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void writeRaw(byte[] bytes) {
        int minCapacity = this.off + bytes.length;
        if (minCapacity - this.bytes.length > 0) {
            ensureCapacity(minCapacity);
        }
        System.arraycopy(bytes, 0, this.bytes, off, bytes.length);
        off += bytes.length;
    }

    public void writeSymbol(int symbol) {
        int minCapacity = off + 3;
        if (minCapacity >= bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        bytes[off++] = BC_SYMBOL;

        if (symbol >= BC_INT32_NUM_MIN && symbol <= BC_INT32_NUM_MAX) {
            bytes[off++] = (byte) symbol;
            return;
        }

        if (symbol >= INT32_BYTE_MIN && symbol <= INT32_BYTE_MAX) {
            bytes[off] = (byte) (BC_INT32_BYTE_ZERO + (symbol >> 8));
            bytes[off + 1] = (byte) (symbol);
            off += 2;
            return;
        }

        writeInt32(symbol);
    }

    @Override
    public void writeNameRaw(byte[] name, long nameHash) {
        int off = this.off;
        int minCapacity = off + name.length + 2;
        if (minCapacity >= this.bytes.length) {
            ensureCapacity(minCapacity);
        }

        final byte[] bytes = this.bytes;
        int symbol;
        if (symbolTable == null
                || (symbol = symbolTable.getOrdinalByHashCode(nameHash)) == -1
        ) {
            if ((context.features & WriteNameAsSymbol.mask) == 0) {
                System.arraycopy(name, 0, bytes, off, name.length);
                this.off = off + name.length;
                return;
            }

            boolean symbolExists = false;
            if (symbols != null) {
                if ((symbol = symbols.putIfAbsent(nameHash, symbolIndex)) != symbolIndex) {
                    symbolExists = true;
                } else {
                    symbolIndex++;
                }
            } else {
                (symbols = new TLongIntHashMap())
                        .put(nameHash, symbol = symbolIndex++);
            }

            if (!symbolExists) {
                bytes[off++] = BC_SYMBOL;
                System.arraycopy(name, 0, bytes, off, name.length);
                this.off = off + name.length;

                if (symbol >= BC_INT32_NUM_MIN && symbol <= BC_INT32_NUM_MAX) {
                    bytes[this.off++] = (byte) symbol;
                } else {
                    writeInt32(symbol);
                }
                return;
            }
            symbol = -symbol;
        }

        bytes[off++] = BC_SYMBOL;
        int intValue = -symbol;
        if (intValue >= BC_INT32_NUM_MIN && intValue <= BC_INT32_NUM_MAX) {
            bytes[off] = (byte) intValue;
            this.off = off + 1;
        } else {
            this.off = off;
            writeInt32(intValue);
        }
    }

    @Override
    public void writeLocalDate(LocalDate date) {
        if (date == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 5);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_DATE;
        int year = date.getYear();
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) date.getMonthValue();
        bytes[off + 4] = (byte) date.getDayOfMonth();
        this.off = off + 5;
    }

    @Override
    public void writeLocalTime(LocalTime time) {
        if (time == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 4);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_TIME;
        bytes[off + 1] = (byte) time.getHour();
        bytes[off + 2] = (byte) time.getMinute();
        bytes[off + 3] = (byte) time.getSecond();
        this.off = off + 4;

        int nano = time.getNano();
        writeInt32(nano);
    }

    @Override
    public void writeLocalDateTime(LocalDateTime dateTime) {
        if (dateTime == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 8);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_DATETIME;
        int year = dateTime.getYear();
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) dateTime.getMonthValue();
        bytes[off + 4] = (byte) dateTime.getDayOfMonth();
        bytes[off + 5] = (byte) dateTime.getHour();
        bytes[off + 6] = (byte) dateTime.getMinute();
        bytes[off + 7] = (byte) dateTime.getSecond();
        this.off = off + 8;

        int nano = dateTime.getNano();
        writeInt32(nano);
    }

    @Override
    public void writeZonedDateTime(ZonedDateTime dateTime) {
        if (dateTime == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 8);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_TIMESTAMP_WITH_TIMEZONE;
        int year = dateTime.getYear();
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) dateTime.getMonthValue();
        bytes[off + 4] = (byte) dateTime.getDayOfMonth();
        bytes[off + 5] = (byte) dateTime.getHour();
        bytes[off + 6] = (byte) dateTime.getMinute();
        bytes[off + 7] = (byte) dateTime.getSecond();
        this.off = off + 8;

        int nano = dateTime.getNano();
        writeInt32(nano);

        ZoneId zoneId = dateTime.getZone();
        String zoneIdStr = zoneId.getId();
        if (zoneIdStr.equals(SHANGHAI_ZONE_ID_NAME)) {
            writeRaw(SHANGHAI_ZONE_ID_NAME_BYTES);
        } else {
            writeString(zoneIdStr);
        }
    }

    @Override
    public void writeOffsetDateTime(OffsetDateTime dateTime) {
        if (dateTime == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 8);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_TIMESTAMP_WITH_TIMEZONE;
        int year = dateTime.getYear();
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) dateTime.getMonthValue();
        bytes[off + 4] = (byte) dateTime.getDayOfMonth();
        bytes[off + 5] = (byte) dateTime.getHour();
        bytes[off + 6] = (byte) dateTime.getMinute();
        bytes[off + 7] = (byte) dateTime.getSecond();
        this.off = off + 8;

        int nano = dateTime.getNano();
        writeInt32(nano);

        ZoneId zoneId = dateTime.getOffset();
        String zoneIdStr = zoneId.getId();
        if (zoneIdStr.equals(OFFSET_8_ZONE_ID_NAME)) {
            writeRaw(OFFSET_8_ZONE_ID_NAME_BYTES);
        } else {
            writeString(zoneIdStr);
        }
    }

    @Override
    public void writeOffsetTime(OffsetTime offsetTime) {
        if (offsetTime == null) {
            writeNull();
            return;
        }

        writeOffsetDateTime(
                OffsetDateTime.of(DateUtils.LOCAL_DATE_19700101, offsetTime.toLocalTime(), offsetTime.getOffset())
        );
    }

    @Override
    public void writeInstant(Instant instant) {
        if (instant == null) {
            writeNull();
            return;
        }

        ensureCapacity(off + 1);
        bytes[off++] = BC_TIMESTAMP;
        long second = instant.getEpochSecond();
        int nano = instant.getNano();
        writeInt64(second);
        writeInt32(nano);
    }

    @Override
    public void writeUUID(UUID value) {
        if (value == null) {
            writeNull();
            return;
        }

        int off = this.off;
        ensureCapacity(off + 18);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_BINARY;
        bytes[off + 1] = BC_INT32_NUM_16;
        long msb = value.getMostSignificantBits();
        UNSAFE.putLong(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 2,
                BIG_ENDIAN ? msb : Long.reverseBytes(msb)
        );
        long lsb = value.getLeastSignificantBits();
        UNSAFE.putLong(
                bytes,
                ARRAY_BYTE_BASE_OFFSET + off + 10,
                BIG_ENDIAN ? lsb : Long.reverseBytes(lsb)
        );
        this.off = off + 18;
    }

    @Override
    public void writeBigInt(BigInteger value, long features) {
        if (value == null) {
            writeNull();
            return;
        }

        if (isInt64(value)) {
            if (off == bytes.length) {
                ensureCapacity(off + 1);
            }
            bytes[off++] = BC_BIGINT_LONG;
            long int64Value = value.longValue();
            writeInt64(int64Value);
            return;
        }

        byte[] valueBytes = value.toByteArray();
        ensureCapacity(off + 5 + valueBytes.length);

        bytes[off++] = BC_BIGINT;
        writeInt32(valueBytes.length);
        System.arraycopy(valueBytes, 0, bytes, off, valueBytes.length);
        off += valueBytes.length;
    }

    @Override
    public void writeBinary(byte[] bytes) {
        if (bytes == null) {
            writeNull();
            return;
        }

        ensureCapacity(off + 6 + bytes.length);
        this.bytes[off++] = BC_BINARY;
        writeInt32(bytes.length);

        System.arraycopy(bytes, 0, this.bytes, off, bytes.length);
        off += bytes.length;
    }

    @Override
    public void writeDecimal(BigDecimal value, long features, DecimalFormat format) {
        if (value == null) {
            writeNull();
            return;
        }

        int precision = value.precision();
        int scale = value.scale();

        if (precision < 19 && FIELD_DECIMAL_INT_COMPACT_OFFSET != -1) {
            long intCompact = UNSAFE.getLong(value, FIELD_DECIMAL_INT_COMPACT_OFFSET);
            if (scale == 0) {
                ensureCapacity(off + 1);
                this.bytes[off++] = BC_DECIMAL_LONG;
                writeInt64(intCompact);
                return;
            }

            ensureCapacity(off + 1);
            this.bytes[off++] = BC_DECIMAL;
            writeInt32(scale);
            if (intCompact >= Integer.MIN_VALUE && intCompact <= Integer.MAX_VALUE) {
                writeInt32((int) intCompact);
            } else {
                writeInt64(intCompact);
            }
            return;
        }

        BigInteger unscaledValue = value.unscaledValue();
        if (scale == 0
                && isInt64(unscaledValue)) {
            ensureCapacity(off + 1);
            this.bytes[off++] = BC_DECIMAL_LONG;
            long longValue = unscaledValue.longValue();
            writeInt64(longValue);
            return;
        }

        ensureCapacity(off + 1);
        this.bytes[off++] = BC_DECIMAL;
        writeInt32(scale);

        if (isInt32(unscaledValue)) {
            int intValue = unscaledValue.intValue();
            writeInt32(intValue);
        } else if (isInt64(unscaledValue)) {
            long longValue = unscaledValue.longValue();
            writeInt64(longValue);
        } else {
            writeBigInt(unscaledValue, 0);
        }
    }

    @Override
    public void writeBool(boolean value) {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        this.bytes[off++] = value ? BC_TRUE : BC_FALSE;
    }

    @Override
    public void writeBool(boolean[] valeus) {
        if (valeus == null) {
            writeNull();
            return;
        }

        startArray(valeus.length);
        for (int i = 0; i < valeus.length; i++) {
            writeBool(valeus[i]);
        }
        endArray();
    }

    @Override
    public void writeReference(String path) {
        if (off == bytes.length) {
            ensureCapacity(off + 1);
        }
        bytes[off++] = BC_REFERENCE;

        if (path == this.lastReference) {
            writeString("#-1");
        } else {
            writeString(path);
        }

        this.lastReference = path;
    }

    @Override
    public void writeDateTime14(
            int year,
            int month,
            int dayOfMonth,
            int hour,
            int minute,
            int second
    ) {
        int off = this.off;
        ensureCapacity(off + 8);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_DATETIME;
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) month;
        bytes[off + 4] = (byte) dayOfMonth;
        bytes[off + 5] = (byte) hour;
        bytes[off + 6] = (byte) minute;
        bytes[off + 7] = (byte) second;
        this.off = off + 8;

        int nano = 0;
        writeInt32(nano);
    }

    @Override
    public void writeDateTime19(
            int year,
            int month,
            int dayOfMonth,
            int hour,
            int minute,
            int second
    ) {
        int off = this.off;
        ensureCapacity(off + 8);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_DATETIME;
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) month;
        bytes[off + 4] = (byte) dayOfMonth;
        bytes[off + 5] = (byte) hour;
        bytes[off + 6] = (byte) minute;
        bytes[off + 7] = (byte) second;
        this.off = off + 8;

        int nano = 0;
        writeInt32(nano);
    }

    @Override
    public void writeDateTimeISO8601(
            int year,
            int month,
            int dayOfMonth,
            int hour,
            int minute,
            int second,
            int millis,
            int offsetSeconds,
            boolean timeZone
    ) {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void writeDateYYYMMDD8(int year, int month, int dayOfMonth) {
        int off = this.off;
        ensureCapacity(off + 5);

        final byte[] bytes = this.bytes;
        bytes[off] = BC_LOCAL_DATE;
        bytes[off + 1] = (byte) (year >>> 8);
        bytes[off + 2] = (byte) year;
        bytes[off + 3] = (byte) month;
        bytes[off + 4] = (byte) dayOfMonth;
        this.off = off + 5;
    }

    @Override
    public void writeDateYYYMMDD10(int year, int month, int dayOfMonth) {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void writeTimeHHMMSS8(int hour, int minute, int second) {
        throw new JSONException("unsupported operation");
    }

    @Override
    public void writeBase64(byte[] bytes) {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public void writeHex(byte[] bytes) {
        writeBinary(bytes);
    }

    @Override
    public void writeRaw(char ch) {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public void writeNameRaw(byte[] bytes) {
        writeRaw(bytes);
    }

    @Override
    public void writeNameRaw(char[] chars) {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public void writeNameRaw(char[] bytes, int offset, int len) {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public void writeColon() {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public void write(List array) {
        if (array == null) {
            writeArrayNull();
            return;
        }

        final int size = array.size();
        startArray(size);
        for (int i = 0; i < array.size(); i++) {
            Object item = array.get(i);
            writeAny(item);
        }
    }

    @Override
    public void write(Map map) {
        if (map == null) {
            writeNull();
            return;
        }

        startObject();
        for (Map.Entry entry : (Iterable) map.entrySet()) {
            writeAny(entry.getKey());
            writeAny(entry.getValue());
        }
        endObject();
    }

    @Override
    public void write(JSONObject object) {
        if (object == null) {
            writeNull();
            return;
        }

        startObject();
        for (Map.Entry entry : object.entrySet()) {
            writeAny(entry.getKey());
            writeAny(entry.getValue());
        }
        endObject();
    }

    @Override
    public byte[] getBytes() {
        return Arrays.copyOf(bytes, off);
    }

    @Override
    public int size() {
        return off;
    }

    @Override
    public byte[] getBytes(Charset charset) {
        throw new JSONException("not support operator");
    }

    @Override
    public int flushTo(OutputStream to) throws IOException {
        int len = off;
        to.write(bytes, 0, off);
        off = 0;
        return len;
    }

    @Override
    public int flushTo(OutputStream out, Charset charset) {
        throw new JSONException("UnsupportedOperation");
    }

    @Override
    public String toString() {
        if (bytes.length == 0) {
            return "";
        }

        byte[] jsonbBytes = getBytes();
        JSONReader reader = JSONReader.ofJSONB(jsonbBytes);
        JSONWriter writer = JSONWriter.of();
        try {
            Object object = reader.readAny();
            writer.writeAny(object);
            return writer.toString();
        } catch (Exception ex) {
            return JSONB.typeName(bytes[0]) + ", bytes length " + off;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy