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

Java tutorial

Introduction

Here is the source code for org.ojai.json.impl.JsonDocumentBuilder.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.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.google.common.base.Preconditions;
import org.ojai.Document;
import org.ojai.DocumentBuilder;
import org.ojai.DocumentStream;
import org.ojai.Value;
import org.ojai.Value.Type;
import org.ojai.annotation.API;
import org.ojai.exceptions.EncodingException;
import org.ojai.json.Json;
import org.ojai.json.JsonOptions;
import org.ojai.types.ODate;
import org.ojai.types.OInterval;
import org.ojai.types.OTime;
import org.ojai.types.OTimestamp;
import org.ojai.util.Decimals;
import org.ojai.util.Types;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;

import static org.ojai.Value.Type.ARRAY;
import static org.ojai.Value.Type.MAP;
import static org.ojai.util.Constants.MILLISECONDSPERDAY;

@API.Internal
public class JsonDocumentBuilder implements DocumentBuilder {

    private JsonGenerator jsonGenerator;
    private ByteArrayWriterOutputStream b;
    private String cachedJson;
    private Stack<ContainerContext> ctxStack;
    private ContainerContext currentContext;
    private JsonOptions jsonOptions;
    private boolean checkContext;

    public JsonDocumentBuilder() {
        b = new ByteArrayWriterOutputStream();
        initJsonGenerator(b);
    }

    protected JsonDocumentBuilder(OutputStream out) {
        initJsonGenerator(out);
    }

    private static final DefaultPrettyPrinter PRETTY_PRINTER;
    static {
        PRETTY_PRINTER = new DefaultPrettyPrinter();
        PRETTY_PRINTER.indentObjectsWith(// standardize on Unix line terminator
                DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withLinefeed("\n"));
    }

    public JsonDocumentBuilder setJsonOptions(JsonOptions options) {
        if (jsonOptions == null || jsonOptions.isPretty() != options.isPretty()) {
            if (options.isPretty()) {
                jsonGenerator.setPrettyPrinter(PRETTY_PRINTER);
            } else {
                jsonGenerator.setPrettyPrinter(null);
            }
        }
        jsonOptions = options;
        return this;
    }

