org.ojai.json.impl.JsonStreamDocumentReader.java Source code

Java tutorial

Introduction

Here is the source code for org.ojai.json.impl.JsonStreamDocumentReader.java

Source

/**
 * Copyright (c) 2015 MapR, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ojai.json.impl;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import org.ojai.DocumentReader;
import org.ojai.Value.Type;
import org.ojai.annotation.API;
import org.ojai.exceptions.DecodingException;
import org.ojai.exceptions.TypeException;
import org.ojai.types.ODate;
import org.ojai.types.OInterval;
import org.ojai.types.OTime;
import org.ojai.types.OTimestamp;
import org.ojai.util.Values;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.Stack;

import static org.ojai.util.Constants.MILLISECONDSPERDAY;
import static org.ojai.util.Types.*;

@API.Internal
public class JsonStreamDocumentReader implements DocumentReader {

    private final JsonDocumentStream documentStream;
    private int mapLevel;
    private boolean eor;

    private long currentLongValue = 0;
    private Object currentObjValue = null;
    private double currentDoubleValue = 0;
    private EventType currentEvent;

    private String fieldName;
    private LinkedList<JsonToken> cachedTokens;
    private Stack<ContainerContext> containerStack;
    private ContainerContext currentContainer;
    private boolean isExtended;

    JsonStreamDocumentReader(JsonDocumentStream stream) {
        containerStack = new Stack<ContainerContext>();
        cachedTokens = new LinkedList<JsonToken>();
        documentStream = stream;
        mapLevel = 0;
        eor = false;
    }

    /*
     * Get next token which should be a field name. If it's one of the extended
     * type, find the type and the value. Verify that the map ends with a single
     * field and consume the following END_OBJECT token.
     */
    private EventType parseMap() {
        // we just entered a Map, look ahead to see if
        // the field name matches an extended type
        JsonToken nextToken = peekToken();
        if (nextToken == JsonToken.END_OBJECT) {
            return EventType.START_MAP; // empty Map
        } else if (nextToken != JsonToken.FIELD_NAME) {
            throw new DecodingException("Encountered " + nextToken + " while looking for a field name.");
        }

        String field_name = getCurrentName();
        if (field_name.startsWith("$")) {
            // determine extended type
            isExtended = true;
            switch (field_name) {
            case TAG_LONG:
                setCurrentEventType(EventType.LONG);
                break;
            case TAG_DECIMAL:
                setCurrentEventType(EventType.DECIMAL);
                break;
            case TAG_DATE:
                setCurrentEventType(EventType.DATE);
                break;
            case TAG_TIME:
                setCurrentEventType(EventType.TIME);
                break;
            case TAG_TIMESTAMP:
                setCurrentEventType(EventType.TIMESTAMP);
                break;
            case TAG_INTERVAL:
                setCurrentEventType(EventType.INTERVAL);
                break;
            case TAG_BINARY:
                setCurrentEventType(EventType.BINARY);
                break;
            default:
                // regular map, return without consuming the field name.
                isExtended = false;
                return EventType.START_MAP;
            }

            // At this point, we have determined that the current map is
            // one of the extended type, so let's consume the field name
            nextToken();
            // and move forward so that the parser is at field value,
            nextToken = nextToken();
            // then cache the current value
            cacheCurrentValue();
            // finally, consume the END_OBJECT tag
            nextToken = nextToken();
            if (nextToken != JsonToken.END_OBJECT) {
                throw new DecodingException("Encountered " + nextToken + " while looking for end object token.");
            }
            return currentEvent;
        } else {
            return EventType.START_MAP;
        }
    }

    /**
     * Reads and caches the current Value from the stream.
     * The parser must be positioned at the value and the
     * current {@link EventType} must be set by calling
     * {@link #setCurrentEventType(EventType)}.
     */
    protected void cacheCurrentValue() {
        try {
            switch (currentEvent) {
            case BOOLEAN:
                currentObjValue = isEventBoolean() ? getParser().getBooleanValue()
                        : Boolean.valueOf(getValueAsString());
                break;
            case STRING:
                currentObjValue = getParser().getText();
                break;
            case BYTE:
                currentLongValue = getValueAsLong() & 0xff;
                break;
            case SHORT:
                currentLongValue = getValueAsLong() & 0xffff;
                break;
            case INT:
                currentLongValue = getValueAsLong() & 0xffffffff;
                break;
            case LONG:
                currentLongValue = getValueAsLong();
                break;
            case FLOAT:
            case DOUBLE:
                currentDoubleValue = getValueAsDouble();
                break;
            case DECIMAL:
                currentObjValue = Values.parseBigDecimal(getParser().getText());
                break;
            case DATE:
                currentObjValue = ODate.parse(getParser().getText());
                break;
            case TIME:
                currentObjValue = OTime.parse(getParser().getText());
                break;
            case TIMESTAMP:
                currentObjValue = OTimestamp.parse(getParser().getText());
                break;
            case INTERVAL:
                currentLongValue = getValueAsLong();
                break;
            case BINARY:
                currentObjValue = ByteBuffer.wrap(getParser().getBinaryValue());
                break;
            default:
                // ARRAY, MAP and NULL need not be cached
                break;
            }
        } catch (IOException ie) {
            throw new DecodingException(ie);
        }
    }

    private void checkEventType(EventType eventType) throws TypeException {
        if (currentEvent != eventType) {
            throw new TypeException(
                    String.format("Event type mismatch, expected %s, got %s", eventType, currentEvent));
        }
    }

    private void checkNumericEventType() throws TypeException {
        switch (currentEvent) {
        case BYTE:
        case SHORT:
        case INT:
        case LONG:
        case FLOAT:
        case DOUBLE:
            return;
        default:
            break;
        }
        throw new TypeException("Event type mismatch. Expected numeric type, found " + currentEvent);
    }

    private JsonParser getParser() {
        return documentStream.getParser();
    }

    private boolean isEventNumeric() {
        return getParser().getCurrentToken().isNumeric();
    }

    private boolean isEventBoolean() {
        return getParser().getCurrentToken().isBoolean();
    }

    private long getValueAsLong() throws NumberFormatException, IOException {
        return isEventNumeric() ? getParser().getLongValue() : Long.valueOf(getValueAsString());
    }

    private double getValueAsDouble() throws NumberFormatException, IOException {
        return isEventNumeric() ? getParser().getDoubleValue() : Double.valueOf(getValueAsString());
    }

    protected String getValueAsString() {
        try {
            return getParser().getText();
        } catch (IOException e) {
            throw new DecodingException(e);
        }
    }

    /**
     * @return {@code true} if the {@code DocumentReader} has read past the end
     *         of the current document or end of the underlying stream has been
     *         reached.
     */
    protected boolean eor() {
        return eor || !hasMoreTokens();
    }

    @Override
    public EventType next() {
        isExtended = false;
        if (eor()) {
            return null;
        }

        JsonToken currentToken = nextToken();
        if (currentToken == JsonToken.FIELD_NAME) {
            fieldName = getCurrentName();
            currentToken = nextToken();
        }
        updateCurrentContainer();

        switch (currentToken) {
        case START_OBJECT:
            setCurrentEventType(parseMap());
            if (currentEvent == EventType.START_MAP) {
                containerStack.push(new ContainerContext(Type.MAP, fieldName));
            }
            break;
        case END_OBJECT:
            setCurrentEventType(EventType.END_MAP);
            ContainerContext lastContainer = containerStack.pop();
            if (lastContainer.getType() == Type.MAP) {
                fieldName = lastContainer.getFieldName();
            }
            updateCurrentContainer();
            break;
        case START_ARRAY:
            setCurrentEventType(EventType.START_ARRAY);
            if (!inMap()) {
                currentContainer.incrementIndex();
            }
            containerStack.push(new ContainerContext(Type.ARRAY));
            break;
        case END_ARRAY:
            setCurrentEventType(EventType.END_ARRAY);
            containerStack.pop();
            updateCurrentContainer();
            break;
        case VALUE_NULL:
            setCurrentEventType(EventType.NULL).cacheCurrentValue();
            break;
        case VALUE_TRUE:
        case VALUE_FALSE:
            setCurrentEventType(EventType.BOOLEAN).cacheCurrentValue();
            break;
        case VALUE_STRING:
            setCurrentEventType(EventType.STRING).cacheCurrentValue();
            break;
        case VALUE_NUMBER_INT:
        case VALUE_NUMBER_FLOAT:
            setCurrentEventType(EventType.DOUBLE).cacheCurrentValue();
            break;
        default:
            throw new DecodingException("Encountered unexpected token of type: " + currentToken);
        }

        if (!inMap() && currentEvent != EventType.END_MAP && currentEvent != EventType.START_ARRAY
                && currentEvent != EventType.END_ARRAY) {
            // if traversing an array, increment the index
            currentContainer.incrementIndex();
        }

        if (currentEvent == EventType.START_MAP) {
            mapLevel++;
        } else if (currentEvent == EventType.END_MAP) {
            mapLevel--;
        }
        if (mapLevel == 0) {
            eor = true;
        }

        return currentEvent;
    }

    /**
     * Forward the stream to end of the current document
     */
    void readFully() {
        if (eor) {
            return;
        }
        /**
         * FIXME: Currently this method just consume the remaining tokens from
         *        the parser stream and discard them. Instead we need to cache
         *        the token and return them in next() and getXXXX() calls.
         */
        JsonToken token;
        while ((token = nextToken()) != null) {
            switch (token) {
            case START_OBJECT:
                mapLevel++;
                break;
            case END_OBJECT:
                mapLevel--;
                break;
            default:
            }

            if (mapLevel == 0) {
                break;
            }
        }
        eor = true;
    }

    @Override
    public boolean inMap() {
        return currentContainer == null || currentContainer.getType() == Type.MAP;
    }

    @Override
    public String getFieldName() {
        if (!inMap()) {
            throw new IllegalStateException("Not traversing a map!");
        }
        return fieldName;
    }

    @Override
    public int getArrayIndex() {
        if (inMap()) {
            throw new IllegalStateException("Not traversing an array!");
        }
        return currentContainer.getIndex();
    }

    @Override
    public byte getByte() {
        checkNumericEventType();
        return (byte) currentLongValue;
    }

    @Override
    public short getShort() {
        checkNumericEventType();
        return (short) currentLongValue;
    }

    @Override
    public int getInt() {
        checkNumericEventType();
        return (int) currentLongValue;
    }

    @Override
    public long getLong() {
        checkEventType(EventType.LONG);
        return currentLongValue;
    }

    @Override
    public float getFloat() {
        checkEventType(EventType.FLOAT);
        return (float) currentDoubleValue;
    }

    @Override
    public double getDouble() {
        checkEventType(EventType.DOUBLE);
        return currentDoubleValue;
    }

    @Override
    public BigDecimal getDecimal() {
        checkEventType(EventType.DECIMAL);
        return (BigDecimal) currentObjValue;
    }

    @Override
    public int getDecimalPrecision() {
        BigDecimal decimal = getDecimal();
        if (decimal != null) {
            return decimal.precision();
        }
        return 0;
    }

    @Override
    public int getDecimalScale() {
        BigDecimal decimal = getDecimal();
        if (decimal != null) {
            return decimal.scale();
        }
        return 0;
    }

    @Override
    public int getDecimalValueAsInt() {
        BigDecimal decimal = getDecimal();
        if (decimal != null) {
            return decimal.intValueExact();
        }
        return 0;
    }

    @Override
    public long getDecimalValueAsLong() {
        BigDecimal decimal = getDecimal();
        if (decimal != null) {
            return decimal.longValueExact();
        }
        return 0;
    }

    @Override
    public ByteBuffer getDecimalValueAsBytes() {
        BigDecimal decimal = getDecimal();
        if (decimal != null) {
            BigInteger decimalInteger = decimal.unscaledValue();
            byte[] bytearray = decimalInteger.toByteArray();
            return ByteBuffer.wrap(bytearray);
        }
        return null;
    }

    @Override
    public boolean getBoolean() {
        checkEventType(EventType.BOOLEAN);
        return (boolean) currentObjValue;
    }

    @Override
    public String getString() {
        checkEventType(EventType.STRING);
        return (String) currentObjValue;
    }

    @Override
    public long getTimestampLong() {
        checkEventType(EventType.TIMESTAMP);
        return ((OTimestamp) currentObjValue).getMillis();
    }

    @Override
    public OTimestamp getTimestamp() {
        checkEventType(EventType.TIMESTAMP);
        return (OTimestamp) currentObjValue;
    }

    @Override
    public int getDateInt() {
        checkEventType(EventType.DATE);
        return ((ODate) currentObjValue).toDaysSinceEpoch();
    }

    @Override
    public ODate getDate() {
        checkEventType(EventType.DATE);
        return (ODate) currentObjValue;
    }

    @Override
    public int getTimeInt() {
        checkEventType(EventType.TIME);
        return ((OTime) currentObjValue).toTimeInMillis();
    }

    @Override
    public OTime getTime() {
        checkEventType(EventType.TIME);
        return (OTime) currentObjValue;
    }

    @Override
    public OInterval getInterval() {
        checkEventType(EventType.INTERVAL);
        return new OInterval(currentLongValue);
    }

    @Override
    public int getIntervalDays() {
        checkEventType(EventType.INTERVAL);
        return (int) (currentLongValue / MILLISECONDSPERDAY);
    }

    @Override
    public long getIntervalMillis() {
        checkEventType(EventType.INTERVAL);
        return currentLongValue;
    }

    @Override
    public ByteBuffer getBinary() {
        checkEventType(EventType.BINARY);
        return (ByteBuffer) currentObjValue;
    }

    private void updateCurrentContainer() {
        currentContainer = containerStack.isEmpty() ? null : containerStack.peek();
    }

    protected JsonToken peekToken() {
        if (hasMoreTokens()) {
            return cachedTokens.peek();
        }
        throw new DecodingException("No more Json tokens.");
    }

    protected JsonToken nextToken() {
        if (hasMoreTokens()) {
            return cachedTokens.remove();
        }
        throw new DecodingException("No more Json tokens.");
    }

    protected boolean hasMoreTokens() {
        try {
            if (!cachedTokens.isEmpty())
                return true;
            JsonToken token = getParser().nextToken();
            if (token == null)
                return false;
            cachedTokens.add(token);
            return true;
        } catch (IOException e) {
            throw new DecodingException(e);
        }
    }

    protected String getCurrentName() {
        try {
            return getParser().getCurrentName();
        } catch (IOException ie) {
            throw new DecodingException(ie);
        }
    }

    protected Object getCurrentObj() {
        return currentObjValue;
    }

    protected JsonStreamDocumentReader setArrayIndex(int index) {
        if (inMap()) {
            throw new IllegalStateException("Not traversing an array!");
        }
        currentContainer.setIndex(index);
        return this;
    }

    protected JsonStreamDocumentReader setFieldName(String fieldName) {
        if (!inMap()) {
            throw new IllegalStateException("Not traversing a map!");
        }
        this.fieldName = fieldName;
        return this;
    }

    protected JsonStreamDocumentReader setCurrentObj(Object obj) {
        this.currentObjValue = obj;
        return this;
    }

    protected EventType getCurrentEventType() {
        return currentEvent;
    }

    protected JsonStreamDocumentReader setCurrentEventType(EventType eventType) {
        this.currentEvent = eventType;
        return this;
    }

    protected long getCurrentLongValue() {
        return currentLongValue;
    }

    protected JsonStreamDocumentReader setCurrentLongValue(long value) {
        this.currentLongValue = value;
        return this;
    }

    protected double getCurrentDoubleValue() {
        return currentDoubleValue;
    }

    protected JsonStreamDocumentReader setCurrentDoubleValue(double value) {
        this.currentDoubleValue = value;
        return this;
    }

    protected boolean isExtended() {
        return isExtended;
    }

}