Java tutorial
/* * 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 } }