    private void initJsonGenerator(OutputStream out) {
        try {
            JsonFactory jFactory = new JsonFactory();
            jsonGenerator = jFactory.createGenerator(out, JsonEncoding.UTF8);
            ctxStack = new Stack<ContainerContext>();
            currentContext = ContainerContext.NULL;
            checkContext = true;
            setJsonOptions(JsonOptions.WITH_TAGS);
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    private RuntimeException transformIOException(IOException ie) {
        return ie instanceof JsonGenerationException ? new IllegalStateException(ie) : new EncodingException(ie);
    }

    private void preparePut() {
        checkContext(MAP);
    }

    private void prepareAdd() {
        checkContext(ARRAY);
        if (currentContext.getType() == ARRAY) {
            currentContext.incrementIndex();
        }
    }

    private void checkContext(Type type) {
        Preconditions.checkState((!checkContext || currentContext.getType() == type),
                "Mismatch in writeContext. Expected %s but found %s", type, currentContext.getType());
    }

    @Override
    public JsonDocumentBuilder put(String field, boolean value) {
        try {
            preparePut();
            jsonGenerator.writeBooleanField(field, value);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, String value) {
        try {
            preparePut();
            jsonGenerator.writeStringField(field, value);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, byte value) {
        return putLongWithTag(field, Types.TAG_BYTE, value);
    }

    @Override
    public JsonDocumentBuilder put(String field, short value) {
        return putLongWithTag(field, Types.TAG_SHORT, value);
    }

    @Override
    public JsonDocumentBuilder put(String field, int value) {
        return putLongWithTag(field, Types.TAG_INT, value);
    }

    @Override
    public JsonDocumentBuilder put(String field, long value) {
        return putLongWithTag(field, Types.TAG_LONG, value);
    }

    @Override
    public JsonDocumentBuilder put(String field, float value) {
        return put(field, (double) value);
    }

    @Override
    public JsonDocumentBuilder put(String field, double value) {
        try {
            preparePut();
            if (isWholeNumberInLongRange(value)) {
                jsonGenerator.writeNumberField(field, (long) value);
            } else {
                jsonGenerator.writeNumberField(field, value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, BigDecimal value) {
        try {
            preparePut();
            if (jsonOptions.isWithTags()) {
                putNewMap(field);
                jsonGenerator.writeStringField(Types.TAG_DECIMAL, value.toString());
                endMap();
            } else {
                jsonGenerator.writeNumberField(field, value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder putDecimal(String field, long decimalValue) {
        return put(field, new BigDecimal(decimalValue));
    }

    @Override
    public JsonDocumentBuilder putDecimal(String field, double decimalValue) {
        return put(field, new BigDecimal(decimalValue));
    }

    @Override
    public JsonDocumentBuilder putDecimal(String field, int unscaledValue, int scale) {
        return put(field, Decimals.convertIntToDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder putDecimal(String field, long unscaledValue, int scale) {
        return put(field, Decimals.convertLongToDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder putDecimal(String field, byte[] unscaledValue, int scale) {
        return put(field, Decimals.convertByteToBigDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder put(String field, byte[] value) {
        try {
            preparePut();
            if (jsonOptions.isWithTags()) {
                putNewMap(field);
                jsonGenerator.writeBinaryField(Types.TAG_BINARY, value);
                endMap();
            } else {
                jsonGenerator.writeBinaryField(field, value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, byte[] value, int offset, int length) {
        try {
            preparePut();
            if (jsonOptions.isWithTags()) {
                putNewMap(field);
                jsonGenerator.writeFieldName(Types.TAG_BINARY);
                jsonGenerator.writeBinary(value, offset, length);
                endMap();
            } else {
                jsonGenerator.writeFieldName(field);
                jsonGenerator.writeBinary(value, offset, length);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, ByteBuffer value) {
        byte[] bytes = new byte[value.remaining()];
        value.slice().get(bytes);
        return put(field, bytes);
    }

    private JsonDocumentBuilder putLongWithTag(String fieldname, String fieldTag, long value) {
        try {
            preparePut();
            if (jsonOptions.isWithTags()) {
                putNewMap(fieldname);
                jsonGenerator.writeNumberField(fieldTag, value);
                endMap();
            } else {
                jsonGenerator.writeNumberField(fieldname, value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    private JsonDocumentBuilder putStringWithTag(String fieldname, String fieldTag, String value) {
        try {
            preparePut();
            if (jsonOptions.isWithTags()) {
                putNewMap(fieldname);
                jsonGenerator.writeStringField(fieldTag, value);
                endMap();
            } else {
                jsonGenerator.writeStringField(fieldname, value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, ODate value) {
        return putStringWithTag(field, Types.TAG_DATE, value.toDateStr());
    }

    @Override
    public JsonDocumentBuilder putDate(String field, int days) {
        return putStringWithTag(field, Types.TAG_DATE, ODate.fromDaysSinceEpoch(days).toDateStr());
    }

    @Override
    public JsonDocumentBuilder put(String field, OTime value) {
        return putStringWithTag(field, Types.TAG_TIME, value.toTimeStr());
    }

    @Override
    public JsonDocumentBuilder putTime(String field, int millis) {
        if (millis > MILLISECONDSPERDAY) {
            throw new IllegalArgumentException(
                    "Long value exceeds " + Long.toString(MILLISECONDSPERDAY) + " " + Long.toString(millis));
        }
        return putStringWithTag(field, Types.TAG_TIME, OTime.fromMillisOfDay(millis).toTimeStr());
    }

    @Override
    public JsonDocumentBuilder put(String field, OTimestamp value) {
        return putStringWithTag(field, Types.TAG_TIMESTAMP, value.toUTCString());
    }

    @Override
    public JsonDocumentBuilder putTimestamp(String field, long timeMillis) {
        return putStringWithTag(field, Types.TAG_TIMESTAMP, new OTimestamp(timeMillis).toUTCString());
    }

    @Override
    public JsonDocumentBuilder put(String field, OInterval value) {
        return putLongWithTag(field, Types.TAG_INTERVAL, value.getTimeInMillis());
    }

    @Override
    public JsonDocumentBuilder putInterval(String field, long durationInMs) {
        return putLongWithTag(field, Types.TAG_INTERVAL, durationInMs);
    }

    @Override
    public JsonDocumentBuilder putInterval(String field, int months, int days, int milliseconds) {
        long total_milliseconds = milliseconds + (days + (long) months * 30) * MILLISECONDSPERDAY;
        return putLongWithTag(field, Types.TAG_INTERVAL, total_milliseconds);
    }

    @Override
    public JsonDocumentBuilder putNewMap(String field) {
        try {
            preparePut();
            jsonGenerator.writeFieldName(field);
            jsonGenerator.writeStartObject();
            ctxStack.push(currentContext);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder putNewArray(String field) {
        try {
            preparePut();
            jsonGenerator.writeFieldName(field);
            jsonGenerator.writeStartArray();
            currentContext = ctxStack.push(new ContainerContext(ARRAY));
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder putNull(String field) {
        try {
            preparePut();
            jsonGenerator.writeNullField(field);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder put(String field, Value value) {
        Value.Type t = value.getType();
        switch (t) {
        case NULL:
            putNull(field);
            break;
        case BOOLEAN:
            put(field, value.getBoolean());
            break;
        case STRING:
            put(field, value.getString());
            break;
        case BYTE:
            put(field, value.getByte());
            break;
        case SHORT:
            put(field, value.getShort());
            break;
        case INT:
            put(field, value.getInt());
            break;
        case LONG:
            put(field, value.getLong());
            break;
        case FLOAT:
            put(field, value.getFloat());
            break;
        case DOUBLE:
            put(field, value.getDouble());
            break;
        case DECIMAL:
            put(field, value.getDecimal());
            break;
        case DATE:
            put(field, value.getDate());
            break;
        case TIME:
            put(field, value.getTime());
            break;
        case TIMESTAMP:
            put(field, value.getTimestamp());
            break;
        case INTERVAL:
            put(field, value.getInterval());
            break;
        case BINARY:
            put(field, value.getBinary());
            break;
        case MAP:
            put(field, (Document) value);
            break;
        case ARRAY:
            putArray(field, value.getList());
            break;
        default:
            break;
        }
        return this;
    }

    private JsonDocumentBuilder iterDocument(Iterator<Entry<String, Value>> it) {
        while (it.hasNext()) {
            Entry<String, Value> kv = it.next();
            String key = kv.getKey();
            Value value = kv.getValue();
            if (value.getType() == MAP) {
                putNewMap(key);
                iterDocument(((Document) value).iterator());
            } else if (value.getType() == ARRAY) {
                putArray(key, value.getList());
            } else {
                // process element.
                put(key, value);
            }
        }
        endMap();
        return this;
    }

    @Override
    public JsonDocumentBuilder put(String field, Map<String, Object> value) {
        return put(field, JsonValueBuilder.initFrom(value));
    }

    @Override
    public JsonDocumentBuilder put(String field, Document value) {
        // iterate over the document interface and extract tokens.
        // Add them to the writer.
        preparePut();
        Iterator<Entry<String, Value>> it = value.iterator();
        putNewMap(field);
        return iterDocument(it);
    }

    @Override
    public DocumentBuilder setArrayIndex(int index) {
        checkContext(ARRAY);
        int lastIndex = currentContext.getIndex();
        if (index <= lastIndex) {
            throw new IllegalArgumentException(String
                    .format("Specified index %d is not larger than the last written index %d", index, lastIndex));
        }
        int nullCount = index - lastIndex;
        for (int i = 1; i < nullCount; i++) {
            addNull();
        }
        return this;
    }

    @Override
    public JsonDocumentBuilder add(boolean value) {
        try {
            prepareAdd();
            jsonGenerator.writeBoolean(value);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(String value) {
        try {
            prepareAdd();
            jsonGenerator.writeString(value);
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(byte value) {
        return addLong(Types.TAG_BYTE, value);
    }

    @Override
    public JsonDocumentBuilder add(short value) {
        return addLong(Types.TAG_SHORT, value);
    }

    @Override
    public JsonDocumentBuilder add(int value) {
        return addLong(Types.TAG_INT, value);
    }

    @Override
    public JsonDocumentBuilder add(long value) {
        return addLong(Types.TAG_LONG, value);
    }

    private JsonDocumentBuilder addLong(String tagName, long value) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeNumberField(tagName, value);
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeNumber(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(float value) {
        return add((double) value);
    }

    @Override
    public JsonDocumentBuilder add(double value) {
        try {
            prepareAdd();
            if (isWholeNumberInLongRange(value)) {
                jsonGenerator.writeNumber((long) value);
            } else {
                jsonGenerator.writeNumber(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(BigDecimal value) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(Types.TAG_DECIMAL, value.toPlainString());
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeNumber(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder addDecimal(long decimalValue) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(Types.TAG_DECIMAL, String.valueOf(decimalValue));
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeNumber(decimalValue);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder addDecimal(double decimalValue) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(Types.TAG_DECIMAL, String.valueOf(decimalValue));
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeNumber(decimalValue);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder addDecimal(int unscaledValue, int scale) {
        return add(Decimals.convertIntToDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder addDecimal(long unscaledValue, int scale) {
        return add(Decimals.convertLongToDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder addDecimal(byte[] unscaledValue, int scale) {
        return add(Decimals.convertByteToBigDecimal(unscaledValue, scale));
    }

    @Override
    public JsonDocumentBuilder add(byte[] value) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeBinaryField(Types.TAG_BINARY, value);
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeBinary(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(byte[] value, int offset, int length) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeFieldName(Types.TAG_BINARY);
                jsonGenerator.writeBinary(value, offset, length);
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeBinary(value, offset, length);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(ByteBuffer value) {
        byte[] bytes = new byte[value.remaining()];
        value.slice().get(bytes);
        return add(bytes);
    }

    @Override
    public JsonDocumentBuilder addNull() {
        try {
            prepareAdd();
            jsonGenerator.writeNull();
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(Value value) {
        Value.Type t = value.getType();
        switch (t) {
        case NULL:
            addNull();
            break;
        case BOOLEAN:
            add(value.getBoolean());
            break;
        case STRING:
            add(value.getString());
            break;
        case BYTE:
            add(value.getByte());
            break;
        case SHORT:
            add(value.getShort());
            break;
        case INT:
            add(value.getInt());
            break;
        case LONG:
            add(value.getLong());
            break;
        case FLOAT:
            add(value.getFloat());
            break;
        case DOUBLE:
            add(value.getDouble());
            break;
        case DATE:
            add(value.getDate());
            break;
        case TIME:
            add(value.getTime());
            break;
        case TIMESTAMP:
            add(value.getTimestamp());
            break;
        case INTERVAL:
            add(value.getInterval());
            break;
        case BINARY:
            add(value.getBinary());
            break;
        case MAP:
            add((Document) value);
            break;
        case DECIMAL:
            add(value.getDecimal());
            break;
        case ARRAY:
            putArray(null, value.getList());
            break;
        default:
            throw new IllegalStateException("Unknown object type");
        }

        return this;
    }

    @Override
    public JsonDocumentBuilder add(Document value) {
        addNewMap();
        Iterator<Entry<String, Value>> it = value.iterator();
        return iterDocument(it);
    }

    @Override
    public JsonDocumentBuilder addNewArray() {
        try {
            prepareAdd();
            jsonGenerator.writeStartArray();
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
        currentContext = ctxStack.push(new ContainerContext(ARRAY));
        return this;
    }

    @Override
    public JsonDocumentBuilder addNewMap() {
        try {
            Preconditions.checkState((currentContext.getType() != MAP),
                    "Context mismatch : addNewMap() can not be called at %s", currentContext.getType());
            if (currentContext.getType() == ARRAY) {
                prepareAdd();
            }
            jsonGenerator.writeStartObject();
            currentContext = ctxStack.push(new ContainerContext(MAP));
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    /* helper function to write date, time and timestamp types as string */
    private JsonDocumentBuilder addStringWithTag(String tagName, String value) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeStringField(tagName, value);
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeString(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder add(OTime value) {
        return addStringWithTag(Types.TAG_TIME, value.toTimeStr());
    }

    @Override
    public JsonDocumentBuilder addTime(int millis) {
        return addStringWithTag(Types.TAG_TIME, OTime.fromMillisOfDay(millis).toTimeStr());
    }

    @Override
    public JsonDocumentBuilder add(ODate value) {
        return addStringWithTag(Types.TAG_DATE, value.toDateStr());
    }

    @Override
    public JsonDocumentBuilder addDate(int days) {
        return addStringWithTag(Types.TAG_DATE, ODate.fromDaysSinceEpoch(days).toDateStr());
    }

    @Override
    public JsonDocumentBuilder add(OTimestamp value) {
        return addStringWithTag(Types.TAG_TIMESTAMP, value.toUTCString());
    }

    @Override
    public JsonDocumentBuilder addTimestamp(long timeMillis) {
        return addStringWithTag(Types.TAG_TIMESTAMP, new OTimestamp(timeMillis).toUTCString());
    }

    @Override
    public JsonDocumentBuilder add(OInterval value) {
        return addLongWithTag(Types.TAG_INTERVAL, value.getTimeInMillis());
    }

    @Override
    public JsonDocumentBuilder addInterval(long durationInMs) {
        return addLongWithTag(Types.TAG_INTERVAL, durationInMs);
    }

    private JsonDocumentBuilder addLongWithTag(String tagName, long value) {
        try {
            prepareAdd();
            if (jsonOptions.isWithTags()) {
                jsonGenerator.writeStartObject();
                jsonGenerator.writeNumberField(tagName, value);
                jsonGenerator.writeEndObject();
            } else {
                jsonGenerator.writeNumber(value);
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder endArray() {
        try {
            checkContext(ARRAY);
            jsonGenerator.writeEndArray();
            ctxStack.pop();
            currentContext = ctxStack.isEmpty() ? ContainerContext.NULL : ctxStack.peek();
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public JsonDocumentBuilder endMap() {
        if (jsonGenerator.isClosed()) {
            throw new IllegalStateException("The document has already been built.");
        }

        try {
            preparePut();
            jsonGenerator.writeEndObject();
            ctxStack.pop();
            if (!ctxStack.empty()) {
                currentContext = ctxStack.peek();
            } else {
                //close the generator
                jsonGenerator.close();
            }
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    @Override
    public String toString() {
        try {
            return b.toString("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public String asUTF8String() {
        if (!jsonGenerator.isClosed()) {
            try {
                jsonGenerator.close();
            } catch (IOException ie) {
                throw transformIOException(ie);
            }
        }
        if (cachedJson == null) {
            cachedJson = toString();
        }
        return cachedJson;
    }

    public byte[] getOutputStream() {
        return b.getByteArray();
    }

    /* private function that adds an array as value of k-v pair */
    private JsonDocumentBuilder putArray(String field, List<Object> values) {
        try {
            if (field != null) {
                checkContext(MAP);
                jsonGenerator.writeFieldName(field);
            } else {
                checkContext(ARRAY);
            }
            jsonGenerator.writeStartArray();
            currentContext = ctxStack.push(new ContainerContext(ARRAY));

            for (Object e : values) {
                add(JsonValueBuilder.initFromObject(e)); //FIXME: unnecessary object creation
            }
            jsonGenerator.writeEndArray();
            ctxStack.pop();
            currentContext = ctxStack.peek();
            return this;
        } catch (IOException ie) {
            throw transformIOException(ie);
        }
    }

    private boolean isWholeNumberInLongRange(double value) {
        return (value == Math.floor(value)) && !Double.isInfinite(value)
                && (value >= Long.MIN_VALUE && value <= Long.MAX_VALUE);
    }

    @Override
    public Document getDocument() {
        Preconditions.checkState(jsonGenerator.isClosed(), "The document has not been built.");

        if (b != null) {
            byte[] barray = b.getByteArray();
            ByteArrayInputStream inputStream = new ByteArrayInputStream(barray);
            DocumentStream documentStream = Json.newDocumentStream(inputStream);
            Iterator<Document> iter = documentStream.iterator();
            if (iter.hasNext()) {
                Document r = iter.next();
                return r;
            }
        }
        return null;
    }

    @API.Internal
    public JsonDocumentBuilder setCheckContext(boolean value) {
        this.checkContext = value;
        return this;
    }

}