Skip to content

Commit 4510155

Browse files
committed
Fixed #63
1 parent 6ab11a8 commit 4510155

File tree

3 files changed

+306
-5
lines changed

3 files changed

+306
-5
lines changed

avro/src/main/java/com/fasterxml/jackson/dataformat/avro/deser/AvroParserImpl.java

+303-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import java.io.IOException;
44
import java.io.InputStream;
5+
import java.math.BigDecimal;
6+
import java.math.BigInteger;
57

68
import org.apache.avro.io.BinaryDecoder;
79

810
import com.fasterxml.jackson.core.*;
911
import com.fasterxml.jackson.core.io.IOContext;
12+
import com.fasterxml.jackson.core.io.NumberInput;
1013
import com.fasterxml.jackson.dataformat.avro.AvroParser;
1114
import com.fasterxml.jackson.dataformat.avro.AvroSchema;
1215
import com.fasterxml.jackson.dataformat.avro.CodecRecycler;
@@ -21,7 +24,7 @@ public class AvroParserImpl extends AvroParser
2124

2225
/*
2326
/**********************************************************
24-
/* Input source config, state
27+
/* Input source config
2528
/**********************************************************
2629
*/
2730

@@ -55,16 +58,36 @@ public class AvroParserImpl extends AvroParser
5558
*/
5659
protected BinaryDecoder _decoder;
5760

61+
/*
62+
/**********************************************************
63+
/* Other decoding state
64+
/**********************************************************
65+
*/
66+
5867
/**
5968
* Index of the union branch that was followed to reach the current token. This is cleared when the next token is read.
69+
*
70+
* @since 2.9
6071
*/
6172
protected int _branchIndex;
6273

6374
/**
6475
* Index of the enum that was read as the current token. This is cleared when the next token is read.
76+
*
77+
* @since 2.9
6578
*/
6679
protected int _enumIndex;
6780

