| package com.fasterxml.jackson.core.base; |
| |
| import java.io.IOException; |
| |
| import com.fasterxml.jackson.core.*; |
| import com.fasterxml.jackson.core.JsonParser.Feature; |
| import com.fasterxml.jackson.core.io.NumberInput; |
| import com.fasterxml.jackson.core.util.ByteArrayBuilder; |
| import com.fasterxml.jackson.core.util.VersionUtil; |
| |
| import static com.fasterxml.jackson.core.JsonTokenId.*; |
| |
| /** |
| * Intermediate base class used by all Jackson {@link JsonParser} |
| * implementations, but does not add any additional fields that depend |
| * on particular method of obtaining input. |
| *<p> |
| * Note that 'minimal' here mostly refers to minimal number of fields |
| * (size) and functionality that is specific to certain types |
| * of parser implementations; but not necessarily to number of methods. |
| */ |
| public abstract class ParserMinimalBase extends JsonParser |
| { |
| // Control chars: |
| protected final static int INT_TAB = '\t'; |
| protected final static int INT_LF = '\n'; |
| protected final static int INT_CR = '\r'; |
| protected final static int INT_SPACE = 0x0020; |
| |
| // Markup |
| protected final static int INT_LBRACKET = '['; |
| protected final static int INT_RBRACKET = ']'; |
| protected final static int INT_LCURLY = '{'; |
| protected final static int INT_RCURLY = '}'; |
| protected final static int INT_QUOTE = '"'; |
| protected final static int INT_BACKSLASH = '\\'; |
| protected final static int INT_SLASH = '/'; |
| protected final static int INT_COLON = ':'; |
| protected final static int INT_COMMA = ','; |
| protected final static int INT_HASH = '#'; |
| |
| // fp numbers |
| protected final static int INT_PERIOD = '.'; |
| protected final static int INT_e = 'e'; |
| protected final static int INT_E = 'E'; |
| |
| /* |
| /********************************************************** |
| /* Minimal generally useful state |
| /********************************************************** |
| */ |
| |
| /** |
| * Last token retrieved via {@link #nextToken}, if any. |
| * Null before the first call to <code>nextToken()</code>, |
| * as well as if token has been explicitly cleared |
| */ |
| protected JsonToken _currToken; |
| |
| /** |
| * Last cleared token, if any: that is, value that was in |
| * effect when {@link #clearCurrentToken} was called. |
| */ |
| protected JsonToken _lastClearedToken; |
| |
| /* |
| /********************************************************** |
| /* Life-cycle |
| /********************************************************** |
| */ |
| |
| protected ParserMinimalBase() { } |
| protected ParserMinimalBase(int features) { super(features); } |
| |
| // NOTE: had base impl in 2.3 and before; but shouldn't |
| // public abstract Version version(); |
| |
| /* |
| /********************************************************** |
| /* Configuration overrides if any |
| /********************************************************** |
| */ |
| |
| // from base class: |
| |
| //public void enableFeature(Feature f) |
| //public void disableFeature(Feature f) |
| //public void setFeature(Feature f, boolean state) |
| //public boolean isFeatureEnabled(Feature f) |
| |
| /* |
| /********************************************************** |
| /* JsonParser impl |
| /********************************************************** |
| */ |
| |
| @Override public abstract JsonToken nextToken() throws IOException; |
| @Override public JsonToken getCurrentToken() { return _currToken; } |
| |
| @Override public int getCurrentTokenId() { |
| final JsonToken t = _currToken; |
| return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id(); |
| } |
| |
| @Override public boolean hasCurrentToken() { return _currToken != null; } |
| @Override public boolean hasTokenId(int id) { |
| final JsonToken t = _currToken; |
| if (t == null) { |
| return (JsonTokenId.ID_NO_TOKEN == id); |
| } |
| return t.id() == id; |
| } |
| |
| @Override public boolean hasToken(JsonToken t) { |
| return (_currToken == t); |
| } |
| |
| @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; } |
| @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; } |
| |
| @Override |
| public JsonToken nextValue() throws IOException { |
| /* Implementation should be as trivial as follows; only |
| * needs to change if we are to skip other tokens (for |
| * example, if comments were exposed as tokens) |
| */ |
| JsonToken t = nextToken(); |
| if (t == JsonToken.FIELD_NAME) { |
| t = nextToken(); |
| } |
| return t; |
| } |
| |
| @Override |
| public JsonParser skipChildren() throws IOException |
| { |
| if (_currToken != JsonToken.START_OBJECT |
| && _currToken != JsonToken.START_ARRAY) { |
| return this; |
| } |
| int open = 1; |
| |
| /* Since proper matching of start/end markers is handled |
| * by nextToken(), we'll just count nesting levels here |
| */ |
| while (true) { |
| JsonToken t = nextToken(); |
| if (t == null) { |
| _handleEOF(); |
| /* given constraints, above should never return; |
| * however, FindBugs doesn't know about it and |
| * complains... so let's add dummy break here |
| */ |
| return this; |
| } |
| if (t.isStructStart()) { |
| ++open; |
| } else if (t.isStructEnd()) { |
| if (--open == 0) { |
| return this; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Method sub-classes need to implement |
| */ |
| protected abstract void _handleEOF() throws JsonParseException; |
| |
| //public JsonToken getCurrentToken() |
| //public boolean hasCurrentToken() |
| |
| @Override public abstract String getCurrentName() throws IOException; |
| @Override public abstract void close() throws IOException; |
| @Override public abstract boolean isClosed(); |
| |
| @Override public abstract JsonStreamContext getParsingContext(); |
| |
| // public abstract JsonLocation getTokenLocation(); |
| |
| // public abstract JsonLocation getCurrentLocation(); |
| |
| /* |
| /********************************************************** |
| /* Public API, token state overrides |
| /********************************************************** |
| */ |
| |
| @Override public void clearCurrentToken() { |
| if (_currToken != null) { |
| _lastClearedToken = _currToken; |
| _currToken = null; |
| } |
| } |
| |
| @Override public JsonToken getLastClearedToken() { return _lastClearedToken; } |
| |
| @Override public abstract void overrideCurrentName(String name); |
| |
| /* |
| /********************************************************** |
| /* Public API, access to token information, text |
| /********************************************************** |
| */ |
| |
| @Override public abstract String getText() throws IOException; |
| @Override public abstract char[] getTextCharacters() throws IOException; |
| @Override public abstract boolean hasTextCharacters(); |
| @Override public abstract int getTextLength() throws IOException; |
| @Override public abstract int getTextOffset() throws IOException; |
| |
| /* |
| /********************************************************** |
| /* Public API, access to token information, binary |
| /********************************************************** |
| */ |
| |
| @Override public abstract byte[] getBinaryValue(Base64Variant b64variant) throws IOException; |
| |
| /* |
| /********************************************************** |
| /* Public API, access with conversion/coercion |
| /********************************************************** |
| */ |
| |
| @Override |
| public boolean getValueAsBoolean(boolean defaultValue) throws IOException |
| { |
| JsonToken t = _currToken; |
| if (t != null) { |
| switch (t.id()) { |
| case ID_STRING: |
| String str = getText().trim(); |
| if ("true".equals(str)) { |
| return true; |
| } |
| if ("false".equals(str)) { |
| return false; |
| } |
| if (_hasTextualNull(str)) { |
| return false; |
| } |
| break; |
| case ID_NUMBER_INT: |
| return getIntValue() != 0; |
| case ID_TRUE: |
| return true; |
| case ID_FALSE: |
| case ID_NULL: |
| return false; |
| case ID_EMBEDDED_OBJECT: |
| Object value = this.getEmbeddedObject(); |
| if (value instanceof Boolean) { |
| return (Boolean) value; |
| } |
| break; |
| default: |
| } |
| } |
| return defaultValue; |
| } |
| |
| @Override |
| public int getValueAsInt() throws IOException |
| { |
| JsonToken t = _currToken; |
| if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) { |
| return getIntValue(); |
| } |
| return getValueAsInt(0); |
| } |
| |
| @Override |
| public int getValueAsInt(int defaultValue) throws IOException |
| { |
| JsonToken t = _currToken; |
| if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) { |
| return getIntValue(); |
| } |
| if (t != null) { |
| switch (t.id()) { |
| case ID_STRING: |
| String str = getText(); |
| if (_hasTextualNull(str)) { |
| return 0; |
| } |
| return NumberInput.parseAsInt(str, defaultValue); |
| case ID_TRUE: |
| return 1; |
| case ID_FALSE: |
| return 0; |
| case ID_NULL: |
| return 0; |
| case ID_EMBEDDED_OBJECT: |
| Object value = this.getEmbeddedObject(); |
| if (value instanceof Number) { |
| return ((Number) value).intValue(); |
| } |
| } |
| } |
| return defaultValue; |
| } |
| |
| @Override |
| public long getValueAsLong() throws IOException |
| { |
| JsonToken t = _currToken; |
| if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) { |
| return getLongValue(); |
| } |
| return getValueAsLong(0L); |
| } |
| |
| @Override |
| public long getValueAsLong(long defaultValue) throws IOException |
| { |
| JsonToken t = _currToken; |
| if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) { |
| return getLongValue(); |
| } |
| if (t != null) { |
| switch (t.id()) { |
| case ID_STRING: |
| String str = getText(); |
| if (_hasTextualNull(str)) { |
| return 0L; |
| } |
| return NumberInput.parseAsLong(str, defaultValue); |
| case ID_TRUE: |
| return 1L; |
| case ID_FALSE: |
| case ID_NULL: |
| return 0L; |
| case ID_EMBEDDED_OBJECT: |
| Object value = this.getEmbeddedObject(); |
| if (value instanceof Number) { |
| return ((Number) value).longValue(); |
| } |
| } |
| } |
| return defaultValue; |
| } |
| |
| @Override |
| public double getValueAsDouble(double defaultValue) throws IOException |
| { |
| JsonToken t = _currToken; |
| if (t != null) { |
| switch (t.id()) { |
| case ID_STRING: |
| String str = getText(); |
| if (_hasTextualNull(str)) { |
| return 0L; |
| } |
| return NumberInput.parseAsDouble(str, defaultValue); |
| case ID_NUMBER_INT: |
| case ID_NUMBER_FLOAT: |
| return getDoubleValue(); |
| case ID_TRUE: |
| return 1.0; |
| case ID_FALSE: |
| case ID_NULL: |
| return 0.0; |
| case ID_EMBEDDED_OBJECT: |
| Object value = this.getEmbeddedObject(); |
| if (value instanceof Number) { |
| return ((Number) value).doubleValue(); |
| } |
| } |
| } |
| return defaultValue; |
| } |
| |
| @Override |
| public String getValueAsString() throws IOException { |
| if (_currToken == JsonToken.VALUE_STRING) { |
| return getText(); |
| } |
| if (_currToken == JsonToken.FIELD_NAME) { |
| return getCurrentName(); |
| } |
| return getValueAsString(null); |
| } |
| |
| @Override |
| public String getValueAsString(String defaultValue) throws IOException { |
| if (_currToken == JsonToken.VALUE_STRING) { |
| return getText(); |
| } |
| if (_currToken == JsonToken.FIELD_NAME) { |
| return getCurrentName(); |
| } |
| if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) { |
| return defaultValue; |
| } |
| return getText(); |
| } |
| |
| /* |
| /********************************************************** |
| /* Base64 decoding |
| /********************************************************** |
| */ |
| |
| /** |
| * Helper method that can be used for base64 decoding in cases where |
| * encoded content has already been read as a String. |
| */ |
| protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant b64variant) throws IOException |
| { |
| // just call helper method introduced in 2.2.3 |
| try { |
| b64variant.decode(str, builder); |
| } catch (IllegalArgumentException e) { |
| _reportError(e.getMessage()); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Coercion helper methods (overridable) |
| /********************************************************** |
| */ |
| |
| /** |
| * Helper method used to determine whether we are currently pointing to |
| * a String value of "null" (NOT a null token); and, if so, that parser |
| * is to recognize and return it similar to if it was real null token. |
| * |
| * @since 2.3 |
| */ |
| protected boolean _hasTextualNull(String value) { return "null".equals(value); } |
| |
| /* |
| /********************************************************** |
| /* Error reporting |
| /********************************************************** |
| */ |
| |
| protected void _reportUnexpectedChar(int ch, String comment) throws JsonParseException |
| { |
| if (ch < 0) { // sanity check |
| _reportInvalidEOF(); |
| } |
| String msg = "Unexpected character ("+_getCharDesc(ch)+")"; |
| if (comment != null) { |
| msg += ": "+comment; |
| } |
| _reportError(msg); |
| } |
| |
| protected void _reportInvalidEOF() throws JsonParseException { |
| _reportInvalidEOF(" in "+_currToken); |
| } |
| |
| protected void _reportInvalidEOF(String msg) throws JsonParseException { |
| _reportError("Unexpected end-of-input"+msg); |
| } |
| |
| protected void _reportInvalidEOFInValue() throws JsonParseException { |
| _reportInvalidEOF(" in a value"); |
| } |
| |
| protected void _reportMissingRootWS(int ch) throws JsonParseException { |
| _reportUnexpectedChar(ch, "Expected space separating root-level values"); |
| } |
| |
| protected void _throwInvalidSpace(int i) throws JsonParseException { |
| char c = (char) i; |
| String msg = "Illegal character ("+_getCharDesc(c)+"): only regular white space (\\r, \\n, \\t) is allowed between tokens"; |
| _reportError(msg); |
| } |
| |
| /** |
| * Method called to report a problem with unquoted control character. |
| * Note: starting with version 1.4, it is possible to suppress |
| * exception by enabling {@link Feature#ALLOW_UNQUOTED_CONTROL_CHARS}. |
| */ |
| protected void _throwUnquotedSpace(int i, String ctxtDesc) throws JsonParseException { |
| // JACKSON-208; possible to allow unquoted control chars: |
| if (!isEnabled(Feature.ALLOW_UNQUOTED_CONTROL_CHARS) || i > INT_SPACE) { |
| char c = (char) i; |
| String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc; |
| _reportError(msg); |
| } |
| } |
| |
| protected char _handleUnrecognizedCharacterEscape(char ch) throws JsonProcessingException { |
| // as per [JACKSON-300] |
| if (isEnabled(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)) { |
| return ch; |
| } |
| // and [JACKSON-548] |
| if (ch == '\'' && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) { |
| return ch; |
| } |
| _reportError("Unrecognized character escape "+_getCharDesc(ch)); |
| return ch; |
| } |
| |
| /* |
| /********************************************************** |
| /* Error reporting, generic |
| /********************************************************** |
| */ |
| |
| protected final static String _getCharDesc(int ch) |
| { |
| char c = (char) ch; |
| if (Character.isISOControl(c)) { |
| return "(CTRL-CHAR, code "+ch+")"; |
| } |
| if (ch > 255) { |
| return "'"+c+"' (code "+ch+" / 0x"+Integer.toHexString(ch)+")"; |
| } |
| return "'"+c+"' (code "+ch+")"; |
| } |
| |
| protected final void _reportError(String msg) throws JsonParseException { |
| throw _constructError(msg); |
| } |
| |
| protected final void _wrapError(String msg, Throwable t) throws JsonParseException { |
| throw _constructError(msg, t); |
| } |
| |
| protected final void _throwInternal() { |
| VersionUtil.throwInternal(); |
| } |
| |
| protected final JsonParseException _constructError(String msg, Throwable t) { |
| return new JsonParseException(this, msg, t); |
| } |
| |
| protected static byte[] _asciiBytes(String str) { |
| byte[] b = new byte[str.length()]; |
| for (int i = 0, len = str.length(); i < len; ++i) { |
| b[i] = (byte) str.charAt(i); |
| } |
| return b; |
| } |
| |
| protected static String _ascii(byte[] b) { |
| try { |
| return new String(b, "US-ASCII"); |
| } catch (IOException e) { // never occurs |
| throw new RuntimeException(e); |
| } |
| } |
| } |