TokenBuffer.java

package tools.jackson.databind.util;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.TreeMap;

import tools.jackson.core.*;
import tools.jackson.core.base.ParserMinimalBase;
import tools.jackson.core.exc.InputCoercionException;
import tools.jackson.core.exc.StreamWriteException;
import tools.jackson.core.exc.UnexpectedEndOfInputException;
import tools.jackson.core.io.CharacterEscapes;
import tools.jackson.core.io.NumberInput;
import tools.jackson.core.io.NumberOutput;
import tools.jackson.core.sym.PropertyNameMatcher;
import tools.jackson.core.util.ByteArrayBuilder;
import tools.jackson.core.util.JacksonFeatureSet;
import tools.jackson.core.util.SimpleStreamWriteContext;
import tools.jackson.databind.*;

/**
 * Utility class used for efficient storage of {@link JsonToken}
 * sequences, needed for temporary buffering.
 * Space efficient for different sequence lengths (especially so for smaller
 * ones; but not significantly less efficient for larger), highly efficient
 * for linear iteration and appending. Implemented as segmented/chunked
 * linked list of tokens; only modifications are via appends.
 */
public class TokenBuffer
// Won't use JsonGeneratorBase, to minimize overhead for validity checking
    extends JsonGenerator
{
    protected final static int DEFAULT_STREAM_WRITE_FEATURES = StreamWriteFeature.collectDefaults();

    // Should work for now
    protected final static JacksonFeatureSet<StreamWriteCapability> BOGUS_WRITE_CAPABILITIES
        = JacksonFeatureSet.fromDefaults(StreamWriteCapability.values());

    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Parse context from "parent" parser (one from which content to buffer is read,
     * if specified). Used, if available, when reading content, to present full
     * context as if content was read from the original parser: this is useful
     * in error reporting and sometimes processing as well.
     */
    protected TokenStreamContext _parentContext;

    /**
     * Bit flag composed of bits that indicate which
     * {@link StreamWriteFeature}s
     * are enabled.
     *<p>
     * NOTE: most features have no effect on this class
     */
    protected int _streamWriteFeatures;

    protected final StreamReadConstraints _streamReadConstraints;

    protected boolean _closed;

    protected boolean _hasNativeTypeIds;

    protected boolean _hasNativeObjectIds;

    protected boolean _mayHaveNativeIds;

    /**
     * Flag set during construction, if use of {@link BigDecimal} is to be forced
     * on all floating-point values.
     */
    protected boolean _forceBigDecimal;

    /*
    /**********************************************************************
    /* Token buffering state
    /**********************************************************************
     */

    /**
     * First segment, for contents this buffer has
     */
    protected Segment _first;

    /**
     * Last segment of this buffer, one that is used
     * for appending more tokens
     */
    protected Segment _last;

    /**
     * Offset within last segment,
     */
    protected int _appendAt;

    /**
     * If native type ids supported, this is the id for following
     * value (or first token of one) to be written.
     */
    protected Object _typeId;

    /**
     * If native object ids supported, this is the id for following
     * value (or first token of one) to be written.
     */
    protected Object _objectId;

    /**
     * Do we currently have a native type or object id buffered?
     */
    protected boolean _hasNativeId = false;

    /*
    /**********************************************************************
    /* Output state
    /**********************************************************************
     */

    protected SimpleStreamWriteContext _tokenWriteContext;

    // 05-Oct-2017, tatu: need to consider if this needs to  be properly linked...
    //   especially for "convertValue()" use case
    /**
     * @since 3.0
     */
    protected ObjectWriteContext _objectWriteContext; // = ObjectWriteContext.empty();

    /*
    /**********************************************************************
    /* Life-cycle: constructors
    /**********************************************************************
     */

    /**
     * @param hasNativeIds Whether resulting {@link JsonParser} (if created)
     *   is considered to support native type and object ids
     */
    @Deprecated
    public TokenBuffer(boolean hasNativeIds)
    {
        _streamWriteFeatures = DEFAULT_STREAM_WRITE_FEATURES;
        _tokenWriteContext = SimpleStreamWriteContext.createRootContext(null);
        // at first we have just one segment
        _first = _last = new Segment();
        _appendAt = 0;
        _hasNativeTypeIds = hasNativeIds;
        _hasNativeObjectIds = hasNativeIds;

        // Nothing to base constraints on so:
        _streamReadConstraints = StreamReadConstraints.defaults();

        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
    }

    /**
     * @since 3.0
     */
    public TokenBuffer(ObjectWriteContext writeContext, boolean hasNativeIds)
    {
        _objectWriteContext = writeContext;
        _streamWriteFeatures = DEFAULT_STREAM_WRITE_FEATURES;
        _tokenWriteContext = SimpleStreamWriteContext.createRootContext(null);

        // Nothing to base constraints on so:
        _streamReadConstraints = StreamReadConstraints.defaults();

        // at first we have just one segment
        _first = _last = new Segment();
        _appendAt = 0;
        _hasNativeTypeIds = hasNativeIds;
        _hasNativeObjectIds = hasNativeIds;

        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
    }

    protected TokenBuffer(JsonParser p, ObjectReadContext ctxt)
    {
        _parentContext = p.streamReadContext();
        // Work-around mostly for unit tests; either should be able to provide
        // proper values but context preferable.
        _streamReadConstraints = (ctxt == null) ? p.streamReadConstraints() : ctxt.streamReadConstraints();
        _streamWriteFeatures = DEFAULT_STREAM_WRITE_FEATURES;
        _tokenWriteContext = SimpleStreamWriteContext.createRootContext(null);
        // at first we have just one segment
        _first = _last = new Segment();
        _appendAt = 0;
        _hasNativeTypeIds = p.canReadTypeId();
        _hasNativeObjectIds = p.canReadObjectId();
        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;
        if (ctxt instanceof DeserializationContext deserializationContext) {
            _forceBigDecimal = deserializationContext.isEnabled(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
        } else {
            _forceBigDecimal = false;
        }
    }

    /*
    /**********************************************************************
    /* Life-cycle: helper factory methods
    /**********************************************************************
     */

    /**
     * Specialized factory method used when we are generating token stream for further processing
     * without tokens coming from specific input token stream.
     *
     * @since 3.0
     */
    public static TokenBuffer forGeneration()
    {
        return new TokenBuffer(false);
    }

    /**
     * Specialized factory method used when we are specifically buffering contents of
     * a token stream for further processing.
     *
     * @since 3.0
     */
    public static TokenBuffer forBuffering(JsonParser p, ObjectReadContext ctxt) {
        return new TokenBuffer(p, ctxt);
    }

    /*
    /**********************************************************************
    /* Life-cycle: initialization
    /**********************************************************************
     */

    /**
     * Method that allows explicitly specifying parent parse context to associate
     * with contents of this buffer. Usually context is assigned at construction,
     * based on given parser; but it is not always available, and may not contain
     * intended context.
     */
    public TokenBuffer overrideParentContext(TokenStreamContext ctxt) {
        _parentContext = ctxt;
        return this;
    }

    public TokenBuffer forceUseOfBigDecimal(boolean b) {
        _forceBigDecimal = b;
        return this;
    }

    /*
    /**********************************************************************
    /* Parser construction
    /**********************************************************************
     */

    /**
     * Method used to create a {@link JsonParser} that can read contents
     * stored in this buffer. Will create an "empty" read context
     * (see {@link ObjectReadContext#empty()} which often is not what you want.
     *<p>
     * Note: instances are not synchronized, that is, they are not thread-safe
     * if there are concurrent appends to the underlying buffer.
     *
     * @return Parser that can be used for reading contents stored in this buffer
     */
    public JsonParser asParser() {
        return new Parser(ObjectReadContext.empty(), this,
                _first, _hasNativeTypeIds, _hasNativeObjectIds,
                _parentContext, _streamReadConstraints);
    }

    /**
     * Method used to create a {@link JsonParser} that can read contents
     * stored in this buffer.
     *<p>
     * Note: instances are not synchronized, that is, they are not thread-safe
     * if there are concurrent appends to the underlying buffer.
     *
     * @param readCtxt Active read context to use.
     *
     * @return Parser that can be used for reading contents stored in this buffer
     */
    public JsonParser asParser(ObjectReadContext readCtxt)
    {
        return new Parser(readCtxt, this,
                _first, _hasNativeTypeIds, _hasNativeObjectIds,
                _parentContext, _streamReadConstraints);
    }

    /**
     * @param p0 Parser to use for accessing source information
     *    like location, streamReadConstraints
     */
    public JsonParser asParser(ObjectReadContext readCtxt, JsonParser p0)
    {
        StreamReadConstraints src = (p0 == null)
                ? _streamReadConstraints : p0.streamReadConstraints();
        Parser p = new Parser(readCtxt, this,
                _first, _hasNativeTypeIds, _hasNativeObjectIds,
                _parentContext, src);
        if (p0 != null) {
            p.setLocation(p0.currentTokenLocation());
        }
        return p;
    }

    /**
     * Same as:
     *<pre>
     *  JsonParser p = asParser(readCtxt);
     *  p.nextToken();
     *  return p;
     *</pre>
     */
    public JsonParser asParserOnFirstToken(ObjectReadContext readCtxt)
        throws JacksonException
    {
        JsonParser p = asParser(readCtxt);
        p.nextToken();
        return p;
    }

    public JsonParser asParserOnFirstToken(ObjectReadContext readCtxt,
            JsonParser src) throws JacksonException
    {
        JsonParser p = asParser(readCtxt, src);
        p.nextToken();
        return p;
    }

    /*
    /**********************************************************************
    /* Versioned (mostly since buffer is `JsonGenerator`
    /**********************************************************************
     */

    @Override
    public Version version() {
        return tools.jackson.databind.cfg.PackageVersion.VERSION;
    }

    /*
    /**********************************************************************
    /* `JsonGenerator` config access
    /**********************************************************************
     */
    
    @Override
    public CharacterEscapes getCharacterEscapes() { return null; }

    @Override
    public int getHighestNonEscapedChar() { return 0; }

    @Override
    public PrettyPrinter getPrettyPrinter() { return null; }

    @Override
    public FormatSchema getSchema() { return null; }

    /*
    /**********************************************************************
    /* Additional accessors
    /**********************************************************************
     */

    public JsonToken firstToken() {
        // no need to null check; never create without `_first`
        return _first.type(0);
    }

    /**
     * Accessor for checking whether this buffer has one or more tokens
     * or not.
     *
     * @return True if this buffer instance has no tokens
     *
     * @since 2.13
     */
    public boolean isEmpty() {
        return (_appendAt == 0) && (_first == _last);
    }
    /*
    /**********************************************************************
    /* Other custom methods not needed for implementing interfaces
    /**********************************************************************
     */

    /**
     * Helper method that will append contents of given buffer into this
     * buffer.
     * Not particularly optimized; can be made faster if there is need.
     *
     * @return This buffer
     */
    @SuppressWarnings("resource")
    public TokenBuffer append(TokenBuffer other)
    {
        // Important? If source has native ids, need to store
        if (!_hasNativeTypeIds) {
            _hasNativeTypeIds = other.canWriteTypeId();
        }
        if (!_hasNativeObjectIds) {
            _hasNativeObjectIds = other.canWriteObjectId();
        }
        _mayHaveNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;

        JsonParser p = other.asParser();
        while (p.nextToken() != null) {
            copyCurrentStructure(p);
        }
        return this;
    }

    /**
     * Helper method that will write all contents of this buffer
     * using given {@link JsonGenerator}.
     *<p>
     * Note: this method would be enough to implement
     * <code>ValueSerializer</code>  for <code>TokenBuffer</code> type;
     * but we cannot have upwards
     * references (from core to mapper package); and as such we also
     * cannot take second argument.
     */
    public void serialize(JsonGenerator gen) throws JacksonException
    {
        Segment segment = _first;
        int ptr = -1;

        final boolean checkIds = _mayHaveNativeIds;
        boolean hasIds = checkIds && (segment.hasIds());

        while (true) {
            if (++ptr >= Segment.TOKENS_PER_SEGMENT) {
                ptr = 0;
                segment = segment.next();
                if (segment == null) break;
                hasIds = checkIds && (segment.hasIds());
            }
            JsonToken t = segment.type(ptr);
            if (t == null) break;

            if (hasIds) {
                Object id = segment.findObjectId(ptr);
                if (id != null) {
                    gen.writeObjectId(id);
                }
                id = segment.findTypeId(ptr);
                if (id != null) {
                    gen.writeTypeId(id);
                }
            }

            // Note: copied from 'copyCurrentEvent'...
            switch (t) {
            case START_OBJECT:
                gen.writeStartObject();
                break;
            case END_OBJECT:
                gen.writeEndObject();
                break;
            case START_ARRAY:
                gen.writeStartArray();
                break;
            case END_ARRAY:
                gen.writeEndArray();
                break;
            case PROPERTY_NAME:
            {
                // 13-Dec-2010, tatu: Maybe we should start using different type tokens to reduce casting?
                Object ob = segment.get(ptr);
                if (ob instanceof SerializableString str) {
                    gen.writeName(str);
                } else {
                    gen.writeName((String) ob);
                }
            }
                break;
            case VALUE_STRING:
                {
                    Object ob = segment.get(ptr);
                    if (ob instanceof SerializableString str) {
                        gen.writeString(str);
                    } else {
                        gen.writeString((String) ob);
                    }
                }
                break;
            case VALUE_NUMBER_INT:
                {
                    Object n = segment.get(ptr);
                    if (n instanceof Integer i) {
                        gen.writeNumber(i);
                    } else if (n instanceof BigInteger bi) {
                        gen.writeNumber(bi);
                    } else if (n instanceof Long l) {
                        gen.writeNumber(l);
                    } else if (n instanceof Short s) {
                        gen.writeNumber(s);
                    } else {
                        gen.writeNumber(((Number) n).intValue());
                    }
                }
                break;
            case VALUE_NUMBER_FLOAT:
                {
                    Object n = segment.get(ptr);
                    if (n instanceof Double d) {
                        gen.writeNumber(d);
                    } else if (n instanceof BigDecimal bd) {
                        gen.writeNumber(bd);
                    } else if (n instanceof Float f) {
                        gen.writeNumber(f);
                    } else if (n == null) {
                        gen.writeNull();
                    } else if (n instanceof String s) {
                        gen.writeNumber(s);
                    } else {
                        throw new StreamWriteException(gen, String.format(
                                "Unrecognized value type for VALUE_NUMBER_FLOAT: %s, cannot serialize",
                                n.getClass().getName()));
                    }
                }
                break;
            case VALUE_TRUE:
                gen.writeBoolean(true);
                break;
            case VALUE_FALSE:
                gen.writeBoolean(false);
                break;
            case VALUE_NULL:
                gen.writeNull();
                break;
            case VALUE_EMBEDDED_OBJECT:
                {
                    Object value = segment.get(ptr);
                    // 01-Sep-2016, tatu: as per [databind#1361], should use `writeEmbeddedObject()`;
                    //    however, may need to consider alternatives for some well-known types
                    //    first
                    if (value instanceof RawValue rawValue) {
                        rawValue.serialize(gen);
                    } else if (value instanceof JacksonSerializable) {
                        gen.writePOJO(value);
                    } else {
                        gen.writeEmbeddedObject(value);
                    }
                }
                break;
            default:
                throw new RuntimeException("Internal error: should never end up through this code path");
            }
        }
    }

    /**
     * Helper method used by standard deserializer.
     */
    public TokenBuffer deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException
    {
        if (!p.hasToken(JsonToken.PROPERTY_NAME)) {
            copyCurrentStructure(p);
            return this;
        }
        // 28-Oct-2014, tatu: As per [databind#592], need to support a special case of starting from
        //    PROPERTY_NAME, which is taken to mean that we are missing START_OBJECT, but need
        //    to assume one did exist.
        JsonToken t;
        writeStartObject();
        do {
            copyCurrentStructure(p);
        } while ((t = p.nextToken()) == JsonToken.PROPERTY_NAME);
        if (t != JsonToken.END_OBJECT) {
            ctxt.reportWrongTokenException(TokenBuffer.class, JsonToken.END_OBJECT,
                    "Expected END_OBJECT after copying contents of a JsonParser into TokenBuffer, got "+t);
            // never gets here
        }
        writeEndObject();
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public String toString()
    {
        // Let's print up to 100 first tokens...
        final int MAX_COUNT = 100;

        StringBuilder sb = new StringBuilder();
        sb.append("[TokenBuffer: ");

        /*
sb.append("NativeTypeIds=").append(_hasNativeTypeIds).append(",");
sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(",");
*/

        JsonParser p = asParser();
        int count = 0;
        final boolean hasNativeIds = _hasNativeTypeIds || _hasNativeObjectIds;

        while (true) {
            JsonToken t = p.nextToken();
            if (t == null) break;

            if (count < MAX_COUNT) {
                if (count > 0) {
                    sb.append(", ");
                }
                if (hasNativeIds) {
                    _appendNativeIds(sb);
                }
                sb.append(t.toString());
                if (t == JsonToken.PROPERTY_NAME) {
                    sb.append('(');
                    sb.append(p.currentName());
                    sb.append(')');
                }
            }
            ++count;
        }
        if (count >= MAX_COUNT) {
            sb.append(" ... (truncated ").append(count-MAX_COUNT).append(" entries)");
        }
        sb.append(']');
        return sb.toString();
    }

    private final void _appendNativeIds(StringBuilder sb)
    {
        Object objectId = _last.findObjectId(_appendAt-1);
        if (objectId != null) {
            sb.append("[objectId=").append(String.valueOf(objectId)).append(']');
        }
        Object typeId = _last.findTypeId(_appendAt-1);
        if (typeId != null) {
            sb.append("[typeId=").append(String.valueOf(typeId)).append(']');
        }
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: context
    /**********************************************************************
     */

    @Override
    public TokenStreamContext streamWriteContext() { return _tokenWriteContext; }

    @Override
    public Object currentValue() {
        return _tokenWriteContext.currentValue();
    }

    @Override
    public void assignCurrentValue(Object v) {
        _tokenWriteContext.assignCurrentValue(v);
    }

    @Override
    public ObjectWriteContext objectWriteContext() { return _objectWriteContext; }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: configuration
    /**********************************************************************
     */

    @Override
    public JsonGenerator configure(StreamWriteFeature f, boolean state) {
        if (state) {
            _streamWriteFeatures |= f.getMask();
        } else {
            _streamWriteFeatures &= ~f.getMask();
        }
        return this;
    }

    //public JsonGenerator configure(SerializationFeature f, boolean state) { }

    @Override
    public boolean isEnabled(StreamWriteFeature f) {
        return (_streamWriteFeatures & f.getMask()) != 0;
    }

    @Override
    public int streamWriteFeatures() {
        return _streamWriteFeatures;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: capability introspection
    /**********************************************************************
     */

    // 20-May-2020, tatu: This may or may not be enough -- ideally access is
    //    via `DeserializationContext`, not parser, but if latter is needed
    //    then we'll need to pass this from parser contents of which were
    //    buffered.
    @Override
    public JacksonFeatureSet<StreamWriteCapability> streamWriteCapabilities() {
        return BOGUS_WRITE_CAPABILITIES;
    }

    @Override
    public boolean has(StreamWriteCapability capability) {
        return BOGUS_WRITE_CAPABILITIES.isEnabled(capability);
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: low-level output handling
    /**********************************************************************
     */

    @Override
    public void flush() { /* NOP */ }

    @Override
    public void close() {
        _closed = true;
    }

    @Override
    public boolean isClosed() { return _closed; }

    @Override
    public Object streamWriteOutputTarget() { return null; }

    @Override
    public int streamWriteOutputBuffered() { return -1; }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: write methods, structural
    /**********************************************************************
     */

    @Override
    public final JsonGenerator writeStartArray()
    {
        _appendStartMarker(JsonToken.START_ARRAY);
        _tokenWriteContext = _tokenWriteContext.createChildArrayContext(null);
        return this;
    }

    @Override
    public final JsonGenerator writeStartArray(Object forValue)
    {
        _appendStartMarker(JsonToken.START_ARRAY);
        _tokenWriteContext = _tokenWriteContext.createChildArrayContext(forValue);
        return this;
    }

    @Override
    public final JsonGenerator writeStartArray(Object forValue, int len)
    {
        _appendStartMarker(JsonToken.START_ARRAY);
        _tokenWriteContext = _tokenWriteContext.createChildArrayContext(forValue);
        return this;
    }

    @Override
    public final JsonGenerator writeEndArray()
    {
        _appendEndMarker(JsonToken.END_ARRAY);
        return this;
    }

    @Override
    public final JsonGenerator writeStartObject()
    {
        _appendStartMarker(JsonToken.START_OBJECT);
        _tokenWriteContext = _tokenWriteContext.createChildObjectContext(null);
        return this;
    }

    @Override
    public JsonGenerator writeStartObject(Object forValue)
    {
        _appendStartMarker(JsonToken.START_OBJECT);
        _tokenWriteContext = _tokenWriteContext.createChildObjectContext(forValue);
        return this;
    }

    @Override
    public JsonGenerator writeStartObject(Object forValue, int size)
    {
        _appendStartMarker(JsonToken.START_OBJECT);
        _tokenWriteContext = _tokenWriteContext.createChildObjectContext(forValue);
        return this;
    }

    @Override
    public final JsonGenerator writeEndObject() {
        _appendEndMarker(JsonToken.END_OBJECT);
        return this;
    }

    @Override
    public final JsonGenerator writeName(String name) {
        _tokenWriteContext.writeName(name);
        _appendName(name);
        return this;
    }

    @Override
    public JsonGenerator writeName(SerializableString name) {
        _tokenWriteContext.writeName(name.getValue());
        _appendName(name);
        return this;
    }

    @Override
    public JsonGenerator writePropertyId(long id) {
        // 15-Aug-2019, tatu: could and probably should be improved to support
        //    buffering but...
        final String name = Long.toString(id);
        _tokenWriteContext.writeName(name);
        _appendName(name);
        return this;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: write methods, textual
    /**********************************************************************
     */

    @Override
    public JsonGenerator writeString(String text) {
        if (text == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_STRING, text);
        }
        return this;
    }

    @Override
    public JsonGenerator writeString(char[] text, int offset, int len) {
        return writeString(new String(text, offset, len));
    }

    @Override
    public JsonGenerator writeString(SerializableString text) {
        if (text == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_STRING, text);
        }
        return this;
    }

    @Override
    public JsonGenerator writeString(Reader reader, final int len)
    {
        if (reader == null) {
            _reportError("null reader");
        }
        int toRead = (len >= 0) ? len : Integer.MAX_VALUE;

        // 11-Mar-2023, tatu: Really crude implementation, but it is not
        //    expected this method gets often used. Feel free to send a PR
        //    for more optimal handling if you got an itch. :)
        final char[] buf = new char[1000];
        StringBuilder sb = new StringBuilder(1000);
        while (toRead > 0) {
            int toReadNow = Math.min(toRead, buf.length);
            int numRead;

            try {
                numRead = reader.read(buf, 0, toReadNow);
            } catch (IOException e) {
                throw _wrapIOFailure(e);
            }
            if (numRead <= 0) {
                break;
            }
            sb.append(buf, 0, numRead);
            toRead -= numRead;
        }
        if (toRead > 0 && len >= 0) {
            _reportError("Was not able to read enough from reader");
        }
        _appendValue(JsonToken.VALUE_STRING, sb.toString());
        return this;
    }

    @Override
    public JsonGenerator writeRawUTF8String(byte[] text, int offset, int length) {
        // could add support for buffering if we really want it...
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeUTF8String(byte[] text, int offset, int length) {
        // could add support for buffering if we really want it...
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRaw(String text) {
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRaw(String text, int offset, int len) {
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRaw(SerializableString text) {
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRaw(char[] text, int offset, int len) {
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRaw(char c) {
        return _reportUnsupportedOperation();
    }

    @Override
    public JsonGenerator writeRawValue(String text) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
        return this;
    }

    @Override
    public JsonGenerator writeRawValue(String text, int offset, int len) {
        if (offset > 0 || len != text.length()) {
            text = text.substring(offset, offset+len);
        }
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text));
        return this;
    }

    @Override
    public JsonGenerator writeRawValue(char[] text, int offset, int len) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new String(text, offset, len));
        return this;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: write methods, primitive types
    /**********************************************************************
     */

    @Override
    public JsonGenerator writeNumber(short i) {
        _appendValue(JsonToken.VALUE_NUMBER_INT, Short.valueOf(i));
        return this;
    }

    @Override
    public JsonGenerator writeNumber(int i) {
        _appendValue(JsonToken.VALUE_NUMBER_INT, Integer.valueOf(i));
        return this;
    }

    @Override
    public JsonGenerator writeNumber(long l) {
        _appendValue(JsonToken.VALUE_NUMBER_INT, Long.valueOf(l));
        return this;
    }

    @Override
    public JsonGenerator writeNumber(double d) {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, Double.valueOf(d));
        return this;
    }

    @Override
    public JsonGenerator writeNumber(float f) {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, Float.valueOf(f));
        return this;
    }

    @Override
    public JsonGenerator writeNumber(BigDecimal dec) {
        if (dec == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_NUMBER_FLOAT, dec);
        }
        return this;
    }

    @Override
    public JsonGenerator writeNumber(BigInteger v) {
        if (v == null) {
            writeNull();
        } else {
            _appendValue(JsonToken.VALUE_NUMBER_INT, v);
        }
        return this;
    }

    @Override
    public JsonGenerator writeNumber(String encodedValue) {
        /* 03-Dec-2010, tatu: related to [JACKSON-423], should try to keep as numeric
         *   identity as long as possible
         */
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
        return this;
    }

    /**
     * Write method that can be used for custom numeric types that can
     * not be (easily?) converted to "standard" Java number types.
     * Because numbers are not surrounded by double quotes, regular
     * {@link #writeString} method cannot be used; nor
     * {@link #writeRaw} because that does not properly handle
     * value separators needed in Array or Object contexts.
     *
     * @param encodedValue Textual (possibly formatted) number representation to write
     * @param isInteger Whether value should be considered an integer
     *
     * @throws IOException if there is either an underlying I/O problem or encoding
     *    issue at format layer
     * @since 2.18
     */
    public void writeNumber(String encodedValue, boolean isInteger) throws IOException {
        _appendValue(
            isInteger ? JsonToken.VALUE_NUMBER_INT : JsonToken.VALUE_NUMBER_FLOAT,
            encodedValue);
    }

    private void writeLazyInteger(Object encodedValue) {
        _appendValue(JsonToken.VALUE_NUMBER_INT, encodedValue);
    }

    private void writeLazyDecimal(Object encodedValue) {
        _appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue);
    }

    @Override
    public JsonGenerator writeBoolean(boolean state) {
        _appendValue(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE);
        return this;
    }

    @Override
    public JsonGenerator writeNull() {
        _appendValue(JsonToken.VALUE_NULL);
        return this;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: write methods for POJOs/trees
    /**********************************************************************
     */

    @Override
    public JsonGenerator writePOJO(Object value)
    {
        if (value == null) {
            return writeNull();
        }
        final Class<?> raw = value.getClass();
        if (raw == byte[].class || (value instanceof RawValue)
                || (_objectWriteContext == null)) {
            _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value);
            return this;
        }
        _objectWriteContext.writeValue(this, value);
        return this;
    }

    @Override
    public JsonGenerator writeTree(TreeNode node)
    {
        if (node == null) {
            return writeNull();
        }
        if (_objectWriteContext == null) {
            _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, node);
            return this;
        }
        _objectWriteContext.writeTree(this, node);
        return this;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation; binary
    /**********************************************************************
     */

    @Override
    public JsonGenerator writeBinary(Base64Variant b64variant, byte[] data, int offset, int len)
    {
        // 12-Jan-2021, tatu: Should we try to preserve the variant? Depends a
        //   lot on whether this during read (no need to retain probably) or
        //   write (probably important)
        byte[] copy = Arrays.copyOfRange(data, offset, offset + len);
        return writePOJO(copy);
    }

    /**
     * Although we could support this method, it does not necessarily make
     * sense: we cannot make good use of streaming because buffer must
     * hold all the data. Because of this, currently this will simply
     * throw {@link UnsupportedOperationException}
     */
    @Override
    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) {
        throw new UnsupportedOperationException();
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation: native ids
    /**********************************************************************
     */

    @Override
    public boolean canWriteTypeId() {
        return _hasNativeTypeIds;
    }

    @Override
    public boolean canWriteObjectId() {
        return _hasNativeObjectIds;
    }

    @Override
    public JsonGenerator writeTypeId(Object id) {
        _typeId = id;
        _hasNativeId = true;
        return this;
    }

    @Override
    public JsonGenerator writeObjectId(Object id) {
        _objectId = id;
        _hasNativeId = true;
        return this;
    }

    @Override
    public JsonGenerator writeEmbeddedObject(Object object) {
        _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, object);
        return this;
    }

    /*
    /**********************************************************************
    /* JsonGenerator implementation; pass-through copy
    /**********************************************************************
     */

    @Override
    public void copyCurrentEvent(JsonParser p)
    {
        if (_mayHaveNativeIds) {
            _checkNativeIds(p);
        }
        switch (p.currentToken()) {
        case START_OBJECT:
            writeStartObject();
            break;
        case END_OBJECT:
            writeEndObject();
            break;
        case START_ARRAY:
            writeStartArray();
            break;
        case END_ARRAY:
            writeEndArray();
            break;
        case PROPERTY_NAME:
            writeName(p.currentName());
            break;
        case VALUE_STRING:
            if (p.hasStringCharacters()) {
                writeString(p.getStringCharacters(), p.getStringOffset(), p.getStringLength());
            } else {
                writeString(p.getString());
            }
            break;
        case VALUE_NUMBER_INT:
            switch (p.getNumberType()) {
            case INT:
                writeNumber(p.getIntValue());
                break;
            case BIG_INTEGER:
                writeLazyInteger(p.getNumberValueDeferred());
                break;
            default:
                writeNumber(p.getLongValue());
            }
            break;
        case VALUE_NUMBER_FLOAT:
            writeLazyDecimal(p.getNumberValueDeferred());
            break;
        case VALUE_TRUE:
            writeBoolean(true);
            break;
        case VALUE_FALSE:
            writeBoolean(false);
            break;
        case VALUE_NULL:
            writeNull();
            break;
        case VALUE_EMBEDDED_OBJECT:
            writePOJO(p.getEmbeddedObject());
            break;
        default:
            throw new RuntimeException("Internal error: unexpected token: "+p.currentToken());
        }
    }

    @Override
    public void copyCurrentStructure(JsonParser p)
    {
        JsonToken t = p.currentToken();

        // Let's handle property name separately first
        if (t == JsonToken.PROPERTY_NAME) {
            if (_mayHaveNativeIds) {
                _checkNativeIds(p);
            }
            writeName(p.currentName());
            t = p.nextToken();
            // fall-through to copy the associated value
        } else if (t == null) {
            // 13-Dec-2023, tatu: For some unexpected EOF cases we may end up here, so:
            throw new UnexpectedEndOfInputException(p, null, "Unexpected end-of-input");
        }

        // We'll do minor handling here to separate structured, scalar values,
        // then delegate appropriately.
        // Plus also deal with oddity of "dangling" END_OBJECT/END_ARRAY
        switch (t) {
        case START_ARRAY:
            if (_mayHaveNativeIds) {
                _checkNativeIds(p);
            }
            writeStartArray();
            _copyBufferContents(p);
            break;
        case START_OBJECT:
            if (_mayHaveNativeIds) {
                _checkNativeIds(p);
            }
            writeStartObject();
            _copyBufferContents(p);
            break;
        case END_ARRAY:
            writeEndArray();
            break;
        case END_OBJECT:
            writeEndObject();
            break;
        default: // others are simple:
            _copyBufferValue(p, t);
        }
    }

    protected void _copyBufferContents(JsonParser p)
    {
        int depth = 1;
        JsonToken t;

        while ((t = p.nextToken()) != null) {
            switch (t) {
            case PROPERTY_NAME:
                if (_mayHaveNativeIds) {
                    _checkNativeIds(p);
                }
                writeName(p.currentName());
                break;

            case START_ARRAY:
                if (_mayHaveNativeIds) {
                    _checkNativeIds(p);
                }
                writeStartArray();
                ++depth;
                break;

            case START_OBJECT:
                if (_mayHaveNativeIds) {
                    _checkNativeIds(p);
                }
                writeStartObject();
                ++depth;
                break;

            case END_ARRAY:
                writeEndArray();
                if (--depth == 0) {
                    return;
                }
                break;
            case END_OBJECT:
                writeEndObject();
                if (--depth == 0) {
                    return;
                }
                break;

            default:
                _copyBufferValue(p, t);
            }
        }
    }

    // NOTE: Copied from earlier `copyCurrentEvent()`
    private void _copyBufferValue(JsonParser p, JsonToken t)
    {
        if (_mayHaveNativeIds) {
            _checkNativeIds(p);
        }
        switch (t) {
        case VALUE_STRING:
            if (p.hasStringCharacters()) {
                writeString(p.getStringCharacters(), p.getStringOffset(), p.getStringLength());
            } else {
                writeString(p.getString());
            }
            break;
        case VALUE_NUMBER_INT:
            switch (p.getNumberType()) {
            case INT:
                writeNumber(p.getIntValue());
                break;
            case BIG_INTEGER:
                writeLazyInteger(p.getNumberValueDeferred());
                break;
            default:
                writeNumber(p.getLongValue());
            }
            break;
        case VALUE_NUMBER_FLOAT:
            writeLazyDecimal(p.getNumberValueDeferred());
            break;
        case VALUE_TRUE:
            writeBoolean(true);
            break;
        case VALUE_FALSE:
            writeBoolean(false);
            break;
        case VALUE_NULL:
            writeNull();
            break;
        case VALUE_EMBEDDED_OBJECT:
            writePOJO(p.getEmbeddedObject());
            break;
        default:
            throw new RuntimeException("Internal error: unexpected token: "+t);
        }
    }

    private final void _checkNativeIds(JsonParser p)
    {
        if ((_typeId = p.getTypeId()) != null) {
            _hasNativeId = true;
        }
        if ((_objectId = p.getObjectId()) != null) {
            _hasNativeId = true;
        }
    }

    /*
    /**********************************************************************
    /* Internal methods
    /**********************************************************************
     */

    /**
     * Method used for appending token known to represent a "simple" scalar
     * value where token is the only information
     */
    protected final void _appendValue(JsonToken type)
    {
         _tokenWriteContext.writeValue();
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, type, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, type);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1; // since we added first at 0
        }
    }

    /**
     * Method used for appending token known to represent a scalar value
     * where there is additional content (text, number) beyond type token
     */
    protected final void _appendValue(JsonToken type, Object value)
    {
         _tokenWriteContext.writeValue();
        Segment next;
        if (_hasNativeId) {
            next = _last.append(_appendAt, type, value, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, type, value);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    /*
     * Specialized method used for appending an Object property name, appending either
     * {@link String} or {@link SerializableString}.
     */
    protected final void _appendName(Object value)
    {
        // NOTE: do NOT clear _objectId / _typeId
        Segment next;
        if (_hasNativeId) {
            next =  _last.append(_appendAt, JsonToken.PROPERTY_NAME, value, _objectId, _typeId);
        } else {
            next = _last.append(_appendAt, JsonToken.PROPERTY_NAME, value);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }
    }

    /**
     * Specialized method used for appending a structural start Object/Array marker
     */
    protected final void _appendStartMarker(JsonToken type)
    {
        _tokenWriteContext.writeValue();

        Segment next;
        if (_hasNativeId) {
            next =_last.append(_appendAt, type, _objectId, _typeId);
        } else {
            next =  _last.append(_appendAt, type);
        }
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1; // since we added first at 0
        }
    }

    /**
     * Specialized method used for appending a structural end Object/Array marker
     */
    protected final void _appendEndMarker(JsonToken type)
    {
        // NOTE: type/object id not relevant
        Segment next = _last.append(_appendAt, type);
        if (next == null) {
            ++_appendAt;
        } else {
            _last = next;
            _appendAt = 1;
        }

        // but then we need to update context. One twist: do allow unbalanced content;
        // for that need to check that we will retain "root context"
        SimpleStreamWriteContext c = _tokenWriteContext.getParent();
        if (c != null) {
            _tokenWriteContext = c;
        }
    }

    @Override
    protected <T> T _reportUnsupportedOperation() {
        throw new UnsupportedOperationException("Called operation not supported for TokenBuffer");
    }

    /*
    /**********************************************************************
    /* Supporting classes
    /**********************************************************************
     */

    protected final static class Parser
        extends ParserMinimalBase
    {
        /*
        /******************************************************************
        /* Configuration
        /******************************************************************
         */

        protected StreamReadConstraints _streamReadConstraints;

        protected final TokenBuffer _source;

        protected final boolean _hasNativeTypeIds;

        protected final boolean _hasNativeObjectIds;

        protected final boolean _hasNativeIds;

        /*
        /******************************************************************
        /* Parsing state
        /******************************************************************
         */

        /**
         * Currently active segment
         */
        protected Segment _segment;

        /**
         * Pointer to current token within current segment
         */
        protected int _segmentPtr;

        /**
         * Information about parser context, context in which
         * the next token is to be parsed (root, array, object).
         */
        protected TokenBufferReadContext _parsingContext;

        protected boolean _closed;

        protected transient ByteArrayBuilder _byteBuilder;

        protected TokenStreamLocation _location = null;

        /*
        /******************************************************************
        /* Construction, init
        /******************************************************************
         */

        public Parser(ObjectReadContext readCtxt, TokenBuffer source,
                Segment firstSeg, boolean hasNativeTypeIds, boolean hasNativeObjectIds,
                TokenStreamContext parentContext,
                StreamReadConstraints streamReadConstraints)
        {
            // 25-Jun-2022, tatu: This should pass stream read features as
            //    per [databind#3528]) but for now at very least should get
            //    sane defaults
            super(readCtxt);
            _source = source;
            _segment = firstSeg;
            _segmentPtr = -1; // not yet read
            _streamReadConstraints = streamReadConstraints;
            _parsingContext = TokenBufferReadContext.createRootContext(parentContext);
            _hasNativeTypeIds = hasNativeTypeIds;
            _hasNativeObjectIds = hasNativeObjectIds;
            _hasNativeIds = (hasNativeTypeIds || hasNativeObjectIds);
        }

        public void setLocation(TokenStreamLocation l) {
            _location = l;
        }

        /*
        /**********************************************************
        /* Public API, config access, capability introspection
        /**********************************************************
         */

        @Override
        public Version version() {
            return tools.jackson.databind.cfg.PackageVersion.VERSION;
        }

        // 20-May-2020, tatu: This may or may not be enough -- ideally access is
        //    via `DeserializationContext`, not parser, but if latter is needed
        //    then we'll need to pass this from parser contents if which were
        //    buffered.
        @Override
        public JacksonFeatureSet<StreamReadCapability> streamReadCapabilities() {
            return DEFAULT_READ_CAPABILITIES;
        }

        @Override
        public TokenBuffer streamReadInputSource() {
            return _source;
        }

        @Override
        public StreamReadConstraints streamReadConstraints() {
            return _streamReadConstraints;
        }

        /*
        /******************************************************************
        /* Extended API beyond JsonParser
        /******************************************************************
         */

        public JsonToken peekNextToken()
        {
            // closed? nothing more to peek, either
            if (_closed) return null;
            Segment seg = _segment;
            int ptr = _segmentPtr+1;
            if (ptr >= Segment.TOKENS_PER_SEGMENT) {
                ptr = 0;
                seg = (seg == null) ? null : seg.next();
            }
            return (seg == null) ? null : seg.type(ptr);
        }

        /*
        /******************************************************************
        /* Closing, related
        /******************************************************************
         */

        @Override
        public void close() {
            _closed = true;
        }

        @Override
        protected void _closeInput() throws IOException { }

        @Override
        protected void _releaseBuffers() { }

        /*
        /******************************************************************
        /* Public API, traversal
        /******************************************************************
         */

        @Override
        public JsonToken nextToken()
        {
            // If we are closed, nothing more to do
            if (_closed || (_segment == null)) {
                _updateTokenToNull();
                return null;
            }

            // Ok, then: any more tokens?
            if (++_segmentPtr >= Segment.TOKENS_PER_SEGMENT) {
                _segmentPtr = 0;
                _segment = _segment.next();
                if (_segment == null) {
                    _updateTokenToNull();
                    return null;
                }
            }
            _updateToken(_segment.type(_segmentPtr));
            // Property name? Need to update context
            if (_currToken == JsonToken.PROPERTY_NAME) {
                Object ob = _currentObject();
                String name = (ob instanceof String string) ? string : ob.toString();
                _parsingContext.setCurrentName(name);
            } else if (_currToken == JsonToken.START_OBJECT) {
                _parsingContext = _parsingContext.createChildObjectContext();
            } else if (_currToken == JsonToken.START_ARRAY) {
                _parsingContext = _parsingContext.createChildArrayContext();
            } else if (_currToken == JsonToken.END_OBJECT
                    || _currToken == JsonToken.END_ARRAY) {
                // Closing JSON Object/Array? Close matching context
                _parsingContext = _parsingContext.parentOrCopy();
            } else {
                _parsingContext.updateForValue();
            }
            return _currToken;
        }

        @Override
        public String nextName()
        {
            // inlined common case from nextToken()
            if (_closed || (_segment == null)) {
                return null;
            }

            int ptr = _segmentPtr+1;
            if ((ptr < Segment.TOKENS_PER_SEGMENT) && (_segment.type(ptr) == JsonToken.PROPERTY_NAME)) {
                _segmentPtr = ptr;
                _updateToken(JsonToken.PROPERTY_NAME);
                Object ob = _segment.get(ptr); // inlined _currentObject();
                String name = (ob instanceof String string) ? string : ob.toString();
                _parsingContext.setCurrentName(name);
                return name;
            }
            return (nextToken() == JsonToken.PROPERTY_NAME) ? currentName() : null;
        }

        // NOTE: since we know there's no native matching just use simpler way:
        @Override // since 3.0
        public int nextNameMatch(PropertyNameMatcher matcher) {
            String str = nextName();
            if (str != null) {
                // 15-Nov-2017, tatu: Cannot assume name given is intern()ed
                return matcher.matchName(str);
            }
            if (hasToken(JsonToken.END_OBJECT)) {
                return PropertyNameMatcher.MATCH_END_OBJECT;
            }
            return PropertyNameMatcher.MATCH_ODD_TOKEN;
        }

        @Override
        public boolean isClosed() { return _closed; }

        /*
        /******************************************************************
        /* Public API, token accessors
        /******************************************************************
         */

        @Override public TokenStreamContext streamReadContext() { return _parsingContext; }
        @Override public void assignCurrentValue(Object v) { _parsingContext.assignCurrentValue(v); }
        @Override public Object currentValue() { return _parsingContext.currentValue(); }

        @Override
        public TokenStreamLocation currentTokenLocation() { return currentLocation(); }

        @Override
        public TokenStreamLocation currentLocation() {
            return (_location == null) ? TokenStreamLocation.NA : _location;
        }

        @Override
        public String currentName() {
            // 25-Jun-2015, tatu: as per [databind#838], needs to be same as ParserBase
            if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
                TokenStreamContext parent = _parsingContext.getParent();
                return parent.currentName();
            }
            return _parsingContext.currentName();
        }

        /*
        /******************************************************************
        /* Public API, access to token information, text
        /******************************************************************
         */

        @Override
        public String getString()
        {
            // common cases first:
            if (_currToken == JsonToken.VALUE_STRING
                    || _currToken == JsonToken.PROPERTY_NAME) {
                Object ob = _currentObject();
                if (ob instanceof String string) {
                    return string;
                }
                return ClassUtil.nullOrToString(ob);
            }
            if (_currToken == null) {
                return null;
            }
            switch (_currToken) {
            case VALUE_NUMBER_INT:
            case VALUE_NUMBER_FLOAT:
                return ClassUtil.nullOrToString(_currentObject());
            default:
            	return _currToken.asString();
            }
        }

        @Override
        public char[] getStringCharacters() {
            String str = getString();
            return (str == null) ? null : str.toCharArray();
        }

        @Override
        public int getStringLength() {
            String str = getString();
            return (str == null) ? 0 : str.length();
        }

        @Override
        public int getStringOffset() { return 0; }

        @Override
        public boolean hasStringCharacters() {
            // We never have raw buffer available, so:
            return false;
        }

        /*
        /******************************************************************
        /* Public API, access to token information, numeric
        /******************************************************************
         */

        @Override
        public boolean isNaN() {
            // can only occur for floating-point numbers
            if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
                Object value = _currentObject();
                if (value instanceof Double d) {
                    return NumberOutput.notFinite(d);
                }
                if (value instanceof Float f) {
                    return NumberOutput.notFinite(f);
                }
            }
            return false;
        }

        @Override
        public BigInteger getBigIntegerValue()
        {
            Number n = _numberValue(NR_BIGINT, true);
            if (n instanceof BigInteger bi) {
                return bi;
            } else if (n instanceof BigDecimal bd) {
                streamReadConstraints().validateBigIntegerScale(bd.scale());
                return bd.toBigInteger();
            }
            // int/long is simple, but let's also just truncate float/double:
            return BigInteger.valueOf(n.longValue());
        }

        @Override
        public BigDecimal getDecimalValue()
        {
            Number n = _numberValue(NR_BIGDECIMAL, true);
            if (n instanceof BigDecimal bd) {
                return bd;
            } else if (n instanceof Integer) {
                return BigDecimal.valueOf(n.intValue());
            } else if (n instanceof Long) {
                return BigDecimal.valueOf(n.longValue());
            } else if (n instanceof BigInteger bi) {
                return new BigDecimal(bi);
            }
            // float or double
            return BigDecimal.valueOf(n.doubleValue());
        }

        @Override
        public double getDoubleValue() {
            return _numberValue(NR_DOUBLE, false).doubleValue();
        }

        @Override
        public float getFloatValue() {
            return _numberValue(NR_FLOAT, false).floatValue();
        }

        @Override
        public int getIntValue()
        {
            final Number n = _numberValue(NR_INT, false);
            if ((n instanceof Integer) || _smallerThanInt(n)) {
                return n.intValue();
            }
            return _convertNumberToInt(n);
        }

        @Override
        public long getLongValue() {
            final Number n = _numberValue(NR_LONG, false);
            if ((n instanceof Long) || _smallerThanLong(n)) {
                return n.longValue();
            }
            return _convertNumberToLong(n);
        }

        @Override
        public NumberType getNumberType()
        {
            // 2021-01-12, tatu: Avoid throwing exception by not calling accessor
            if (_currToken == null) {
                return null;
            }
            final Object value = _currentObject();
            if (value instanceof Number n) {
                if (n instanceof Integer) return NumberType.INT;
                if (n instanceof Long) return NumberType.LONG;
                if (n instanceof Double) return NumberType.DOUBLE;
                if (n instanceof BigDecimal) return NumberType.BIG_DECIMAL;
                if (n instanceof BigInteger) return NumberType.BIG_INTEGER;
                if (n instanceof Float) return NumberType.FLOAT;
                if (n instanceof Short) return NumberType.INT;       // should be SHORT
            } else if (value instanceof String) {
                return (_currToken == JsonToken.VALUE_NUMBER_FLOAT)
                        ? NumberType.BIG_DECIMAL : NumberType.BIG_INTEGER;
            }
            return null;
        }

        @Override
        public NumberTypeFP getNumberTypeFP()
        {
            if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
                Object n = _currentObject();
                if (n instanceof Double) return NumberTypeFP.DOUBLE64;
                if (n instanceof BigDecimal) return NumberTypeFP.BIG_DECIMAL;
                if (n instanceof Float) return NumberTypeFP.FLOAT32;
            }
            return NumberTypeFP.UNKNOWN;
        }

        @Override
        public final Number getNumberValue() {
            return _numberValue(-1, false);
        }

        @Override
        public Object getNumberValueDeferred() {
            // Former "_checkIsNumber()"
            if (_currToken == null || !_currToken.isNumeric()) {
                throw _constructNotNumericType(_currToken, 0);
            }
            return _currentObject();
        }

        private Number _numberValue(final int targetNumType, final boolean preferBigNumbers) {
            // Former "_checkIsNumber()"
            if (_currToken == null || !_currToken.isNumeric()) {
                throw _constructNotNumericType(_currToken, targetNumType);
            }
            Object value = _currentObject();
            if (value instanceof Number number) {
                return number;
            }
            // Difficult to really support numbers-as-Strings; but let's try.
            // NOTE: no access to DeserializationConfig, unfortunately, so cannot
            // try to determine Double/BigDecimal preference...

            // 12-Jan-2021, tatu: Is this really needed, and for what? CSV, XML?
            if (value instanceof String str) {
                final int len = str.length();
                if (_currToken == JsonToken.VALUE_NUMBER_INT) {
                    // 08-Dec-2023, tatu: Note -- deferred numbers' validity (wrt input token)
                    //    has been verified by underlying `JsonParser`: no need to check again
                    if (preferBigNumbers
                            // 01-Feb-2023, tatu: Not really accurate but we'll err on side
                            //   of not losing accuracy (should really check 19-char case,
                            //   or, with minus sign, 20-char)
                            || (len >= 19)) {
                        return NumberInput.parseBigInteger(str, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
                    }
                    // Otherwise things get trickier; here, too, we should use more accurate
                    // boundary checks
                    if (len >= 10) {
                        return NumberInput.parseLong(str);
                    }
                    return NumberInput.parseInt(str);
                }
                if (preferBigNumbers) {
                    BigDecimal dec = NumberInput.parseBigDecimal(str,
                            isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
                    // 01-Feb-2023, tatu: This is... weird. Seen during tests, only
                    if (dec == null) {
                        throw new IllegalStateException("Internal error: failed to parse number '"+str+"'");
                    }
                    return dec;
                }
                return NumberInput.parseDouble(str, isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
            }
            throw new IllegalStateException("Internal error: entry should be a Number, but is of type "
                    +ClassUtil.classNameOf(value));
        }

        private final boolean _smallerThanInt(Number n) {
            return (n instanceof Short) || (n instanceof Byte);
        }

        private final boolean _smallerThanLong(Number n) {
            return (n instanceof Integer) || (n instanceof Short) || (n instanceof Byte);
        }

        // 02-Jan-2017, tatu: Modified from method(s) in `ParserBase`

        protected int _convertNumberToInt(Number n) throws InputCoercionException
        {
            if (n instanceof Long) {
                long l = n.longValue();
                int result = (int) l;
                if (((long) result) != l) {
                    _reportOverflowInt();
                }
                return result;
            }
            if (n instanceof BigInteger big) {
                if (BI_MIN_INT.compareTo(big) > 0
                        || BI_MAX_INT.compareTo(big) < 0) {
                    _reportOverflowInt();
                }
            } else if ((n instanceof Double) || (n instanceof Float)) {
                double d = n.doubleValue();
                // Need to check boundaries
                if (d < MIN_INT_D || d > MAX_INT_D) {
                    _reportOverflowInt();
                }
                return (int) d;
            } else if (n instanceof BigDecimal big) {
                if (BD_MIN_INT.compareTo(big) > 0
                    || BD_MAX_INT.compareTo(big) < 0) {
                    _reportOverflowInt();
                }
            } else {
                _throwInternal();
            }
            return n.intValue();
        }

        protected long _convertNumberToLong(Number n) throws InputCoercionException
        {
            if (n instanceof BigInteger big) {
                if (BI_MIN_LONG.compareTo(big) > 0
                        || BI_MAX_LONG.compareTo(big) < 0) {
                    _reportOverflowLong();
                }
            } else if ((n instanceof Double) || (n instanceof Float)) {
                double d = n.doubleValue();
                // Need to check boundaries
                if (d < MIN_LONG_D || d > MAX_LONG_D) {
                    _reportOverflowLong();
                }
                return (long) d;
            } else if (n instanceof BigDecimal big) {
                if (BD_MIN_LONG.compareTo(big) > 0
                    || BD_MAX_LONG.compareTo(big) < 0) {
                    _reportOverflowLong();
                }
            } else {
                _throwInternal();
            }
            return n.longValue();
        }

        /*
        /******************************************************************
        /* Public API, access to token information, other
        /******************************************************************
         */

        @Override
        public Object getEmbeddedObject()
        {
            if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
                return _currentObject();
            }
            return null;
        }

        @Override
        @SuppressWarnings("resource")
        public byte[] getBinaryValue(Base64Variant b64variant) throws JacksonException
        {
            // First: maybe we some special types?
            if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
                // Embedded byte array would work nicely...
                Object ob = _currentObject();
                if (ob instanceof byte[] byteArray) {
                    return byteArray;
                }
                // fall through to error case
            }
            if (_currToken != JsonToken.VALUE_STRING) {
                throw _constructReadException("Current token ("+_currToken+") not VALUE_STRING (or VALUE_EMBEDDED_OBJECT with byte[]), cannot access as binary");
            }
            final String str = getString();
            if (str == null) {
                return null;
            }
            ByteArrayBuilder builder = _byteBuilder;
            if (builder == null) {
                _byteBuilder = builder = new ByteArrayBuilder(100);
            } else {
                _byteBuilder.reset();
            }
            _decodeBase64(str, builder, b64variant);
            return builder.toByteArray();
        }

        @Override
        public int readBinaryValue(Base64Variant b64variant, OutputStream out)
            throws JacksonException
        {
            byte[] data = getBinaryValue(b64variant);
            if (data != null) {
                try {
                    out.write(data, 0, data.length);
                } catch (IOException e) {
                    throw _wrapIOFailure(e);
                }
                return data.length;
            }
            return 0;
        }

        /*
        /******************************************************************
        /* Public API, native ids
        /******************************************************************
         */

        @Override
        public boolean canReadObjectId() {
            return _hasNativeObjectIds;
        }

        @Override
        public boolean canReadTypeId() {
            return _hasNativeTypeIds;
        }

        @Override
        public Object getTypeId() {
            return _segment.findTypeId(_segmentPtr);
        }

        @Override
        public Object getObjectId() {
            return _segment.findObjectId(_segmentPtr);
        }

        /*
        /******************************************************************
        /* Internal methods
        /******************************************************************
         */

        protected final Object _currentObject() {
            return _segment.get(_segmentPtr);
        }

        @Override
        protected void _handleEOF() {
            _throwInternal();
        }
    }

    /**
     * Individual segment of TokenBuffer that can store up to 16 tokens
     * (limited by 4 bits per token type marker requirement).
     * Current implementation uses fixed length array; could alternatively
     * use 16 distinct elements and switch statement (slightly more efficient
     * storage, slightly slower access)
     */
    protected final static class Segment
    {
        public final static int TOKENS_PER_SEGMENT = 16;

        /**
         * Static array used for fast conversion between token markers and
         * matching {@link JsonToken} instances
         */
        private final static JsonToken[] TOKEN_TYPES_BY_INDEX;
        static {
            // ... here we know that there are <= 15 values in JsonToken enum
            TOKEN_TYPES_BY_INDEX = new JsonToken[16];
            JsonToken[] t = JsonToken.values();
            // and reserve entry 0 for "not available"
            System.arraycopy(t, 1, TOKEN_TYPES_BY_INDEX, 1, Math.min(15, t.length - 1));
        }

        // // // Linking

        protected Segment _next;

        // // // State

        /**
         * Bit field used to store types of buffered tokens; 4 bits per token.
         * Value 0 is reserved for "not in use"
         */
        protected long _tokenTypes;


        // Actual tokens

        protected final Object[] _tokens = new Object[TOKENS_PER_SEGMENT];

        /**
         * Lazily constructed Map for storing native type and object ids, if any
         */
        protected TreeMap<Integer,Object> _nativeIds;

        public Segment() { }

        // // // Accessors

        public JsonToken type(int index)
        {
            long l = _tokenTypes;
            if (index > 0) {
                l >>= (index << 2);
            }
            int ix = ((int) l) & 0xF;
            return TOKEN_TYPES_BY_INDEX[ix];
        }

        public int rawType(int index)
        {
            long l = _tokenTypes;
            if (index > 0) {
                l >>= (index << 2);
            }
            return ((int) l) & 0xF;
        }

        public Object get(int index) {
            return _tokens[index];
        }

        public Segment next() { return _next; }

        /**
         * Accessor for checking whether this segment may have native
         * type or object ids.
         */
        public boolean hasIds() {
            return _nativeIds != null;
        }

        // // // Mutators

        public Segment append(int index, JsonToken tokenType)
        {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType,
                Object objectId, Object typeId)
        {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, objectId, typeId);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, objectId, typeId);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType, Object value)
        {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, value);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, value);
            return _next;
        }

        public Segment append(int index, JsonToken tokenType, Object value,
                Object objectId, Object typeId)
        {
            if (index < TOKENS_PER_SEGMENT) {
                set(index, tokenType, value, objectId, typeId);
                return null;
            }
            _next = new Segment();
            _next.set(0, tokenType, value, objectId, typeId);
            return _next;
        }

        private void set(int index, JsonToken tokenType)
        {
            /* Assumption here is that there are no overwrites, just appends;
             * and so no masking is needed (nor explicit setting of null)
             */
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
        }

        private void set(int index, JsonToken tokenType,
                Object objectId, Object typeId)
        {
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
            assignNativeIds(index, objectId, typeId);
        }

        private void set(int index, JsonToken tokenType, Object value)
        {
            _tokens[index] = value;
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
        }

        private void set(int index, JsonToken tokenType, Object value,
                Object objectId, Object typeId)
        {
            _tokens[index] = value;
            long typeCode = tokenType.ordinal();
            if (index > 0) {
                typeCode <<= (index << 2);
            }
            _tokenTypes |= typeCode;
            assignNativeIds(index, objectId, typeId);
        }

        private final void assignNativeIds(int index, Object objectId, Object typeId)
        {
            if (_nativeIds == null) {
                _nativeIds = new TreeMap<Integer,Object>();
            }
            if (objectId != null) {
                _nativeIds.put(_objectIdIndex(index), objectId);
            }
            if (typeId != null) {
                _nativeIds.put(_typeIdIndex(index), typeId);
            }
        }

        public Object findObjectId(int index) {
            return (_nativeIds == null) ? null : _nativeIds.get(_objectIdIndex(index));
        }

        public Object findTypeId(int index) {
            return (_nativeIds == null) ? null : _nativeIds.get(_typeIdIndex(index));
        }

        private final int _typeIdIndex(int i) { return i+i; }
        private final int _objectIdIndex(int i) { return i+i+1; }
    }
}