Skip to content

Commit 2d57978

Browse files
committed
Fix #17, now BigInteger, BigDecimal round-trip correctly
1 parent 0568f83 commit 2d57978

File tree

6 files changed

+178
-82
lines changed

6 files changed

+178
-82
lines changed

cbor/release-notes/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Project: jackson-dataformat-cbor
66

77
2.8.0 (not yet released)
88

9+
#17: Support parsing of `BigInteger`, `BigDecimal`, not just generating
910
#18: Fail to report error for trying to write field name outside Object (root level)
1011

1112
2.7.3 (16-Mar-2016)

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

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,13 @@ public void writeNumber(BigInteger v) throws IOException
780780
return;
781781
}
782782
_verifyValueWrite("write number");
783-
783+
_write(v);
784+
}
785+
786+
// Main write method isolated so that it can be called directly
787+
// in cases where that is needed (to encode BigDecimal)
788+
protected void _write(BigInteger v) throws IOException
789+
{
784790
/* Supported by using type tags, as per spec: major type for tag '6';
785791
* 5 LSB either 2 for positive bignum or 3 for negative bignum.
786792
* And then byte sequence that encode variable length integer.
@@ -796,7 +802,7 @@ public void writeNumber(BigInteger v) throws IOException
796802
_writeLengthMarker(PREFIX_TYPE_BYTES, len);
797803
_writeBytes(data, 0, len);
798804
}
799-
805+
800806
@Override
801807
public void writeNumber(double d) throws IOException
802808
{
@@ -852,36 +858,25 @@ public void writeNumber(BigDecimal dec) throws IOException
852858
_verifyValueWrite("write number");
853859
/* Supported by using type tags, as per spec: major type for tag '6';
854860
* 5 LSB 4.
855-
* And then a two-int array, with mantissa and exponent
861+
* And then a two-element array; integer exponent, and int/bigint mantissa
856862
*/
857-
_writeByte(BYTE_TAG_BIGFLOAT);
863+
// 12-May-2016, tatu: Before 2.8, used "bigfloat", but that was incorrect...
864+
_writeByte(BYTE_TAG_DECIMAL_FRACTION);
858865
_writeByte(BYTE_ARRAY_2_ELEMENTS);
859866

860867
int scale = dec.scale();
861868
_writeIntValue(scale);
862-
863869
/* Hmmmh. Specification suggest use of regular integer for mantissa.
864-
* But... it may or may not fit. Let's try to do that, if it works;
865-
* if not, use byte array.
870+
* But if it doesn't fit, use "bignum"
866871
*/
867872
BigInteger unscaled = dec.unscaledValue();
868-
byte[] data = unscaled.toByteArray();
869-
if (data.length <= 4) {
870-
int v = data[0]; // let it be sign extended on purpose
871-
for (int i = 1; i < data.length; ++i) {
872-
v = (v << 8) + (data[i] & 0xFF);
873-
}
874-
_writeIntValue(v);
875-
} else if (data.length <= 8) {
876-
long v = data[0]; // let it be sign extended on purpose
877-
for (int i = 1; i < data.length; ++i) {
878-
v = (v << 8) + (data[i] & 0xFF);
879-
}
880-
_writeLongValue(v);
873+
int bitLength = unscaled.bitLength();
874+
if (bitLength <= 31) {
875+
_writeIntValue(unscaled.intValue());
876+
} else if (bitLength <= 63) {
877+
_writeLongValue(unscaled.longValue());
881878
} else {
882-
final int len = data.length;
883-
_writeLengthMarker(PREFIX_TYPE_BYTES, len);
884-
_writeBytes(data, 0, len);
879+
_write(unscaled);
885880
}
886881
}
887882

@@ -1307,8 +1302,7 @@ private final void _writeIntValue(int i) throws IOException
13071302
{
13081303
int marker;
13091304
if (i < 0) {
1310-
i += 1;
1311-
i = -1;
1305+
i = -i - 1;
13121306
marker = PREFIX_TYPE_INT_NEG;
13131307
} else {
13141308
marker = PREFIX_TYPE_INT_POS;
@@ -1321,7 +1315,7 @@ private final void _writeLongValue(long l) throws IOException
13211315
_ensureRoomForOutput(9);
13221316
if (l < 0) {
13231317
l += 1;
1324-
l = -1;
1318+
l = -l;
13251319
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_NEG + 27);
13261320
} else {
13271321
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + 27);

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

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
1616
import com.fasterxml.jackson.core.util.TextBuffer;
1717

18+
import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.*;
19+
1820
public final class CBORParser extends ParserMinimalBase
1921
{
2022
private final static byte[] NO_BYTES = new byte[0];
@@ -713,7 +715,7 @@ public JsonToken nextToken() throws IOException
713715
_typeByte = ch;
714716
_tokenIncomplete = true;
715717
if (_tagValue >= 0) {
716-
return _nextBinaryWithTag(_tagValue);
718+
return _handleTaggedBinary(_tagValue);
717719
}
718720
return (_currToken = JsonToken.VALUE_EMBEDDED_OBJECT);
719721

@@ -723,12 +725,14 @@ public JsonToken nextToken() throws IOException
723725
return (_currToken = JsonToken.VALUE_STRING);
724726

725727
case 4: // Array
726-
_currToken = JsonToken.START_ARRAY;
727728
{
728729
int len = _decodeExplicitLength(lowBits);
730+
if (_tagValue >= 0) {
731+
return _handleTaggedArray(_tagValue, len);
732+
}
729733
_parsingContext = _parsingContext.createChildArrayContext(len);
730734
}
731-
return _currToken;
735+
return (_currToken = JsonToken.START_ARRAY);
732736

733737
case 5: // Object
734738
_currToken = JsonToken.START_OBJECT;
@@ -816,14 +820,13 @@ protected String _numberToName(int ch, boolean neg) throws IOException
816820
return String.valueOf(1);
817821
}
818822

819-
protected JsonToken _nextBinaryWithTag(int tag) throws IOException
823+
protected JsonToken _handleTaggedBinary(int tag) throws IOException
820824
{
821825
// For now all we should get is BigInteger
822-
823826
boolean neg;
824-
if (tag == CBORConstants.TAG_BIGNUM_POS) {
827+
if (tag == TAG_BIGNUM_POS) {
825828
neg = false;
826-
} else if (tag == CBORConstants.TAG_BIGNUM_NEG) {
829+
} else if (tag == TAG_BIGNUM_NEG) {
827830
neg = true;
828831
} else {
829832
// 12-May-2016, tatu: Since that's all we know, let's otherwise
@@ -834,12 +837,68 @@ protected JsonToken _nextBinaryWithTag(int tag) throws IOException
834837
// First: get the data
835838
_finishToken();
836839

837-
_numberBigInt = new BigInteger(_binaryValue);
838-
_numTypesValid |= NR_BIGINT;
839-
840+
BigInteger nr = new BigInteger(_binaryValue);
841+
if (neg) {
842+
nr = nr.negate();
843+
}
844+
_numberBigInt = nr;
845+
_numTypesValid = NR_BIGINT;
840846
return (_currToken = JsonToken.VALUE_NUMBER_INT);
841847
}
842848

849+
protected JsonToken _handleTaggedArray(int tag, int len) throws IOException
850+
{
851+
// For simplicity, let's create matching array context -- in perfect
852+
// world that wouldn't be necessarily, but in this one there are
853+
// some constraints that make it necessary
854+
_parsingContext = _parsingContext.createChildArrayContext(len);
855+
856+
// BigDecimal is the only thing we know for sure
857+
if (tag != CBORConstants.TAG_DECIMAL_FRACTION) {
858+
return (_currToken = JsonToken.START_ARRAY);
859+
}
860+
_currToken = JsonToken.START_ARRAY;
861+
862+
// but has to have length of 2; otherwise we have a problem...
863+
if (len != 2) {
864+
_reportError("Unexpected array size ("+len+") for tagged 'bigfloat' value; should have exactly 2 number elements");
865+
}
866+
// and then use recursion to get values
867+
JsonToken t = nextToken();
868+
// First: exponent, which MUST be a simple integer value
869+
if (t != JsonToken.VALUE_NUMBER_INT) {
870+
_reportError("Unexpected token ("+t+") as the first part of 'bigfloat' value: should get VALUE_NUMBER_INT");
871+
}
872+
int exp = getIntValue();
873+
874+
t = nextToken();
875+
// Should get an integer value; int/long/BigInteger
876+
if (t != JsonToken.VALUE_NUMBER_INT) {
877+
_reportError("Unexpected token ("+t+") as the second part of 'bigfloat' value: should get VALUE_NUMBER_INT");
878+
}
879+
880+
BigDecimal dec;
881+
882+
switch (getNumberType()) {
883+
case INT:
884+
case LONG:
885+
dec = BigDecimal.valueOf(getLongValue(), exp);
886+
break;
887+
case BIG_INTEGER:
888+
default:
889+
dec = new BigDecimal(getBigIntegerValue(), exp);
890+
break;
891+
}
892+
893+
t = nextToken();
894+
if (t != JsonToken.END_ARRAY) {
895+
_reportError("Unexpected token ("+t+") after 2 elements of 'bigfloat' value");
896+
}
897+
_numberBigDecimal = dec;
898+
_numTypesValid = NR_BIGDECIMAL;
899+
return (_currToken = JsonToken.VALUE_NUMBER_FLOAT);
900+
}
901+
843902
// base impl is fine:
844903
//public String getCurrentName() throws IOException
845904

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.fasterxml.jackson.dataformat.cbor;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.math.BigDecimal;
5+
import java.math.BigInteger;
6+
7+
import com.fasterxml.jackson.core.JsonToken;
8+
import com.fasterxml.jackson.dataformat.cbor.*;
9+
10+
// tests for [cbor#17]
11+
public class BigNumbersTest extends CBORTestBase
12+
{
13+
public void testBigDecimal() throws Exception
14+
{
15+
_testBigDecimal(BigDecimal.ONE);
16+
_testBigDecimal(BigDecimal.ZERO);
17+
_testBigDecimal(BigDecimal.TEN);
18+
_testBigDecimal(BigDecimal.ONE.scaleByPowerOfTen(-1));
19+
_testBigDecimal(BigDecimal.ONE.scaleByPowerOfTen(-3));
20+
_testBigDecimal(BigDecimal.ONE.scaleByPowerOfTen(-100));
21+
_testBigDecimal(BigDecimal.ONE.scaleByPowerOfTen(3));
22+
_testBigDecimal(BigDecimal.ONE.scaleByPowerOfTen(137));
23+
24+
_testBigDecimal(new BigDecimal("0.01"));
25+
_testBigDecimal(new BigDecimal("0.33"));
26+
_testBigDecimal(new BigDecimal("1.1"));
27+
_testBigDecimal(new BigDecimal("900.373"));
28+
29+
BigDecimal bd = new BigDecimal("12345.667899024");
30+
_testBigDecimal(bd);
31+
_testBigDecimal(bd.negate());
32+
33+
// ensure mantissa is beyond long; more than 22 digits or so
34+
bd = new BigDecimal("1234567890.12345678901234567890");
35+
_testBigDecimal(bd);
36+
_testBigDecimal(bd.negate());
37+
}
38+
39+
private void _testBigDecimal(BigDecimal expValue) throws Exception
40+
{
41+
final ByteArrayOutputStream sourceBytes = new ByteArrayOutputStream();
42+
final CBORGenerator sourceGen = cborGenerator(sourceBytes);
43+
sourceGen.writeStartObject();
44+
sourceGen.writeFieldName("a");
45+
sourceGen.writeNumber(expValue);
46+
sourceGen.writeEndObject();
47+
sourceGen.close();
48+
49+
byte[] b = sourceBytes.toByteArray();
50+
51+
// but verify that the original content can be parsed
52+
CBORParser parser = cborParser(b);
53+
assertToken(JsonToken.START_OBJECT, parser.nextToken());
54+
assertToken(JsonToken.FIELD_NAME, parser.nextToken());
55+
assertEquals("a", parser.getCurrentName());
56+
assertToken(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
57+
assertEquals(expValue, parser.getDecimalValue());
58+
assertToken(JsonToken.END_OBJECT, parser.nextToken());
59+
parser.close();
60+
}
61+
62+
public void testBigInteger() throws Exception
63+
{
64+
_testBigInteger(BigInteger.TEN);
65+
_testBigInteger(BigInteger.TEN.negate());
66+
}
67+
68+
private void _testBigInteger(BigInteger expValue) throws Exception
69+
{
70+
final ByteArrayOutputStream sourceBytes = new ByteArrayOutputStream();
71+
final CBORGenerator sourceGen = cborGenerator(sourceBytes);
72+
sourceGen.writeNumber(expValue);
73+
sourceGen.close();
74+
75+
// but verify that the original content can be parsed
76+
CBORParser parser = cborParser(sourceBytes.toByteArray());
77+
assertToken(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
78+
assertEquals(expValue, parser.getBigIntegerValue());
79+
assertNull(parser.nextToken());
80+
parser.close();
81+
82+
// but wait... there's more. Negative variants
83+
}
84+
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorSimpleTest.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,10 @@ public void testCopyCurrentEventWithTag() throws Exception {
383383
// copyCurrentEvent doesn't preserve fixed arrays, so we can't
384384
// compare with the source bytes.
385385
Assert.assertArrayEquals(new byte[] {
386-
CBORConstants.BYTE_TAG_BIGFLOAT,
387-
CBORConstants.BYTE_ARRAY_INDEFINITE,
386+
CBORConstants.BYTE_TAG_DECIMAL_FRACTION,
387+
CBORConstants.BYTE_ARRAY_2_ELEMENTS,
388388
0,
389389
1,
390-
CBORConstants.BYTE_BREAK
391390
},
392391
targetBytes.toByteArray());
393392
}
@@ -409,11 +408,10 @@ public void testCopyCurrentSturctureWithTag() throws Exception {
409408
// copyCurrentEvent doesn't preserve fixed arrays, so we can't
410409
// compare with the source bytes.
411410
Assert.assertArrayEquals(new byte[] {
412-
CBORConstants.BYTE_TAG_BIGFLOAT,
413-
CBORConstants.BYTE_ARRAY_INDEFINITE,
411+
CBORConstants.BYTE_TAG_DECIMAL_FRACTION,
412+
CBORConstants.BYTE_ARRAY_2_ELEMENTS,
414413
0,
415414
1,
416-
CBORConstants.BYTE_BREAK
417415
},
418416
targetBytes.toByteArray());
419417
}

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/failing/BigNumbersTest.java

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)