package com.fasterxml.jackson.core.base;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.exc.InputCoercionException;
import com.fasterxml.jackson.core.io.JsonEOFException;
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 JsonParser implementations, but does not add any additional fields that depend on particular method of obtaining input.

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.

/** * 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_APOS = '\''; protected final static int INT_BACKSLASH = '\\'; protected final static int INT_SLASH = '/'; protected final static int INT_ASTERISK = '*'; protected final static int INT_COLON = ':'; protected final static int INT_COMMA = ','; protected final static int INT_HASH = '#'; // Number chars protected final static int INT_0 = '0'; protected final static int INT_9 = '9'; protected final static int INT_MINUS = '-'; protected final static int INT_PLUS = '+'; protected final static int INT_PERIOD = '.'; protected final static int INT_e = 'e'; protected final static int INT_E = 'E'; protected final static char CHAR_NULL = '\0';
Since:2.9
/** * @since 2.9 */
protected final static byte[] NO_BYTES = new byte[0];
Since:2.9
/** * @since 2.9 */
protected final static int[] NO_INTS = new int[0]; /* /********************************************************** /* Constants and fields of former 'JsonNumericParserBase' /********************************************************** */ protected final static int NR_UNKNOWN = 0; // First, integer types protected final static int NR_INT = 0x0001; protected final static int NR_LONG = 0x0002; protected final static int NR_BIGINT = 0x0004; // And then floating point types protected final static int NR_DOUBLE = 0x008; protected final static int NR_BIGDECIMAL = 0x0010;
NOTE! Not used by JSON implementation but used by many of binary codecs
Since:2.9
/** * NOTE! Not used by JSON implementation but used by many of binary codecs * * @since 2.9 */
protected final static int NR_FLOAT = 0x020; // Also, we need some numeric constants protected final static BigInteger BI_MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE); protected final static BigInteger BI_MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE); protected final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); protected final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); protected final static BigDecimal BD_MIN_LONG = new BigDecimal(BI_MIN_LONG); protected final static BigDecimal BD_MAX_LONG = new BigDecimal(BI_MAX_LONG); protected final static BigDecimal BD_MIN_INT = new BigDecimal(BI_MIN_INT); protected final static BigDecimal BD_MAX_INT = new BigDecimal(BI_MAX_INT); protected final static long MIN_INT_L = (long) Integer.MIN_VALUE; protected final static long MAX_INT_L = (long) Integer.MAX_VALUE; // These are not very accurate, but have to do... (for bounds checks) protected final static double MIN_LONG_D = (double) Long.MIN_VALUE; protected final static double MAX_LONG_D = (double) Long.MAX_VALUE; protected final static double MIN_INT_D = (double) Integer.MIN_VALUE; protected final static double MAX_INT_D = (double) Integer.MAX_VALUE; /* /********************************************************** /* Misc other constants /********************************************************** */
Maximum number of characters to include in token reported as part of error messages.
Since:2.9
/** * Maximum number of characters to include in token reported * as part of error messages. * * @since 2.9 */
protected final static int MAX_ERROR_TOKEN_LENGTH = 256; /* /********************************************************** /* Minimal generally useful state /********************************************************** */
Last token retrieved via nextToken, if any. Null before the first call to nextToken(), as well as if token has been explicitly cleared
/** * 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 clearCurrentToken was called.
/** * 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 currentToken() { return _currToken; } @Override public int currentTokenId() { final JsonToken t = _currToken; return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id(); } @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; } // 23-May-2018, tatu: [core#463] Need to consider non-blocking case... } else if (t == JsonToken.NOT_AVAILABLE) { // Nothing much we can do except to either return `null` (which seems wrong), // or, what we actually do, signal error _reportError("Not enough content available for `skipChildren()`: non-blocking parser? (%s)", getClass().getName()); } } }
Method sub-classes need to implement
/** * 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 = 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 = 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 = 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 { // sub-classes tend to override so... 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.
/** * 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 { 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
/** * 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 reportUnexpectedNumberChar(int ch, String comment) throws JsonParseException { String msg = String.format("Unexpected character (%s) in numeric value", _getCharDesc(ch)); if (comment != null) { msg += ": "+comment; } _reportError(msg); }
Method called to throw an exception for input token that looks like a number based on first character(s), but is not valid according to rules of format. In case of JSON this also includes invalid forms like positive sign and leading zeroes.
/** * Method called to throw an exception for input token that looks like a number * based on first character(s), but is not valid according to rules of format. * In case of JSON this also includes invalid forms like positive sign and * leading zeroes. */
protected void reportInvalidNumber(String msg) throws JsonParseException { _reportError("Invalid numeric value: "+msg); }
Method called to throw an exception for integral (not floating point) input token with value outside of Java signed 32-bit range when requested as int. Result will be InputCoercionException being thrown.
/** * Method called to throw an exception for integral (not floating point) input * token with value outside of Java signed 32-bit range when requested as {@link int}. * Result will be {@link InputCoercionException} being thrown. */
protected void reportOverflowInt() throws IOException { reportOverflowInt(getText()); } // @since 2.10 protected void reportOverflowInt(String numDesc) throws IOException { reportOverflowInt(numDesc, currentToken()); } // @since 2.10 protected void reportOverflowInt(String numDesc, JsonToken inputType) throws IOException { _reportInputCoercion(String.format("Numeric value (%s) out of range of int (%d - %s)", _longIntegerDesc(numDesc), Integer.MIN_VALUE, Integer.MAX_VALUE), inputType, Integer.TYPE); }
Method called to throw an exception for integral (not floating point) input token with value outside of Java signed 64-bit range when requested as long. Result will be InputCoercionException being thrown.
/** * Method called to throw an exception for integral (not floating point) input * token with value outside of Java signed 64-bit range when requested as {@link long}. * Result will be {@link InputCoercionException} being thrown. */
protected void reportOverflowLong() throws IOException { reportOverflowLong(getText()); } // @since 2.10 protected void reportOverflowLong(String numDesc) throws IOException { reportOverflowLong(numDesc, currentToken()); } // @since 2.10 protected void reportOverflowLong(String numDesc, JsonToken inputType) throws IOException { _reportInputCoercion(String.format("Numeric value (%s) out of range of long (%d - %s)", _longIntegerDesc(numDesc), Long.MIN_VALUE, Long.MAX_VALUE), inputType, Long.TYPE); }
Since:2.10
/** * @since 2.10 */
protected void _reportInputCoercion(String msg, JsonToken inputType, Class<?> targetType) throws InputCoercionException { throw new InputCoercionException(this, msg, inputType, targetType); } // @since 2.9.8 protected String _longIntegerDesc(String rawNum) { int rawLen = rawNum.length(); if (rawLen < 1000) { return rawNum; } if (rawNum.startsWith("-")) { rawLen -= 1; } return String.format("[Integer with %d digits]", rawLen); } // @since 2.9.8 protected String _longNumberDesc(String rawNum) { int rawLen = rawNum.length(); if (rawLen < 1000) { return rawNum; } if (rawNum.startsWith("-")) { rawLen -= 1; } return String.format("[number with %d characters]", rawLen); } protected void _reportUnexpectedChar(int ch, String comment) throws JsonParseException { if (ch < 0) { // sanity check _reportInvalidEOF(); } String msg = String.format("Unexpected character (%s)", _getCharDesc(ch)); if (comment != null) { msg += ": "+comment; } _reportError(msg); } protected void _reportInvalidEOF() throws JsonParseException { _reportInvalidEOF(" in "+_currToken, _currToken); }
Since:2.8
/** * @since 2.8 */
protected void _reportInvalidEOFInValue(JsonToken type) throws JsonParseException { String msg; if (type == JsonToken.VALUE_STRING) { msg = " in a String value"; } else if ((type == JsonToken.VALUE_NUMBER_INT) || (type == JsonToken.VALUE_NUMBER_FLOAT)) { msg = " in a Number value"; } else { msg = " in a value"; } _reportInvalidEOF(msg, type); }
Since:2.8
/** * @since 2.8 */
protected void _reportInvalidEOF(String msg, JsonToken currToken) throws JsonParseException { throw new JsonEOFException(this, currToken, "Unexpected end-of-input"+msg); }
Deprecated:Since 2.8 use _reportInvalidEOF(String, JsonToken) instead
/** * @deprecated Since 2.8 use {@link #_reportInvalidEOF(String, JsonToken)} instead */
@Deprecated // since 2.8 protected void _reportInvalidEOFInValue() throws JsonParseException { _reportInvalidEOF(" in a value"); }
Deprecated:Since 2.8 use _reportInvalidEOF(String, JsonToken) instead
/** * @deprecated Since 2.8 use {@link #_reportInvalidEOF(String, JsonToken)} instead */
@Deprecated // since 2.8 protected void _reportInvalidEOF(String msg) throws JsonParseException { throw new JsonEOFException(this, null, "Unexpected end-of-input"+msg); } 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); } /* /********************************************************** /* 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); } // @since 2.9 protected final void _reportError(String msg, Object arg) throws JsonParseException { throw _constructError(String.format(msg, arg)); } // @since 2.9 protected final void _reportError(String msg, Object arg1, Object arg2) throws JsonParseException { throw _constructError(String.format(msg, arg1, arg2)); } 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); } @Deprecated // since 2.11 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; } @Deprecated // since 2.11 protected static String _ascii(byte[] b) { try { return new String(b, "US-ASCII"); } catch (IOException e) { // never occurs throw new RuntimeException(e); } } }