com.addthis.codec.config.ConfigTraversingParser.java Source code

Java tutorial

Introduction

Here is the source code for com.addthis.codec.config.ConfigTraversingParser.java

Source

/*
 * 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 com.addthis.codec.config;

import java.io.IOException;
import java.io.OutputStream;

import java.math.BigDecimal;
import java.math.BigInteger;

import com.fasterxml.jackson.core.Base64Variant;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.core.base.ParserMinimalBase;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigOrigin;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueType;

import static com.typesafe.config.ConfigValueType.LIST;
import static com.typesafe.config.ConfigValueType.OBJECT;

public class ConfigTraversingParser extends ParserMinimalBase {
    /*
    /**********************************************************
    /* Configuration
    /**********************************************************
     */

    protected ObjectCodec _objectCodec;

    /**
     * Traversal context within tree
     */
    protected ConfigNodeCursor _nodeCursor;

    /*
    /**********************************************************
    /* State
    /**********************************************************
     */

    /**
     * Sometimes parser needs to buffer a single look-ahead token; if so,
     * it'll be stored here. This is currently used for handling
     */
    protected JsonToken _nextToken;

    /**
     * Flag needed to handle recursion into contents of child
     * Array/Object nodes.
     */
    protected boolean _startContainer;

    /**
     * Flag that indicates whether parser is closed or not. Gets
     * set when parser is either closed by explicit call
     * ({@link #close}) or when end-of-input is reached.
     */
    protected boolean _closed;

    protected ConfigValue currentConfig;

    /*
    /**********************************************************
    /* Life-cycle
    /**********************************************************
     */

    public ConfigTraversingParser(ConfigValue n) {
        this(n, null);
    }

    public ConfigTraversingParser(ConfigValue n, ObjectCodec codec) {
        super(0);
        _objectCodec = codec;
        currentConfig = n;
        if (n.valueType() == LIST) {
            _nextToken = JsonToken.START_ARRAY;
            _nodeCursor = new ConfigNodeCursor.Array((ConfigList) n, null);
        } else if (n.valueType() == OBJECT) {
            _nextToken = JsonToken.START_OBJECT;
            _nodeCursor = new ConfigNodeCursor.Object((ConfigObject) n, null);
        } else { // value node
            _nodeCursor = new ConfigNodeCursor.RootValue(n, null);
        }
    }

    @Override
    public void setCodec(ObjectCodec c) {
        _objectCodec = c;
    }

    @Override
    public ObjectCodec getCodec() {
        return _objectCodec;
    }

    @Override
    public Version version() {
        return Version.unknownVersion();
    }

    /*
    /**********************************************************
    /* Closeable implementation
    /**********************************************************
     */

    @Override
    public void close() {
        if (!_closed) {
            _closed = true;
            _nodeCursor = null;
            _currToken = null;
        }
    }

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

    public ConfigValue currentConfig() {
        if (_nextToken != null) {
            // haven't read or started reading root value yet
            return null;
        }
        return currentConfig;
    }

    @Override
    public JsonToken nextToken() throws IOException, JsonParseException {
        if (_nextToken != null) {
            _currToken = _nextToken;
            _nextToken = null;
            return _currToken;
        }
        // are we to descend to a container child?
        if (_startContainer) {
            _startContainer = false;
            // minor optimization: empty containers can be skipped
            if (!_nodeCursor.currentHasChildren()) {
                _currToken = (_currToken == JsonToken.START_OBJECT) ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
                return _currToken;
            }
            _nodeCursor = _nodeCursor.iterateChildren();
            _currToken = _nodeCursor.nextToken();
            if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
                _startContainer = true;
            }
            currentConfig = currentNode();
            return _currToken;
        }
        // No more content?
        if (_nodeCursor == null) {
            _closed = true; // if not already set
            currentConfig = null;
            return null;
        }
        // Otherwise, next entry from current cursor
        _currToken = _nodeCursor.nextToken();
        if (_currToken != null) {
            currentConfig = currentNode();
            if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
                _startContainer = true;
            }
            return _currToken;
        }
        // null means no more children; need to return end marker
        _currToken = _nodeCursor.endToken();
        _nodeCursor = _nodeCursor.getParent();
        currentConfig = currentNode();
        return _currToken;
    }

    // default works well here:
    //public JsonToken nextValue() throws IOException, JsonParseException

    @Override
    public JsonParser skipChildren() throws IOException, JsonParseException {
        if (_currToken == JsonToken.START_OBJECT) {
            _startContainer = false;
            _currToken = JsonToken.END_OBJECT;
        } else if (_currToken == JsonToken.START_ARRAY) {
            _startContainer = false;
            _currToken = JsonToken.END_ARRAY;
        }
        return this;
    }

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

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

    @Override
    public String getCurrentName() {
        if (_nodeCursor == null) {
            return null;
        } else {
            return _nodeCursor.getCurrentName();
        }
    }

    @Override
    public void overrideCurrentName(String name) {
        if (_nodeCursor != null) {
            _nodeCursor.overrideCurrentName(name);
        }
    }

    @Override
    public JsonStreamContext getParsingContext() {
        return _nodeCursor;
    }

    @Override
    public JsonLocation getTokenLocation() {
        ConfigValue current = currentConfig();
        if (current == null) {
            return JsonLocation.NA;
        }
        ConfigOrigin nodeOrigin = current.origin();
        return new JsonLocation(current, -1, nodeOrigin.lineNumber(), -1);
    }

    @Override
    public JsonLocation getCurrentLocation() {
        return getTokenLocation();
    }

    /*
    /**********************************************************
    /* Public API, access to textual content
    /**********************************************************
     */

    @Override
    public String getText() {
        if (_closed) {
            return null;
        }
        // need to separate handling a bit...
        switch (_currToken) {
        case FIELD_NAME:
            return _nodeCursor.getCurrentName();
        case VALUE_STRING:
            return (String) currentNode().unwrapped();
        case VALUE_NUMBER_INT:
        case VALUE_NUMBER_FLOAT:
            return String.valueOf(currentNode().unwrapped());
        case VALUE_EMBEDDED_OBJECT:
            // not supported and shouldn't be called, but emulating the 'null' result by not throwing
        default:
            if (_currToken == null) {
                return null;
            } else {
                return _currToken.asString();
            }
        }
    }

    @Override
    public char[] getTextCharacters() throws IOException, JsonParseException {
        return getText().toCharArray();
    }

    @Override
    public int getTextLength() throws IOException, JsonParseException {
        return getText().length();
    }

    @Override
    public int getTextOffset() throws IOException, JsonParseException {
        return 0;
    }

    @Override
    public boolean hasTextCharacters() {
        // generally we do not have efficient access as char[], hence:
        return false;
    }

    /*
    /**********************************************************
    /* Public API, typed non-text access
    /**********************************************************
     */

    //public byte getByteValue() throws IOException, JsonParseException

    @Override
    public NumberType getNumberType() throws IOException, JsonParseException {
        JsonNode n = currentNumericNode();
        return (n == null) ? null : n.numberType();
    }

    @Override
    public BigInteger getBigIntegerValue() throws IOException, JsonParseException {
        return currentNumericNode().bigIntegerValue();
    }

    @Override
    public BigDecimal getDecimalValue() throws IOException, JsonParseException {
        return currentNumericNode().decimalValue();
    }

    @Override
    public double getDoubleValue() throws IOException, JsonParseException {
        return currentNumericNode().doubleValue();
    }

    @Override
    public float getFloatValue() throws IOException, JsonParseException {
        return (float) currentNumericNode().doubleValue();
    }

    @Override
    public long getLongValue() throws IOException, JsonParseException {
        return currentNumericNode().longValue();
    }

    @Override
    public int getIntValue() throws IOException, JsonParseException {
        JsonNode numericNode = currentNumericNode();
        if (numericNode.canConvertToInt()) {
            return currentNumericNode().intValue();
        }
        throw _constructError("Numeric value (" + getText() + ") out of range of Java short");
    }

    @Override
    public Number getNumberValue() throws IOException, JsonParseException {
        return currentNumericNode().numberValue();
    }

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

    /*
    /**********************************************************
    /* Public API, typed binary (base64) access
    /**********************************************************
     */

    @Override
    public byte[] getBinaryValue(Base64Variant b64variant) throws IOException, JsonParseException {
        // otherwise return null to mark we have no binary content
        return null;
    }

    @Override
    public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException, JsonParseException {
        return 0;
    }

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

    protected ConfigValue currentNode() {
        if (_closed || _nodeCursor == null) {
            return null;
        }
        return _nodeCursor.currentNode();
    }

    protected JsonNode currentNumericNode() throws JsonParseException {
        ConfigValue configValue = currentNode();
        if ((configValue == null) || (configValue.valueType() != ConfigValueType.NUMBER)) {
            JsonToken t = (configValue == null) ? null : ConfigNodeCursor.forConfigValue(configValue);
            throw _constructError("Current token (" + t + ") not numeric, can not use numeric value accessors");
        }
        Number value = (Number) configValue.unwrapped();
        if (value instanceof Double) {
            return JsonNodeFactory.instance.numberNode((Double) value);
        }
        if (value instanceof Long) {
            return JsonNodeFactory.instance.numberNode((Long) value);
        }
        if (value instanceof Integer) {
            return JsonNodeFactory.instance.numberNode((Integer) value);
        }
        // only possible if Config has since added more numeric types
        throw _constructError(value.getClass() + " is not a supported numeric config type");
    }

    @Override
    protected void _handleEOF() throws JsonParseException {
        _throwInternal(); // should never get called
    }
}