com.fasterxml.jackson.core.base.ParserBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
package com.fasterxml.jackson.core.base;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.json.DupDetector;
import com.fasterxml.jackson.core.json.JsonReadContext;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.core.util.TextBuffer;
/**
* Intermediate base class used by all Jackson {@link JsonParser}
* implementations. Contains most common things that are independent
* of actual underlying input source.
*/
public abstract class ParserBase
extends ParserMinimalBase
{
/*
/**********************************************************
/* Generic I/O state
/**********************************************************
*/
/**
* I/O context for this reader. It handles buffer allocation
* for the reader.
*/
final protected IOContext _ioContext;
/**
* 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;
/*
/**********************************************************
/* Current input data
/**********************************************************
*/
// Note: type of actual buffer depends on sub-class, can't include
/**
* Pointer to next available character in buffer
*/
protected int _inputPtr = 0;
/**
* Index of character after last available one in the buffer.
*/
protected int _inputEnd = 0;
/*
/**********************************************************
/* Current input location information
/**********************************************************
*/
/**
* Number of characters/bytes that were contained in previous blocks
* (blocks that were already processed prior to the current buffer).
*/
protected long _currInputProcessed = 0L;
/**
* Current row location of current point in input buffer, starting
* from 1, if available.
*/
protected int _currInputRow = 1;
/**
* Current index of the first character of the current row in input
* buffer. Needed to calculate column position, if necessary; benefit
* of not having column itself is that this only has to be updated
* once per line.
*/
protected int _currInputRowStart = 0;
/*
/**********************************************************
/* Information about starting location of event
/* Reader is pointing to; updated on-demand
/**********************************************************
*/
// // // Location info at point when current token was started
/**
* Total number of bytes/characters read before start of current token.
* For big (gigabyte-sized) sizes are possible, needs to be long,
* unlike pointers and sizes related to in-memory buffers.
*/
protected long _tokenInputTotal = 0;
/**
* Input row on which current token starts, 1-based
*/
protected int _tokenInputRow = 1;
/**
* Column on input row that current token starts; 0-based (although
* in the end it'll be converted to 1-based)
*/
protected int _tokenInputCol = 0;
/*
/**********************************************************
/* Parsing state
/**********************************************************
*/
/**
* Information about parser context, context in which
* the next token is to be parsed (root, array, object).
*/
protected JsonReadContext _parsingContext;
/**
* Secondary token related to the next token after current one;
* used if its type is known. This may be value token that
* follows FIELD_NAME, for example.
*/
protected JsonToken _nextToken;
/*
/**********************************************************
/* Buffer(s) for local name(s) and text content
/**********************************************************
*/
/**
* Buffer that contains contents of String values, including
* field names if necessary (name split across boundary,
* contains escape sequence, or access needed to char array)
*/
protected final TextBuffer _textBuffer;
/**
* Temporary buffer that is needed if field name is accessed
* using {@link #getTextCharacters} method (instead of String
* returning alternatives)
*/
protected char[] _nameCopyBuffer = null;
/**
* Flag set to indicate whether the field name is available
* from the name copy buffer or not (in addition to its String
* representation being available via read context)
*/
protected boolean _nameCopied = false;
/**
* ByteArrayBuilder is needed if 'getBinaryValue' is called. If so,
* we better reuse it for remainder of content.
*/
protected ByteArrayBuilder _byteArrayBuilder = null;
/**
* We will hold on to decoded binary data, for duration of
* current event, so that multiple calls to
* {@link #getBinaryValue} will not need to decode data more
* than once.
*/
protected byte[] _binaryValue;
/*
/**********************************************************
/* Constants and fields of former 'JsonNumericParserBase'
/**********************************************************
*/
final protected static int NR_UNKNOWN = 0;
// First, integer types
final protected static int NR_INT = 0x0001;
final protected static int NR_LONG = 0x0002;
final protected static int NR_BIGINT = 0x0004;
// And then floating point types
final protected static int NR_DOUBLE = 0x008;
final protected static int NR_BIGDECIMAL = 0x0010;
// Also, we need some numeric constants
final static BigInteger BI_MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE);
final static BigInteger BI_MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE);
final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
final static BigDecimal BD_MIN_LONG = new BigDecimal(BI_MIN_LONG);
final static BigDecimal BD_MAX_LONG = new BigDecimal(BI_MAX_LONG);
final static BigDecimal BD_MIN_INT = new BigDecimal(BI_MIN_INT);
final static BigDecimal BD_MAX_INT = new BigDecimal(BI_MAX_INT);
final static long MIN_INT_L = (long) Integer.MIN_VALUE;
final static long MAX_INT_L = (long) Integer.MAX_VALUE;
// These are not very accurate, but have to do... (for bounds checks)
final static double MIN_LONG_D = (double) Long.MIN_VALUE;
final static double MAX_LONG_D = (double) Long.MAX_VALUE;
final static double MIN_INT_D = (double) Integer.MIN_VALUE;
final static double MAX_INT_D = (double) Integer.MAX_VALUE;
// Digits, numeric
final protected static int INT_0 = '0';
final protected static int INT_9 = '9';
final protected static int INT_MINUS = '-';
final protected static int INT_PLUS = '+';
final protected static char CHAR_NULL = '\0';
// Numeric value holders: multiple fields used for
// for efficiency
/**
* Bitfield that indicates which numeric representations
* have been calculated for the current type
*/
protected int _numTypesValid = NR_UNKNOWN;
// First primitives
protected int _numberInt;
protected long _numberLong;
protected double _numberDouble;
// And then object types
protected BigInteger _numberBigInt;
protected BigDecimal _numberBigDecimal;
// And then other information about value itself
/**
* Flag that indicates whether numeric value has a negative
* value. That is, whether its textual representation starts
* with minus character.
*/
protected boolean _numberNegative;
/**
* Length of integer part of the number, in characters
*/
protected int _intLength;
/**
* Length of the fractional part (not including decimal
* point or exponent), in characters.
* Not used for pure integer values.
*/
protected int _fractLength;
/**
* Length of the exponent part of the number, if any, not
* including 'e' marker or sign, just digits.
* Not used for pure integer values.
*/
protected int _expLength;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
protected ParserBase(IOContext ctxt, int features)
{
super();
_features = features;
_ioContext = ctxt;
_textBuffer = ctxt.constructTextBuffer();
DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features)
? DupDetector.rootDetector(this) : null;
JsonReadContext readCtxt = JsonReadContext.createRootContext(dups);
_parsingContext = readCtxt;
}
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************
/* JsonParser impl
/**********************************************************
*/
/**
* Method that can be called to get the name associated with
* the current event.
*/
@Override
public String getCurrentName()
throws IOException, JsonParseException
{
// [JACKSON-395]: start markers require information from parent
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
JsonReadContext parent = _parsingContext.getParent();
return parent.getCurrentName();
}
return _parsingContext.getCurrentName();
}
@Override
public void overrideCurrentName(String name)
{
// Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
JsonReadContext ctxt = _parsingContext;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
}
/* 24-Sep-2013, tatu: Unfortunate, but since we did not expose exceptions,
* need to wrap this here
*/
try {
ctxt.setCurrentName(name);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
@Override
public void close() throws IOException
{
if (!_closed) {
_closed = true;
try {
_closeInput();
} finally {
// as per [JACKSON-324], do in finally block
// Also, internal buffer(s) can now be released as well
_releaseBuffers();
}
}
}
@Override
public boolean isClosed() { return _closed; }
@Override
public JsonReadContext getParsingContext()
{
return _parsingContext;
}
/**
* Method that return the starting location of the current
* token; that is, position of the first character from input
* that starts the current token.
*/
@Override
public JsonLocation getTokenLocation()
{
return new JsonLocation(_ioContext.getSourceReference(),
-1L, getTokenCharacterOffset(), // bytes, chars
getTokenLineNr(),
getTokenColumnNr());
}
/**
* Method that returns location of the last processed character;
* usually for error reporting purposes
*/
@Override
public JsonLocation getCurrentLocation()
{
int col = _inputPtr - _currInputRowStart + 1; // 1-based
return new JsonLocation(_ioContext.getSourceReference(),
-1L, _currInputProcessed + _inputPtr, // bytes, chars
_currInputRow, col);
}
/*
/**********************************************************
/* Public API, access to token information, text and similar
/**********************************************************
*/
@Override
public boolean hasTextCharacters()
{
if (_currToken == JsonToken.VALUE_STRING) {
return true; // usually true
}
if (_currToken == JsonToken.FIELD_NAME) {
return _nameCopied;
}
return false;
}
// No embedded objects with base impl...
@Override
public Object getEmbeddedObject() throws IOException, JsonParseException {
return null;
}
/*
/**********************************************************
/* Public low-level accessors
/**********************************************************
*/
public long getTokenCharacterOffset() { return _tokenInputTotal; }
public int getTokenLineNr() { return _tokenInputRow; }
public int getTokenColumnNr() {
// note: value of -1 means "not available"; otherwise convert from 0-based to 1-based
int col = _tokenInputCol;
return (col < 0) ? col : (col + 1);
}
/*
/**********************************************************
/* Low-level reading, other
/**********************************************************
*/
protected final void loadMoreGuaranteed()
throws IOException
{
if (!loadMore()) {
_reportInvalidEOF();
}
}
/*
/**********************************************************
/* Abstract methods needed from sub-classes
/**********************************************************
*/
protected abstract boolean loadMore() throws IOException;
protected abstract void _finishString() throws IOException, JsonParseException;
protected abstract void _closeInput() throws IOException;
/*
/**********************************************************
/* Low-level reading, other
/**********************************************************
*/
/**
* Method called to release internal buffers owned by the base
* reader. This may be called along with {@link #_closeInput} (for
* example, when explicitly closing this reader instance), or
* separately (if need be).
*/
protected void _releaseBuffers() throws IOException
{
_textBuffer.releaseBuffers();
char[] buf = _nameCopyBuffer;
if (buf != null) {
_nameCopyBuffer = null;
_ioContext.releaseNameCopyBuffer(buf);
}
}
/**
* Method called when an EOF is encountered between tokens.
* If so, it may be a legitimate EOF, but only iff there
* is no open non-root context.
*/
@Override
protected void _handleEOF() throws JsonParseException
{
if (!_parsingContext.inRoot()) {
_reportInvalidEOF(": expected close marker for "+_parsingContext.getTypeDesc()+" (from "+_parsingContext.getStartLocation(_ioContext.getSourceReference())+")");
}
}
/*
/**********************************************************
/* Internal/package methods: Error reporting
/**********************************************************
*/
protected void _reportMismatchedEndMarker(int actCh, char expCh)
throws JsonParseException
{
String startDesc = ""+_parsingContext.getStartLocation(_ioContext.getSourceReference());
_reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.getTypeDesc()+" starting at "+startDesc+")");
}
/*
/**********************************************************
/* Internal/package methods: shared/reusable builders
/**********************************************************
*/
public ByteArrayBuilder _getByteArrayBuilder()
{
if (_byteArrayBuilder == null) {
_byteArrayBuilder = new ByteArrayBuilder();
} else {
_byteArrayBuilder.reset();
}
return _byteArrayBuilder;
}
/*
/**********************************************************
/* Methods from former JsonNumericParserBase
/**********************************************************
*/
// // // Life-cycle of number-parsing
protected final JsonToken reset(boolean negative, int intLen, int fractLen, int expLen)
{
if (fractLen < 1 && expLen < 1) { // integer
return resetInt(negative, intLen);
}
return resetFloat(negative, intLen, fractLen, expLen);
}
protected final JsonToken resetInt(boolean negative, int intLen)
{
_numberNegative = negative;
_intLength = intLen;
_fractLength = 0;
_expLength = 0;
_numTypesValid = NR_UNKNOWN; // to force parsing
return JsonToken.VALUE_NUMBER_INT;
}
protected final JsonToken resetFloat(boolean negative, int intLen, int fractLen, int expLen)
{
_numberNegative = negative;
_intLength = intLen;
_fractLength = fractLen;
_expLength = expLen;
_numTypesValid = NR_UNKNOWN; // to force parsing
return JsonToken.VALUE_NUMBER_FLOAT;
}
protected final JsonToken resetAsNaN(String valueStr, double value)
{
_textBuffer.resetWithString(valueStr);
_numberDouble = value;
_numTypesValid = NR_DOUBLE;
return JsonToken.VALUE_NUMBER_FLOAT;
}
/*
/**********************************************************
/* Numeric accessors of public API
/**********************************************************
*/
@Override
public Number getNumberValue() throws IOException, JsonParseException
{
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_UNKNOWN); // will also check event type
}
// Separate types for int types
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
if ((_numTypesValid & NR_INT) != 0) {
return _numberInt;
}
if ((_numTypesValid & NR_LONG) != 0) {
return _numberLong;
}
if ((_numTypesValid & NR_BIGINT) != 0) {
return _numberBigInt;
}
// Shouldn't get this far but if we do
return _numberBigDecimal;
}
/* And then floating point types. But here optimal type
* needs to be big decimal, to avoid losing any data?
*/
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
return _numberBigDecimal;
}
if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check
_throwInternal();
}
return _numberDouble;
}
@Override
public NumberType getNumberType() throws IOException, JsonParseException
{
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_UNKNOWN); // will also check event type
}
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
if ((_numTypesValid & NR_INT) != 0) {
return NumberType.INT;
}
if ((_numTypesValid & NR_LONG) != 0) {
return NumberType.LONG;
}
return NumberType.BIG_INTEGER;
}
/* And then floating point types. Here optimal type
* needs to be big decimal, to avoid losing any data?
* However... using BD is slow, so let's allow returning
* double as type if no explicit call has been made to access
* data as BD?
*/
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
return NumberType.BIG_DECIMAL;
}
return NumberType.DOUBLE;
}
@Override
public int getIntValue() throws IOException, JsonParseException
{
if ((_numTypesValid & NR_INT) == 0) {
if (_numTypesValid == NR_UNKNOWN) { // not parsed at all
_parseNumericValue(NR_INT); // will also check event type
}
if ((_numTypesValid & NR_INT) == 0) { // wasn't an int natively?
convertNumberToInt(); // let's make it so, if possible
}
}
return _numberInt;
}
@Override
public long getLongValue() throws IOException, JsonParseException
{
if ((_numTypesValid & NR_LONG) == 0) {
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_LONG);
}
if ((_numTypesValid & NR_LONG) == 0) {
convertNumberToLong();
}
}
return _numberLong;
}
@Override
public BigInteger getBigIntegerValue() throws IOException, JsonParseException
{
if ((_numTypesValid & NR_BIGINT) == 0) {
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_BIGINT);
}
if ((_numTypesValid & NR_BIGINT) == 0) {
convertNumberToBigInteger();
}
}
return _numberBigInt;
}
@Override
public float getFloatValue() throws IOException, JsonParseException
{
double value = getDoubleValue();
/* 22-Jan-2009, tatu: Bounds/range checks would be tricky
* here, so let's not bother even trying...
*/
/*
if (value < -Float.MAX_VALUE || value > MAX_FLOAT_D) {
_reportError("Numeric value ("+getText()+") out of range of Java float");
}
*/
return (float) value;
}
@Override
public double getDoubleValue() throws IOException, JsonParseException
{
if ((_numTypesValid & NR_DOUBLE) == 0) {
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_DOUBLE);
}
if ((_numTypesValid & NR_DOUBLE) == 0) {
convertNumberToDouble();
}
}
return _numberDouble;
}
@Override
public BigDecimal getDecimalValue() throws IOException, JsonParseException
{
if ((_numTypesValid & NR_BIGDECIMAL) == 0) {
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_BIGDECIMAL);
}
if ((_numTypesValid & NR_BIGDECIMAL) == 0) {
convertNumberToBigDecimal();
}
}
return _numberBigDecimal;
}
/*
/**********************************************************
/* Conversion from textual to numeric representation
/**********************************************************
*/
/**
* Method that will parse actual numeric value out of a syntactically
* valid number value. Type it will parse into depends on whether
* it is a floating point number, as well as its magnitude: smallest
* legal type (of ones available) is used for efficiency.
*
* @param expType Numeric type that we will immediately need, if any;
* mostly necessary to optimize handling of floating point numbers
*/
protected void _parseNumericValue(int expType)
throws IOException, JsonParseException
{
// Int or float?
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
char[] buf = _textBuffer.getTextBuffer();
int offset = _textBuffer.getTextOffset();
int len = _intLength;
if (_numberNegative) {
++offset;
}
if (len <= 9) { // definitely fits in int
int i = NumberInput.parseInt(buf, offset, len);
_numberInt = _numberNegative ? -i : i;
_numTypesValid = NR_INT;
return;
}
if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls
long l = NumberInput.parseLong(buf, offset, len);
if (_numberNegative) {
l = -l;
}
// [JACKSON-230] Could still fit in int, need to check
if (len == 10) {
if (_numberNegative) {
if (l >= MIN_INT_L) {
_numberInt = (int) l;
_numTypesValid = NR_INT;
return;
}
} else {
if (l <= MAX_INT_L) {
_numberInt = (int) l;
_numTypesValid = NR_INT;
return;
}
}
}
_numberLong = l;
_numTypesValid = NR_LONG;
return;
}
_parseSlowInt(expType, buf, offset, len);
return;
}
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
_parseSlowFloat(expType);
return;
}
_reportError("Current token ("+_currToken+") not numeric, can not use numeric value accessors");
}
private void _parseSlowFloat(int expType)
throws IOException, JsonParseException
{
/* Nope: floating point. Here we need to be careful to get
* optimal parsing strategy: choice is between accurate but
* slow (BigDecimal) and lossy but fast (Double). For now
* let's only use BD when explicitly requested -- it can
* still be constructed correctly at any point since we do
* retain textual representation
*/
try {
if (expType == NR_BIGDECIMAL) {
_numberBigDecimal = _textBuffer.contentsAsDecimal();
_numTypesValid = NR_BIGDECIMAL;
} else {
// Otherwise double has to do
_numberDouble = _textBuffer.contentsAsDouble();
_numTypesValid = NR_DOUBLE;
}
} catch (NumberFormatException nex) {
// Can this ever occur? Due to overflow, maybe?
_wrapError("Malformed numeric value '"+_textBuffer.contentsAsString()+"'", nex);
}
}
private void _parseSlowInt(int expType, char[] buf, int offset, int len)
throws IOException, JsonParseException
{
String numStr = _textBuffer.contentsAsString();
try {
// [JACKSON-230] Some long cases still...
if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) {
// Probably faster to construct a String, call parse, than to use BigInteger
_numberLong = Long.parseLong(numStr);
_numTypesValid = NR_LONG;
} else {
// nope, need the heavy guns... (rare case)
_numberBigInt = new BigInteger(numStr);
_numTypesValid = NR_BIGINT;
}
} catch (NumberFormatException nex) {
// Can this ever occur? Due to overflow, maybe?
_wrapError("Malformed numeric value '"+numStr+"'", nex);
}
}
/*
/**********************************************************
/* Numeric conversions
/**********************************************************
*/
protected void convertNumberToInt()
throws IOException, JsonParseException
{
// First, converting from long ought to be easy
if ((_numTypesValid & NR_LONG) != 0) {
// Let's verify it's lossless conversion by simple roundtrip
int result = (int) _numberLong;
if (((long) result) != _numberLong) {
_reportError("Numeric value ("+getText()+") out of range of int");
}
_numberInt = result;
} else if ((_numTypesValid & NR_BIGINT) != 0) {
if (BI_MIN_INT.compareTo(_numberBigInt) > 0
|| BI_MAX_INT.compareTo(_numberBigInt) < 0) {
reportOverflowInt();
}
_numberInt = _numberBigInt.intValue();
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
// Need to check boundaries
if (_numberDouble < MIN_INT_D || _numberDouble > MAX_INT_D) {
reportOverflowInt();
}
_numberInt = (int) _numberDouble;
} else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
if (BD_MIN_INT.compareTo(_numberBigDecimal) > 0
|| BD_MAX_INT.compareTo(_numberBigDecimal) < 0) {
reportOverflowInt();
}
_numberInt = _numberBigDecimal.intValue();
} else {
_throwInternal();
}
_numTypesValid |= NR_INT;
}
protected void convertNumberToLong()
throws IOException, JsonParseException
{
if ((_numTypesValid & NR_INT) != 0) {
_numberLong = (long) _numberInt;
} else if ((_numTypesValid & NR_BIGINT) != 0) {
if (BI_MIN_LONG.compareTo(_numberBigInt) > 0
|| BI_MAX_LONG.compareTo(_numberBigInt) < 0) {
reportOverflowLong();
}
_numberLong = _numberBigInt.longValue();
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
// Need to check boundaries
if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) {
reportOverflowLong();
}
_numberLong = (long) _numberDouble;
} else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
if (BD_MIN_LONG.compareTo(_numberBigDecimal) > 0
|| BD_MAX_LONG.compareTo(_numberBigDecimal) < 0) {
reportOverflowLong();
}
_numberLong = _numberBigDecimal.longValue();
} else {
_throwInternal();
}
_numTypesValid |= NR_LONG;
}
protected void convertNumberToBigInteger()
throws IOException, JsonParseException
{
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
// here it'll just get truncated, no exceptions thrown
_numberBigInt = _numberBigDecimal.toBigInteger();
} else if ((_numTypesValid & NR_LONG) != 0) {
_numberBigInt = BigInteger.valueOf(_numberLong);
} else if ((_numTypesValid & NR_INT) != 0) {
_numberBigInt = BigInteger.valueOf(_numberInt);
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
_numberBigInt = BigDecimal.valueOf(_numberDouble).toBigInteger();
} else {
_throwInternal();
}
_numTypesValid |= NR_BIGINT;
}
protected void convertNumberToDouble()
throws IOException, JsonParseException
{
/* 05-Aug-2008, tatus: Important note: this MUST start with
* more accurate representations, since we don't know which
* value is the original one (others get generated when
* requested)
*/
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
_numberDouble = _numberBigDecimal.doubleValue();
} else if ((_numTypesValid & NR_BIGINT) != 0) {
_numberDouble = _numberBigInt.doubleValue();
} else if ((_numTypesValid & NR_LONG) != 0) {
_numberDouble = (double) _numberLong;
} else if ((_numTypesValid & NR_INT) != 0) {
_numberDouble = (double) _numberInt;
} else {
_throwInternal();
}
_numTypesValid |= NR_DOUBLE;
}
protected void convertNumberToBigDecimal()
throws IOException, JsonParseException
{
/* 05-Aug-2008, tatus: Important note: this MUST start with
* more accurate representations, since we don't know which
* value is the original one (others get generated when
* requested)
*/
if ((_numTypesValid & NR_DOUBLE) != 0) {
/* Let's actually parse from String representation,
* to avoid rounding errors that non-decimal floating operations
* would incur
*/
_numberBigDecimal = NumberInput.parseBigDecimal(getText());
} else if ((_numTypesValid & NR_BIGINT) != 0) {
_numberBigDecimal = new BigDecimal(_numberBigInt);
} else if ((_numTypesValid & NR_LONG) != 0) {
_numberBigDecimal = BigDecimal.valueOf(_numberLong);
} else if ((_numTypesValid & NR_INT) != 0) {
_numberBigDecimal = BigDecimal.valueOf(_numberInt);
} else {
_throwInternal();
}
_numTypesValid |= NR_BIGDECIMAL;
}
/*
/**********************************************************
/* Number handling exceptions
/**********************************************************
*/
protected void reportUnexpectedNumberChar(int ch, String comment)
throws JsonParseException
{
String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value";
if (comment != null) {
msg += ": "+comment;
}
_reportError(msg);
}
protected void reportInvalidNumber(String msg)
throws JsonParseException
{
_reportError("Invalid numeric value: "+msg);
}
protected void reportOverflowInt()
throws IOException, JsonParseException
{
_reportError("Numeric value ("+getText()+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")");
}
protected void reportOverflowLong()
throws IOException, JsonParseException
{
_reportError("Numeric value ("+getText()+") out of range of long ("+Long.MIN_VALUE+" - "+Long.MAX_VALUE+")");
}
/*
/**********************************************************
/* Base64 handling support
/**********************************************************
*/
/**
* Method that sub-classes must implement to support escaped sequences
* in base64-encoded sections.
* Sub-classes that do not need base64 support can leave this as is
*/
protected char _decodeEscaped()
throws IOException, JsonParseException {
throw new UnsupportedOperationException();
}
protected final int _decodeBase64Escape(Base64Variant b64variant, int ch, int index)
throws IOException, JsonParseException
{
// 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
if (ch != '\\') {
throw reportInvalidBase64Char(b64variant, ch, index);
}
int unescaped = _decodeEscaped();
// if white space, skip if first triplet; otherwise errors
if (unescaped <= INT_SPACE) {
if (index == 0) { // whitespace only allowed to be skipped between triplets
return -1;
}
}
// otherwise try to find actual triplet value
int bits = b64variant.decodeBase64Char(unescaped);
if (bits < 0) {
throw reportInvalidBase64Char(b64variant, unescaped, index);
}
return bits;
}
protected final int _decodeBase64Escape(Base64Variant b64variant, char ch, int index)
throws IOException, JsonParseException
{
// 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars
if (ch != '\\') {
throw reportInvalidBase64Char(b64variant, ch, index);
}
char unescaped = _decodeEscaped();
// if white space, skip if first triplet; otherwise errors
if (unescaped <= INT_SPACE) {
if (index == 0) { // whitespace only allowed to be skipped between triplets
return -1;
}
}
// otherwise try to find actual triplet value
int bits = b64variant.decodeBase64Char(unescaped);
if (bits < 0) {
throw reportInvalidBase64Char(b64variant, unescaped, index);
}
return bits;
}
protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex)
throws IllegalArgumentException
{
return reportInvalidBase64Char(b64variant, ch, bindex, null);
}
/**
* @param bindex Relative index within base64 character unit; between 0
* and 3 (as unit has exactly 4 characters)
*/
protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex, String msg)
throws IllegalArgumentException
{
String base;
if (ch <= INT_SPACE) {
base = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units";
} else if (b64variant.usesPaddingChar(ch)) {
base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character";
} else if (!Character.isDefined(ch) || Character.isISOControl(ch)) {
// Not sure if we can really get here... ? (most illegal xml chars are caught at lower level)
base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content";
} else {
base = "Illegal character '"+((char)ch)+"' (code 0x"+Integer.toHexString(ch)+") in base64 content";
}
if (msg != null) {
base = base + ": " + msg;
}
return new IllegalArgumentException(base);
}
}