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

oracle.kv.impl.api.table.JsonDefImpl Maven / Gradle / Ivy

/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.api.table;

import static oracle.kv.impl.api.table.TableJsonUtils.jsonParserGetDecimalValue;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;

import oracle.kv.impl.util.SerialVersion;
import oracle.kv.table.BooleanValue;
import oracle.kv.table.DoubleValue;
import oracle.kv.table.FieldDef;
import oracle.kv.table.FieldValue;
import oracle.kv.table.FieldValueFactory;
import oracle.kv.table.FloatValue;
import oracle.kv.table.IntegerValue;
import oracle.kv.table.JsonDef;
import oracle.kv.table.LongValue;
import oracle.kv.table.NumberValue;
import oracle.kv.table.StringValue;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;

/**
 * JsonDefImpl implements the JsonDef interface.
 */
public class JsonDefImpl extends FieldDefImpl implements JsonDef {

    private static final long serialVersionUID = 1L;

    JsonDefImpl(String description) {

        super(FieldDef.Type.JSON, description);
    }

    JsonDefImpl() {
        this((String)null);
    }

    /*
     * Public api methods from Object and FieldDef
     */

    @Override
    public JsonDefImpl clone() {
        return FieldDefImpl.jsonDef;
    }

    @Override
    public boolean isComplex() {
        return true;
    }