81+
/**
82+
* Value if decoded directly as `float`.
83+
*<p>
84+
* NOTE: base class (`ParserBase`) has other value storage, but since JSON
85+
* has no distinction between double, float, only includes `float`.
86+
*
87+
* @since 2.9
88+
*/
89+
protected float _numberFloat;
90+
6891
/*
6992
/**********************************************************
7093
/* Life-cycle
@@ -221,6 +244,283 @@ protected void _initSchema(AvroSchema schema) throws JsonProcessingException {
221244
_avroContext = new RootReader(this, schema.getReader());
222245
}
223246

247+
/*
248+
/**********************************************************
249+
/* Numeric accessors of public API
250+
/**********************************************************
251+
*/
252+
253+
@Override // since 2.9
254+
public boolean isNaN() {
255+
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
256+
if ((_numTypesValid & NR_DOUBLE) != 0) {
257+
// 10-Mar-2017, tatu: Alas, `Double.isFinite(d)` only added in JDK 8
258+
double d = _numberDouble;
259+
return Double.isNaN(d) || Double.isInfinite(d);
260+
}
261+
if ((_numTypesValid & NR_FLOAT) != 0) {
262+
float f = _numberFloat;
263+
return Float.isNaN(f) || Float.isInfinite(f);
264+
}
265+
}
266+
return false;
267+
}
268+
269+
@Override
270+
public Number getNumberValue() throws IOException
271+
{
272+
if (_numTypesValid == NR_UNKNOWN) {
273+
_checkNumericValue(NR_UNKNOWN); // will also check event type
274+
}
275+
// Separate types for int types
276+
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
277+
if ((_numTypesValid & NR_INT) != 0) {
278+
return _numberInt;
279+
}
280+
if ((_numTypesValid & NR_LONG) != 0) {
281+
return _numberLong;
282+
}
283+
if ((_numTypesValid & NR_BIGINT) != 0) {
284+
return _numberBigInt;
285+
}
286+
// Shouldn't get this far but if we do
287+
return _numberBigDecimal;
288+
}
289+
290+
// And then floating point types. But here optimal type
291+
// needs to be big decimal, to avoid losing any data?
292+
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
293+
return _numberBigDecimal;
294+
}
295+
if ((_numTypesValid & NR_DOUBLE) != 0) {
296+
return _numberDouble;
297+
}
298+
if ((_numTypesValid & NR_FLOAT) == 0) { // sanity check
299+
_throwInternal();
300+
}
301+
return _numberFloat;
302+
}
303+
304+
@Override
305+
public NumberType getNumberType() throws IOException
306+
{
307+
if (_numTypesValid == NR_UNKNOWN) {
308+
_checkNumericValue(NR_UNKNOWN); // will also check event type
309+
}
310+
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
311+
if ((_numTypesValid & NR_INT) != 0) {
312+
return NumberType.INT;
313+
}
314+
if ((_numTypesValid & NR_LONG) != 0) {
315+
return NumberType.LONG;
316+
}
317+
return NumberType.BIG_INTEGER;
318+
}
319+
320+
// And then floating point types. Here optimal type should be big decimal,
321+
// to avoid losing any data? However... using BD is slow, so let's allow returning
322+
// double as type if no explicit call has been made to access data as BD?
323+
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
324+
return NumberType.BIG_DECIMAL;
325+
}
326+
if ((_numTypesValid & NR_DOUBLE) != 0) {
327+
return NumberType.DOUBLE;
328+
}
329+
return NumberType.FLOAT;
330+
}
331+
332+
@Override
333+
public float getFloatValue() throws IOException
334+
{
335+
if ((_numTypesValid & NR_FLOAT) == 0) {
336+
if (_numTypesValid == NR_UNKNOWN) {
337+
_checkNumericValue(NR_FLOAT);
338+
}
339+
if ((_numTypesValid & NR_FLOAT) == 0) {
340+
convertNumberToFloat();
341+
}
342+
}
343+
// Bounds/range checks would be tricky here, so let's not bother even trying...
344+
/*
345+
if (value < -Float.MAX_VALUE || value > MAX_FLOAT_D) {
346+
_reportError("Numeric value ("+getText()+") out of range of Java float");
347+
}
348+
*/
349+
return _numberFloat;
350+
}
351+
352+
/*
353+
/**********************************************************
354+
/* Numeric conversions
355+
/**********************************************************
356+
*/
357+
358+
protected void _checkNumericValue(int expType) throws IOException
359+
{
360+
// Int or float?
361+
if (_currToken == JsonToken.VALUE_NUMBER_INT || _currToken == JsonToken.VALUE_NUMBER_FLOAT) {
362+
return;
363+
}
364+
_reportError("Current token ("+getCurrentToken()+") not numeric, can not use numeric value accessors");
365+
}
366+
367+
@Override
368+
protected void convertNumberToInt() throws IOException
369+
{
370+
// First, converting from long ought to be easy
371+
if ((_numTypesValid & NR_LONG) != 0) {
372+
// Let's verify it's lossless conversion by simple roundtrip
373+
int result = (int) _numberLong;
374+
if (((long) result) != _numberLong) {
375+
_reportError("Numeric value ("+getText()+") out of range of int");
376+
}
377+
_numberInt = result;
378+
} else if ((_numTypesValid & NR_BIGINT) != 0) {
379+
if (BI_MIN_INT.compareTo(_numberBigInt) > 0
380+
|| BI_MAX_INT.compareTo(_numberBigInt) < 0) {
381+
reportOverflowInt();
382+
}
383+
_numberInt = _numberBigInt.intValue();
384+
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
385+
// Need to check boundaries
386+
if (_numberDouble < MIN_INT_D || _numberDouble > MAX_INT_D) {
387+
reportOverflowInt();
388+
}
389+
_numberInt = (int) _numberDouble;
390+
} else if ((_numTypesValid & NR_FLOAT) != 0) {
391+
if (_numberFloat < MIN_INT_D || _numberFloat > MAX_INT_D) {
392+
reportOverflowInt();
393+
}
394+
_numberInt = (int) _numberFloat;
395+
} else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
396+
if (BD_MIN_INT.compareTo(_numberBigDecimal) > 0
397+
|| BD_MAX_INT.compareTo(_numberBigDecimal) < 0) {
398+
reportOverflowInt();
399+
}
400+
_numberInt = _numberBigDecimal.intValue();
401+
} else {
402+
_throwInternal();
403+
}
404+
_numTypesValid |= NR_INT;
405+
}
406+
407+
@Override
408+
protected void convertNumberToLong() throws IOException
409+
{
410+
if ((_numTypesValid & NR_INT) != 0) {
411+
_numberLong = (long) _numberInt;
412+
} else if ((_numTypesValid & NR_BIGINT) != 0) {
413+
if (BI_MIN_LONG.compareTo(_numberBigInt) > 0
414+
|| BI_MAX_LONG.compareTo(_numberBigInt) < 0) {
415+
reportOverflowLong();
416+
}
417+
_numberLong = _numberBigInt.longValue();
418+
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
419+
if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) {
420+
reportOverflowLong();
421+
}
422+
_numberLong = (long) _numberDouble;
423+
} else if ((_numTypesValid & NR_FLOAT) != 0) {
424+
if (_numberFloat < MIN_LONG_D || _numberFloat > MAX_LONG_D) {
425+
reportOverflowInt();
426+
}
427+
_numberLong = (long) _numberFloat;
428+
} else if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
429+
if (BD_MIN_LONG.compareTo(_numberBigDecimal) > 0
430+
|| BD_MAX_LONG.compareTo(_numberBigDecimal) < 0) {
431+
reportOverflowLong();
432+
}
433+
_numberLong = _numberBigDecimal.longValue();
434+
} else {
435+
_throwInternal();
436+
}
437+
_numTypesValid |= NR_LONG;
438+
}
439+
440+
@Override
441+
protected void convertNumberToBigInteger() throws IOException
442+
{
443+
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
444+
// here it'll just get truncated, no exceptions thrown
445+
_numberBigInt = _numberBigDecimal.toBigInteger();
446+
} else if ((_numTypesValid & NR_LONG) != 0) {
447+
_numberBigInt = BigInteger.valueOf(_numberLong);
448+
} else if ((_numTypesValid & NR_INT) != 0) {
449+
_numberBigInt = BigInteger.valueOf(_numberInt);
450+
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
451+
_numberBigInt = BigDecimal.valueOf(_numberDouble).toBigInteger();
452+
} else if ((_numTypesValid & NR_FLOAT) != 0) {
453+
_numberBigInt = BigDecimal.valueOf(_numberFloat).toBigInteger();
454+
} else {
455+
_throwInternal();
456+
}
457+
_numTypesValid |= NR_BIGINT;
458+
}
459+
460+
protected void convertNumberToFloat() throws IOException
461+
{
462+
// Note: this MUST start with more accurate representations, since we don't know which
463+
// value is the original one (others get generated when requested)
464+
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
465+
_numberFloat = _numberBigDecimal.floatValue();
466+
} else if ((_numTypesValid & NR_BIGINT) != 0) {
467+
_numberFloat = _numberBigInt.floatValue();
468+
} else if ((_numTypesValid & NR_DOUBLE) != 0) {
469+
_numberFloat = (float) _numberDouble;
470+
} else if ((_numTypesValid & NR_LONG) != 0) {
471+
_numberFloat = (float) _numberLong;
472+
} else if ((_numTypesValid & NR_INT) != 0) {
473+
_numberFloat = (float) _numberInt;
474+
} else {
475+
_throwInternal();
476+
}
477+
_numTypesValid |= NR_FLOAT;
478+
}
479+
480+
@Override
481+
protected void convertNumberToDouble() throws IOException
482+
{
483+
// Note: this MUST start with more accurate representations, since we don't know which
484+
// value is the original one (others get generated when requested)
485+
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
486+
_numberDouble = _numberBigDecimal.doubleValue();
487+
} else if ((_numTypesValid & NR_FLOAT) != 0) {
488+
_numberDouble = (double) _numberFloat;
489+
} else if ((_numTypesValid & NR_BIGINT) != 0) {
490+
_numberDouble = _numberBigInt.doubleValue();
491+
} else if ((_numTypesValid & NR_LONG) != 0) {
492+
_numberDouble = (double) _numberLong;
493+
} else if ((_numTypesValid & NR_INT) != 0) {
494+
_numberDouble = (double) _numberInt;
495+
} else {
496+
_throwInternal();
497+
}
498+
_numTypesValid |= NR_DOUBLE;
499+
}
500+
501+
@Override
502+
protected void convertNumberToBigDecimal() throws IOException
503+
{
504+
// Note: this MUST start with more accurate representations, since we don't know which
505+
// value is the original one (others get generated when requested)
506+
if ((_numTypesValid & NR_DOUBLE) != 0) {
507+
// 05-Apt-2017, tatu: Unlike with textual formats, we never have textual
508+
// representation to work with here
509+
_numberBigDecimal = new BigDecimal(_numberDouble);
510+
} else if ((_numTypesValid & NR_FLOAT) != 0) {
511+
_numberBigDecimal = new BigDecimal(_numberFloat);
512+
} else if ((_numTypesValid & NR_BIGINT) != 0) {
513+
_numberBigDecimal = new BigDecimal(_numberBigInt);
514+
} else if ((_numTypesValid & NR_LONG) != 0) {
515+
_numberBigDecimal = BigDecimal.valueOf(_numberLong);
516+
} else if ((_numTypesValid & NR_INT) != 0) {
517+
_numberBigDecimal = BigDecimal.valueOf(_numberInt);
518+
} else {
519+
_throwInternal();
520+
}
521+
_numTypesValid |= NR_BIGDECIMAL;
522+
}
523+
224524
/*
225525
/**********************************************************
226526
/* Methods for AvroReadContext implementations: decoding
@@ -415,8 +715,8 @@ protected JsonToken setNumber(long v) {
415715
}
416716

417717
protected JsonToken setNumber(float v) {
418-
_numberDouble = v;
419-
_numTypesValid = NR_DOUBLE;
718+
_numberFloat = v;
719+
_numTypesValid = NR_FLOAT;
420720
return JsonToken.VALUE_NUMBER_FLOAT;
421721
}
422722

cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORParser.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -1609,7 +1609,7 @@ public Number getNumberValue() throws IOException
16091609
}
16101610
return _numberFloat;
16111611
}
1612-
1612+
16131613
@Override
16141614
public NumberType getNumberType() throws IOException
16151615
{
@@ -1682,7 +1682,7 @@ public BigInteger getBigIntegerValue() throws IOException
16821682
}
16831683
return _numberBigInt;
16841684
}
1685-
1685+
16861686
@Override
16871687
public float getFloatValue() throws IOException
16881688
{

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Modules:
2121
(contributed by baharclerode@github)
2222
#60 (avro): Add support for `@Union` and polymorphic types
2323
(contributed by baharclerode@github)
24+
#64: (avro): Implement native `float` handling for parser
2425
#64: (proto): Implement native `float` handling for parser
2526
#68 (proto): Getting "type not supported as root type by protobuf" for serialization
2627
of short and UUID types

0 commit comments

Comments
 (0)