Skip to content

Commit

Permalink
Fixed #63
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Apr 5, 2017
1 parent 6ab11a8 commit 4510155
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

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

import org.apache.avro.io.BinaryDecoder;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.dataformat.avro.AvroParser;
import com.fasterxml.jackson.dataformat.avro.AvroSchema;
import com.fasterxml.jackson.dataformat.avro.CodecRecycler;
Expand All @@ -21,7 +24,7 @@ public class AvroParserImpl extends AvroParser

/*
/**********************************************************
/* Input source config, state
/* Input source config
/**********************************************************
*/

Expand Down Expand Up @@ -55,16 +58,36 @@ public class AvroParserImpl extends AvroParser
*/
protected BinaryDecoder _decoder;

/*
/**********************************************************
/* Other decoding state
/**********************************************************
*/

/**
* Index of the union branch that was followed to reach the current token. This is cleared when the next token is read.
*
* @since 2.9
*/
protected int _branchIndex;

/**
* Index of the enum that was read as the current token. This is cleared when the next token is read.
*
* @since 2.9
*/
protected int _enumIndex;

/**
* Value if decoded directly as `float`.
*<p>
* NOTE: base class (`ParserBase`) has other value storage, but since JSON
* has no distinction between double, float, only includes `float`.
*
* @since 2.9
*/
protected float _numberFloat;

/*
/**********************************************************
/* Life-cycle
Expand Down Expand Up @@ -221,6 +244,283 @@ protected void _initSchema(AvroSchema schema) throws JsonProcessingException {
_avroContext = new RootReader(this, schema.getReader());
}

/*
/**********************************************************
/* Numeric accessors of public API
/**********************************************************
*/

@Override // since 2.9
public boolean isNaN() {
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
if ((_numTypesValid & NR_DOUBLE) != 0) {
// 10-Mar-2017, tatu: Alas, `Double.isFinite(d)` only added in JDK 8
double d = _numberDouble;
return Double.isNaN(d) || Double.isInfinite(d);
}
if ((_numTypesValid & NR_FLOAT) != 0) {
float f = _numberFloat;
return Float.isNaN(f) || Float.isInfinite(f);
}
}
return false;
}

@Override
public Number getNumberValue() throws IOException
{
if (_numTypesValid == NR_UNKNOWN) {
_checkNumericValue(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) {
return _numberDouble;
}
if ((_numTypesValid & NR_FLOAT) == 0) { // sanity check
_throwInternal();
}
return _numberFloat;
}

@Override
public NumberType getNumberType() throws IOException
{
if (_numTypesValid == NR_UNKNOWN) {
_checkNumericValue(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 should 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;
}
if ((_numTypesValid & NR_DOUBLE) != 0) {
return NumberType.DOUBLE;
}
return NumberType.FLOAT;
}

@Override
public float getFloatValue() throws IOException
{
if ((_numTypesValid & NR_FLOAT) == 0) {
if (_numTypesValid == NR_UNKNOWN) {
_checkNumericValue(NR_FLOAT);
}
if ((_numTypesValid & NR_FLOAT) == 0) {
convertNumberToFloat();
}
}
// 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 _numberFloat;
}

/*
/**********************************************************
/* Numeric conversions
/**********************************************************
*/

protected void _checkNumericValue(int expType) throws IOException
{
// Int or float?
if (_currToken == JsonToken.VALUE_NUMBER_INT || _currToken == JsonToken.VALUE_NUMBER_FLOAT) {
return;
}
_reportError("Current token ("+getCurrentToken()+") not numeric, can not use numeric value accessors");
}

@Override
protected void convertNumberToInt() throws IOException
{
// 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_FLOAT) != 0) {
if (_numberFloat < MIN_INT_D || _numberFloat > MAX_INT_D) {
reportOverflowInt();
}
_numberInt = (int) _numberFloat;
} 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;
}

@Override
protected void convertNumberToLong() throws IOException
{
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) {
if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) {
reportOverflowLong();
}
_numberLong = (long) _numberDouble;
} else if ((_numTypesValid & NR_FLOAT) != 0) {
if (_numberFloat < MIN_LONG_D || _numberFloat > MAX_LONG_D) {
reportOverflowInt();
}
_numberLong = (long) _numberFloat;
} 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;
}

@Override
protected void convertNumberToBigInteger() throws IOException
{
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 if ((_numTypesValid & NR_FLOAT) != 0) {
_numberBigInt = BigDecimal.valueOf(_numberFloat).toBigInteger();
} else {
_throwInternal();
}
_numTypesValid |= NR_BIGINT;
}

protected void convertNumberToFloat() throws IOException
{
// 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) {
_numberFloat = _numberBigDecimal.floatValue();
} else if ((_numTypesValid & NR_BIGINT) != 0) {
_numberFloat = _numberBigInt.floatValue();
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
_numberFloat = (float) _numberDouble;
} else if ((_numTypesValid & NR_LONG) != 0) {
_numberFloat = (float) _numberLong;
} else if ((_numTypesValid & NR_INT) != 0) {
_numberFloat = (float) _numberInt;
} else {
_throwInternal();
}
_numTypesValid |= NR_FLOAT;
}

@Override
protected void convertNumberToDouble() throws IOException
{
// 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_FLOAT) != 0) {
_numberDouble = (double) _numberFloat;
} 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;
}

@Override
protected void convertNumberToBigDecimal() throws IOException
{
// 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) {
// 05-Apt-2017, tatu: Unlike with textual formats, we never have textual
// representation to work with here
_numberBigDecimal = new BigDecimal(_numberDouble);
} else if ((_numTypesValid & NR_FLOAT) != 0) {
_numberBigDecimal = new BigDecimal(_numberFloat);
} 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;
}

/*
/**********************************************************
/* Methods for AvroReadContext implementations: decoding
Expand Down Expand Up @@ -415,8 +715,8 @@ protected JsonToken setNumber(long v) {
}

protected JsonToken setNumber(float v) {
_numberDouble = v;
_numTypesValid = NR_DOUBLE;
_numberFloat = v;
_numTypesValid = NR_FLOAT;
return JsonToken.VALUE_NUMBER_FLOAT;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,7 @@ public Number getNumberValue() throws IOException
}
return _numberFloat;
}

@Override
public NumberType getNumberType() throws IOException
{
Expand Down Expand Up @@ -1682,7 +1682,7 @@ public BigInteger getBigIntegerValue() throws IOException
}
return _numberBigInt;
}

@Override
public float getFloatValue() throws IOException
{
Expand Down
1 change: 1 addition & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Modules:
(contributed by baharclerode@github)
#60 (avro): Add support for `@Union` and polymorphic types
(contributed by baharclerode@github)
#64: (avro): Implement native `float` handling for parser
#64: (proto): Implement native `float` handling for parser
#68 (proto): Getting "type not supported as root type by protobuf" for serialization
of short and UUID types
Expand Down

0 comments on commit 4510155

Please sign in to comment.