BaseJsonNode.java

package tools.jackson.databind.node;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;

import tools.jackson.core.*;
import tools.jackson.databind.JacksonSerializable;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.exc.JsonNodeException;
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.util.ClassUtil;

/**
 * Abstract base class common to all standard {@link JsonNode}
 * implementations.
 * The main addition here is that we declare that sub-classes must
 * implement {@link JacksonSerializable}.
 * This simplifies object mapping aspects a bit, as no external serializers are needed.
 *<p>
 * Note that support for {@link java.io.Serializable} is added here and so all subtypes
 * are fully JDK serializable: but also note that serialization is as JSON and should
 * only be used for interoperability purposes where other approaches are not available.
 */
public abstract class BaseJsonNode
    extends JsonNode
    implements java.io.Serializable
{
    private static final long serialVersionUID = 3L;

    protected final static Optional<Boolean> OPT_FALSE = Optional.of(false);
    protected final static Optional<Boolean> OPT_TRUE = Optional.of(true);

    // Simplest way is by using a helper
    Object writeReplace() {
        return NodeSerialization.from(this);
    }

    protected BaseJsonNode() { }

    /*
    /**********************************************************************
    /* Defaulting for introspection
    /**********************************************************************
     */

    @Override
    public boolean isMissingNode() { return false; }

    @Override
    public boolean isEmbeddedValue() { return false; }

    /*
    /**********************************************************************
    /* Defaulting for number access
    /**********************************************************************
     */

    @Override
    public Number numberValue() {
        return _reportCoercionFail("numberValue()", Number.class, "value type not numeric");
    }

    @Override
    public short shortValue() {
        return _reportCoercionFail("shortValue()", Short.TYPE, "value type not numeric");
    }

    @Override
    public short shortValue(short defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<Short> shortValueOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public short asShort() {
        return _reportCoercionFail("asShort()", Short.TYPE, "value type not numeric");
    }

    @Override
    public short asShort(short defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<Short> asShortOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public int intValue() {
        return _reportCoercionFail("intValue()", Integer.TYPE, "value type not numeric");
    }

    @Override
    public int intValue(int defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalInt intValueOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalInt.empty();
    }

    @Override
    public int asInt() {
        return _reportCoercionFail("asInt()", Integer.TYPE, "value type not numeric");
    }

    @Override
    public int asInt(int defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalInt asIntOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalInt.empty();
    }

    @Override
    public long longValue() {
        return _reportCoercionFail("longValue()", Long.TYPE, "value type not numeric");
    }

    @Override
    public long longValue(long defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalLong longValueOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalLong.empty();
    }

    @Override
    public long asLong() {
        return _reportCoercionFail("asLong()", Long.TYPE, "value type not numeric");
    }

    @Override
    public long asLong(long defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalLong asLongOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalLong.empty();
    }

    @Override
    public BigInteger bigIntegerValue() {
        return _reportCoercionFail("bigIntegerValue()", BigInteger.class, "value type not numeric");
    }

    @Override
    public BigInteger bigIntegerValue(BigInteger defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<BigInteger> bigIntegerValueOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public BigInteger asBigInteger() {
        return _reportCoercionFail("asBigInteger()", BigInteger.class, "value type not numeric");
    }

    @Override
    public BigInteger asBigInteger(BigInteger defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<BigInteger> asBigIntegerOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public float floatValue() {
        return _reportCoercionFail("floatValue()", Float.TYPE, "value type not numeric");
    }

    @Override
    public float floatValue(float defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<Float> floatValueOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public float asFloat() {
        return _reportCoercionFail("asFloat()", Float.TYPE, "value type not numeric");
    }

    @Override
    public float asFloat(float defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<Float> asFloatOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public double doubleValue() {
        return _reportCoercionFail("doubleValue()", Double.TYPE, "value type not numeric");
    }

    @Override
    public double doubleValue(double defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalDouble doubleValueOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalDouble.empty();
    }

    @Override
    public double asDouble() {
        return _reportCoercionFail("asDouble()", Double.TYPE, "value type not numeric");
    }

    @Override
    public double asDouble(double defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public OptionalDouble asDoubleOpt() {
        // Overridden by NumericNode, for other types return default
        return OptionalDouble.empty();
    }

    @Override
    public BigDecimal decimalValue() {
        return _reportCoercionFail("decimalValue()", BigDecimal.class, "value type not numeric");
    }

    @Override
    public BigDecimal decimalValue(BigDecimal defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<BigDecimal> decimalValueOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    @Override
    public BigDecimal asDecimal() {
        return _reportCoercionFail("asDecimal()", BigDecimal.class,
                "value type not coercible to `BigDecimal`");
    }

    @Override
    public BigDecimal asDecimal(BigDecimal defaultValue) {
        // Overridden by NumericNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<BigDecimal> asDecimalOpt() {
        // Overridden by NumericNode, for other types return default
        return Optional.empty();
    }

    /*
    /**********************************************************************
    /* Defaulting for non-number scalar access
    /**********************************************************************
     */

    @Override
    public byte[] binaryValue() {
        return _reportCoercionFail("binaryValue()", Boolean.TYPE,
                "value type not binary (or convertible to binary via Base64-decoding)");
    }

    @Override
    public boolean booleanValue() {
        return _reportCoercionFail("booleanValue()", Boolean.TYPE,
                "value type not boolean");
    }

    @Override
    public boolean booleanValue(boolean defaultValue) {
        // Overridden by BooleanNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<Boolean> booleanValueOpt() {
        // Overridden by BooleanNode, for other types return default
        return Optional.empty();
    }

    @Override
    public boolean asBoolean() {
        Boolean b = _asBoolean();
        if (b == null) {
            return _reportCoercionFail("asBoolean()", Boolean.TYPE,
                    "value type not coercible to `boolean`");
        }
        return b;
    }

    @Override
    public boolean asBoolean(boolean defaultValue) {
        Boolean b = _asBoolean();
        if (b == null) {
            return defaultValue;
        }
        return b;
    }

    @Override
    public Optional<Boolean> asBooleanOpt() {
        Boolean b = _asBoolean();
        if (b == null) {
            return Optional.empty();
        }
        return b.booleanValue() ? OPT_TRUE : OPT_FALSE;
    }

    @Override
    public String stringValue() {
        return _reportCoercionFail("stringValue()", String.class,
                "value type not String");
    }

    @Override
    public String stringValue(String defaultValue) {
        // Overridden by StringNode, for other types return default
        return defaultValue;
    }

    @Override
    public Optional<String> stringValueOpt() {
        // Overridden by StringNode, for other types return default
        return Optional.empty();
    }

    @Override
    public String asString() {
        String str = _asString();
        if (str == null) {
            return _reportCoercionFail("asString()", String.class,
                    "value type not coercible to `String`");
        }
        return str;
    }

    @Override
    public String asString(String defaultValue) {
        String str = _asString();
        if (str == null) {
            return defaultValue;
        }
        return str;
    }

    @Override
    public Optional<String> asStringOpt() {
        return Optional.ofNullable(_asString());
    }

    /*
    /**********************************************************************
    /* Basic definitions for non-container types
    /**********************************************************************
     */

    @Override
    public final JsonNode findPath(String fieldName)
    {
        JsonNode value = findValue(fieldName);
        if (value == null) {
            return MissingNode.getInstance();
        }
        return value;
    }

    // Also, force (re)definition
    @Override
    public abstract int hashCode();

    /*
    /**********************************************************************
    /* Improved required-ness checks for standard JsonNode implementations
    /**********************************************************************
     */

    @Override
    public JsonNode required(String fieldName) {
        return _reportRequiredViolation("Node of type %s has no fields",
                ClassUtil.nameOf(getClass()));
    }

    @Override
    public JsonNode required(int index) {
        return _reportRequiredViolation("Node of type %s has no indexed values",
                ClassUtil.nameOf(getClass()));
    }

    /*
    /**********************************************************************
    /* Support for traversal-as-stream
    /**********************************************************************
     */

    @Override
    public JsonParser traverse(ObjectReadContext readCtxt) {
        return new TreeTraversingParser(this, readCtxt);
    }

    /**
     * Method that can be used for efficient type detection
     * when using stream abstraction for traversing nodes.
     * Will return the first {@link JsonToken} that equivalent
     * stream event would produce (for most nodes there is just
     * one token but for structured/container types multiple)
     */
    @Override
    public abstract JsonToken asToken();

    /**
     * Returns code that identifies type of underlying numeric
     * value, if (and only if) node is a number node.
     */
    @Override
    public JsonParser.NumberType numberType() {
        // most types non-numeric, so:
        return null;
    }

    /*
    /**********************************************************************
    /* With-Traversal
    /**********************************************************************
     */

    @Override
    public ObjectNode withObject(JsonPointer ptr,
            OverwriteMode overwriteMode, boolean preferIndex)
    {
        // Degenerate case of using with "empty" path; ok if ObjectNode
        if (ptr.matches()) {
            if (this instanceof ObjectNode objectNode) {
                return objectNode;
            }
            _reportWrongNodeType("Can only call `withObject()` with empty JSON Pointer on `ObjectNode`, not %s",
                    ClassUtil.nameOf(getClass()));
        }
        // Otherwise check recursively
        ObjectNode n = _withObject(ptr, ptr, overwriteMode, preferIndex);
        if (n == null) {
            _reportWrongNodeType("Cannot replace context node (of type %s) using `withObject()` with  JSON Pointer '%s'",
                    ClassUtil.nameOf(getClass()), ptr);
        }
        return n;
    }

    protected ObjectNode _withObject(JsonPointer origPtr,
            JsonPointer currentPtr,
            OverwriteMode overwriteMode, boolean preferIndex)
    {
        // Three-part logic:
        //
        // 1) If we are at the end of JSON Pointer; if so, return
        //    `this` if Object node, `null` if not (for caller to handle)
        // 2) If not at the end, if we can follow next segment, call recursively
        //    handle non-null (existing Object node, return)
        //    vs `null` (must replace; may not be allowed to)
        // 3) Cannot follow the segment? Try constructing, adding path
        //
        // But the default implementation assumes non-container behavior so
        // it'll simply return `null`
        return null;
    }

    protected void _withXxxVerifyReplace(JsonPointer origPtr,
            JsonPointer currentPtr,
            OverwriteMode overwriteMode, boolean preferIndex,
            JsonNode toReplace)
    {
        if (!_withXxxMayReplace(toReplace, overwriteMode)) {
            _reportWrongNodeType(
"Cannot replace `JsonNode` of type %s for property \"%s\" in JSON Pointer \"%s\" (mode `OverwriteMode.%s`)",
                ClassUtil.nameOf(toReplace.getClass()), currentPtr.getMatchingProperty(),
                origPtr, overwriteMode);
        }
    }

    protected boolean _withXxxMayReplace(JsonNode node, OverwriteMode overwriteMode) {
        switch (overwriteMode) {
        case NONE:
            return false;
        case NULLS:
            return node.isNull();
        case SCALARS:
            return !node.isContainer();
        default:
        case ALL:
            return true;
        }
    }

    @Override
    public ArrayNode withArray(JsonPointer ptr,
            OverwriteMode overwriteMode, boolean preferIndex)
    {
        // Degenerate case of using with "empty" path; ok if ArrayNode
        if (ptr.matches()) {
            if (this instanceof ArrayNode arrayNode) {
                return arrayNode;
            }
            _reportWrongNodeType("Can only call `withArray()` with empty JSON Pointer on `ArrayNode`, not %s",
                    ClassUtil.nameOf(getClass()));
        }
        // Otherwise check recursively
        ArrayNode n = _withArray(ptr, ptr, overwriteMode, preferIndex);
        if (n == null) {
            _reportWrongNodeType("Cannot replace context node (of type %s) using `withArray()` with  JSON Pointer '%s'",
                    ClassUtil.nameOf(getClass()), ptr);
        }
        return n;
    }

    protected ArrayNode _withArray(JsonPointer origPtr,
            JsonPointer currentPtr,
            OverwriteMode overwriteMode, boolean preferIndex)
    {
        // Similar logic to "_withObject()" but the default implementation
        // used for non-container behavior so it'll simply return `null`
        return null;
    }

    /*
    /**********************************************************************
    /* asXxx() helper methods for sub-classes to implement
    /**********************************************************************
     */

    /**
     * Method sub-classes should override if they can produce {@code boolean}
     * values via {@link #asBoolean()} -- if not, return {@code null} (in which
     * case appropriate error will be thrown or default value returned).
     *
     * @return Coerced value if possible; otherwise {@code null} to indicate this
     *     node cannot be coerced.
     */
    protected Boolean _asBoolean() {
        return null;
    }

    /**
     * Method sub-classes should override if they can produce {@code String}
     * values via {@link #asString()} -- if not, return {@code null} (in which
     * case appropriate error will be thrown or default value returned).
     *
     * @return Coerced value if possible; otherwise {@code null} to indicate this
     *     node cannot be coerced.
     */
    protected String _asString() {
        return null;
    }

    /*
    /**********************************************************************
    /* JacksonSerializable
    /**********************************************************************
     */

    /**
     * Method called to serialize node instances using given generator.
     */
    @Override
    public abstract void serialize(JsonGenerator jgen, SerializationContext ctxt)
        throws JacksonException;

    /**
     * Type information is needed, even if JsonNode instances are "plain" JSON,
     * since they may be mixed with other types.
     */
    @Override
    public abstract void serializeWithType(JsonGenerator jgen, SerializationContext ctxt,
            TypeSerializer typeSer)
        throws JacksonException;

    /*
    /**********************************************************************
    /* Standard method overrides
    /**********************************************************************
     */

    @Override
    public String toString() {
        return InternalNodeSerializer.toString(this);
    }

    @Override
    public String toPrettyString() {
        return InternalNodeSerializer.toPrettyString(this);
    }

    /*
    /**********************************************************************
    /* Helper method: error reporting
    /**********************************************************************
     */

    protected <T> T _reportCoercionFail(String method, Class<?> targetType,
            String message)
    {
        throw JsonNodeException.from(this, "'%s' method `%s` cannot convert value %s to %s: %s",
                getClass().getSimpleName(), method,
                _valueDesc(), ClassUtil.nameOf(targetType), message);
    }

    protected short _reportShortCoercionRangeFail(String method) {
        return _reportCoercionFail(method, Short.TYPE,
            "value not in 16-bit `short` range");
    }

    protected int _reportIntCoercionRangeFail(String method) {
        return _reportCoercionFail(method, Integer.TYPE,
            "value not in 32-bit `int` range");
    }

    protected long _reportLongCoercionRangeFail(String method) {
        return _reportCoercionFail(method, Long.TYPE,
            "value not in 64-bit `long` range");
    }

    protected float _reportFloatCoercionRangeFail(String method) {
        return _reportCoercionFail(method, Float.TYPE,
            "value not in 32-bit `float` range");
    }

    protected double _reportDoubleCoercionRangeFail(String method) {
        return _reportCoercionFail(method, Double.TYPE,
            "value not in 64-bit `double` range");
    }

    protected short _reportShortCoercionFractionFail(String method) {
        return _reportCoercionFail(method, Short.TYPE,
                "value has fractional part");
    }

    protected int _reportIntCoercionFractionFail(String method) {
        return _reportCoercionFail(method, Integer.TYPE,
                "value has fractional part");
    }

    protected long _reportLongCoercionFractionFail(String method) {
        return _reportCoercionFail(method, Long.TYPE,
                "value has fractional part");
    }

    protected BigInteger _reportBigIntegerCoercionFractionFail(String method) {
        return _reportCoercionFail(method, BigInteger.class,
                "value has fractional part");
    }

    protected int _reportIntCoercionNaNFail(String method) {
        return _reportCoercionFail(method, Integer.TYPE,
                "value non-Finite ('NaN')");
    }

    protected long _reportLongCoercionNaNFail(String method) {
        return _reportCoercionFail(method, Long.TYPE,
                "value non-Finite ('NaN')");
    }

    protected BigInteger _reportBigIntegerCoercionNaNFail(String method) {
        return _reportCoercionFail(method, BigInteger.class,
                "value non-Finite ('NaN')");
    }
    
    protected BigDecimal _reportBigDecimalCoercionNaNFail(String method) {
        return _reportCoercionFail(method, BigDecimal.class,
                "value non-Finite ('NaN')");
    }

    /**
     * Helper method that throws {@link JsonNodeException} as a result of
     * this node being of wrong type
     */
    protected <T> T _reportWrongNodeType(String msgTemplate, Object...args) {
        throw JsonNodeException.from(this, String.format(msgTemplate, args));
    }

    /*
    /**********************************************************************
    /* Other helper methods for subtypes
    /**********************************************************************
     */

    protected JsonPointer _jsonPointerIfValid(String exprOrProperty) {
        if (exprOrProperty.isEmpty() || exprOrProperty.charAt(0) == '/') {
            return JsonPointer.compile(exprOrProperty);
        }
        return null;
    }

    /**
     * Method for implementation classes to return a short description of contained
     * value, to be used in error messages.
     */
    protected abstract String _valueDesc();
}