    @Override
    public JsonDef asJson() {
        return this;
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof JsonDefImpl);
    }

    @Override
    public boolean isValidIndexField() {
        return true;
    }

    /*
     * FieldDefImpl internal api methods
     */

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

    @Override
    public boolean isSubtype(FieldDefImpl superType) {

        return (superType.isJson() || superType.isAny());
    }

    @Override
    public short getRequiredSerialVersion() {
        return SerialVersion.QUERY_VERSION_2;
    }

    @Override
    public ArrayValueImpl createArray() {
        return new ArrayValueImpl(FieldDefImpl.arrayJsonDef);
    }

    @Override
    public BooleanValue createBoolean(boolean value) {
        return booleanDef.createBoolean(value);
    }

    @Override
    public DoubleValue createDouble(double value) {
        return doubleDef.createDouble(value);
    }

    @Override
    public FloatValue createFloat(float value) {
        return floatDef.createFloat(value);
    }

    @Override
    public IntegerValue createInteger(int value) {
        return integerDef.createInteger(value);
    }

    @Override
    public LongValue createLong(long value) {
        return longDef.createLong(value);
    }

    @Override
    public NumberValue createNumber(int value) {
        return numberDef.createNumber(value);
    }

    @Override
    public NumberValue createNumber(long value) {
        return numberDef.createNumber(value);
    }

    @Override
    public NumberValue createNumber(float value) {
        return numberDef.createNumber(value);
    }

    @Override
    public NumberValue createNumber(double value) {
        return numberDef.createNumber(value);
    }

    @Override
    public NumberValue createNumber(BigDecimal decimal) {
        return numberDef.createNumber(decimal);
    }

    @Override
    public MapValueImpl createMap() {
        return new MapValueImpl(FieldDefImpl.mapJsonDef);
    }

    @Override
    public StringValue createString(String value) {
        return stringDef.createString(value);
    }

    @Override
    public FieldValue createJsonNull() {
        return NullJsonValueImpl.getInstance();
    }

    /**
     * A common method to validate JSON-compatible values.  This includes all
     * atomic types except (FIXED_)BINARY and ENUM. It excludes RECORD, but
     * includes ARRAY and MAP IFF they are instances of JsonArrayValueImpl or
     * JsonMapValueImpl.
     */
    static void validateJsonType(FieldValue value) {
        FieldDef.Type type = value.getType();

        if (type == FieldDef.Type.BINARY ||
            type == FieldDef.Type.FIXED_BINARY ||
            type == FieldDef.Type.ENUM ||
            type == FieldDef.Type.RECORD) {
            throw new IllegalArgumentException
                ("Type is not supported in JSON: " + type);
        }

        if (type == FieldDef.Type.MAP || type ==  FieldDef.Type.ARRAY) {

            FieldDefImpl elemDef = (FieldDefImpl)value.getDefinition();

            if (!elemDef.isSubtype(FieldDefImpl.jsonDef)) {
                throw new IllegalArgumentException(
                    "Type is not supported in JSON: " + type);
            }
        }
    }

    /**
     * Construct a FieldValue based on arbitrary JSON from the incoming JSON
     * The top-level object may be any valid JSON:
     * 1. an object
     * 2. an array
     * 3. a scalar, including the JSON null value
     *
     * This code creates FieldValue types based on the type inferred from the
     * parser.
     */
    public static FieldValue createFromJson(JsonParser jp, boolean getNext) {

        try {
            JsonToken token = (getNext ? jp.nextToken() : jp.getCurrentToken());
            if (token == null) {
                throw new IllegalStateException(
                    "createFromJson called with null token");
            }

            switch (token) {
            case VALUE_STRING:

                return FieldDefImpl.stringDef.createString(jp.getText());

            case VALUE_NUMBER_INT:
            case VALUE_NUMBER_FLOAT:

                /*
                 * Handle all numeric types here. 4 types are supported (3
                 * until 4.4):
                 *  INTEGER
                 *  LONG (long and integer)
                 *  DOUBLE (double and float)
                 *  NUMBER (anything that won't fit into the two above)
                 */
                JsonParser.NumberType numberType = jp.getNumberType();

                switch (numberType) {
                case BIG_INTEGER:
                case BIG_DECIMAL:
                    return FieldDefImpl.numberDef.createNumber(
                        jsonParserGetDecimalValue(jp));
                case INT:
                    return FieldDefImpl.integerDef.createInteger(jp.getIntValue());
                case LONG:
                    return FieldDefImpl.longDef.createLong(jp.getLongValue());

                /* Never store FLOAT, use DOUBLE */
                case FLOAT:
                case DOUBLE:
                    double dbl = jp.getDoubleValue();
                    /*
                     * Jackson parse a floating-point numbers to a double:
                     *  - if the Math.abs(value) > Double.MAX_VALUE, return
                     *    Infinity.
                     *  - if the abs(value) is smaller than Double.MIN_VALUE,
                     *    then return Zero.
                     *
                     * So check if the double value is Infinity or Zero, try to
                     * read it as BigDecimal value. The real 0.0 is a special
                     * value, it is treated as valid double value.
                     */
                    if (Double.isInfinite(dbl) || dbl == 0.0) {
                        BigDecimal bd = jsonParserGetDecimalValue(jp);
                        if (bd.compareTo(BigDecimal.ZERO) != 0) {
                            return FieldDefImpl.numberDef.createNumber(bd);
                        }
                    }
                    return FieldDefImpl.doubleDef.createDouble(dbl);
                }
                throw new IllegalStateException("Unexpected numeric type: " +
                                                numberType);
            case VALUE_TRUE:

                return FieldDefImpl.booleanDef.createBoolean(true);

            case VALUE_FALSE:

                return FieldDefImpl.booleanDef.createBoolean(false);

            case VALUE_NULL:

                return NullJsonValueImpl.getInstance();

            case START_OBJECT:

                return parseObject(jp);

            case START_ARRAY:

                return parseArray(jp);

            case FIELD_NAME:
            case END_OBJECT:
            case END_ARRAY:
            default:
                throw new IllegalStateException(
                    "Unexpected token while parsing JSON: " + token);
            }
        } catch (IOException ioe) {
            throw new IllegalArgumentException(
                "Failed to parse JSON input: " + ioe.getMessage());
        }
    }

    /**
     * Creates a FieldValue from a Reader. This helper method is shared
     * among the complex types that handle JSON and factors out IOException
     * handling.
     */
    static FieldValue createFromReader(Reader jsonReader) {
        try {
            return FieldValueFactory.createValueFromJson(jsonReader);
        } catch (IOException ioe) {
            throw new IllegalArgumentException("Unable to parse JSON " +
                                               "input: " + ioe.getMessage(),
                                               ioe);
        }
    }

    /**
     * Creates a JSON map from the parsed JSON object.
     */
    private static FieldValueImpl parseObject(JsonParser jp)
        throws IOException {

        MapValueImpl map = FieldDefImpl.jsonDef.createMap();

        JsonToken token;
        while ((token = jp.nextToken()) != JsonToken.END_OBJECT) {
            String fieldName = jp.getCurrentName();
            if (token == null || fieldName == null) {
                throw new IllegalArgumentException(
                    "null token or field name parsing JSON object");
            }

            /* true tells the method to fetch the next token */
            FieldValue field = createFromJson(jp, true);
            if (field.isJsonNull()) {
                map.put(fieldName, NullJsonValueImpl.getInstance());
            } else {
                map.put(fieldName, field);
            }
        }
        return map;
    }

    /**
     * Creates a JSON array from the parsed JSON array by adding
     */
    private static FieldValueImpl parseArray(JsonParser jp)
        throws IOException {

        ArrayValueImpl array = FieldDefImpl.jsonDef.createArray();

        JsonToken token;
        while ((token = jp.nextToken()) != JsonToken.END_ARRAY) {
            if (token == null) {
                throw new IllegalStateException(
                    "null token while parsing JSON array");
            }

            /* false means don't get the next token, it's been fetched */
            array.add(createFromJson(jp, false));
        }
        return array;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy