Skip to content

Commit 6cedcc4

Browse files
committed
Work on fix for #201 based on #200
1 parent 8dfb021 commit 6cedcc4

File tree

5 files changed

+135
-23
lines changed

5 files changed

+135
-23
lines changed

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

+49-10
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,6 @@ public int getMask() {
114114
*/
115115
private final static int MIN_BUFFER_LENGTH = (3 * 256) + 2;
116116

117-
private final static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE;
118-
private final static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE;
119-
120117
/**
121118
* Special value that is use to keep tracks of arrays and maps opened with infinite length
122119
*/
@@ -671,10 +668,48 @@ private final void _writeIntNoCheck(int i) throws IOException {
671668
_outputBuffer[_outputTail++] = b0;
672669
}
673670

671+
private final void _writeIntMinimal(int markerBase, int i) throws IOException
672+
{
673+
_ensureRoomForOutput(5);
674+
byte b0;
675+
if (i >= 0) {
676+
if (i < 24) {
677+
_outputBuffer[_outputTail++] = (byte) (markerBase + i);
678+
return;
679+
}
680+
if (i <= 0xFF) {
681+
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT8_ELEMENTS);
682+
_outputBuffer[_outputTail++] = (byte) i;
683+
return;
684+
}
685+
b0 = (byte) i;
686+
i >>= 8;
687+
if (i <= 0xFF) {
688+
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT16_ELEMENTS);
689+
_outputBuffer[_outputTail++] = (byte) i;
690+
_outputBuffer[_outputTail++] = b0;
691+
return;
692+
}
693+
} else {
694+
b0 = (byte) i;
695+
i >>= 8;
696+
}
697+
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS);
698+
_outputBuffer[_outputTail++] = (byte) (i >> 16);
699+
_outputBuffer[_outputTail++] = (byte) (i >> 8);
700+
_outputBuffer[_outputTail++] = (byte) i;
701+
_outputBuffer[_outputTail++] = b0;
702+
}
703+
674704
private final void _writeLongNoCheck(long l) throws IOException {
675705
if (_cfgMinimalInts) {
676-
if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) {
677-
_writeIntNoCheck((int) l);
706+
if (l >= 0) {
707+
if (l <= 0x100000000L) {
708+
_writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l);
709+
return;
710+
}
711+
} else if (l >= -0x100000000L) {
712+
_writeIntMinimal(PREFIX_TYPE_INT_NEG, (int) (-l - 1));
678713
return;
679714
}
680715
}
@@ -935,14 +970,18 @@ public void writeNumber(int i) throws IOException {
935970

936971
@Override
937972
public void writeNumber(long l) throws IOException {
938-
if (_cfgMinimalInts) {
939-
// First: maybe 32 bits is enough?
940-
if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) {
941-
writeNumber((int) l);
973+
_verifyValueWrite("write number");
974+
if (_cfgMinimalInts) { // maybe 32 bits is enough?
975+
if (l >= 0) {
976+
if (l <= 0x100000000L) {
977+
_writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l);
978+
return;
979+
}
980+
} else if (l >= -0x100000000L) {
981+
_writeIntMinimal(PREFIX_TYPE_INT_NEG, (int) (-l - 1));
942982
return;
943983
}
944984
}
945-
_verifyValueWrite("write number");
946985
_ensureRoomForOutput(9);
947986
if (l < 0L) {
948987
l += 1;

cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/gen/ArrayGenerationTest.java

+43-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import java.io.ByteArrayOutputStream;
44

55
import com.fasterxml.jackson.core.*;
6+
import com.fasterxml.jackson.core.JsonParser.NumberType;
67
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
8+
import com.fasterxml.jackson.dataformat.cbor.CBORGenerator;
9+
import com.fasterxml.jackson.dataformat.cbor.CBORParser;
710
import com.fasterxml.jackson.dataformat.cbor.CBORTestBase;
811

912
/**
@@ -15,23 +18,54 @@ public class ArrayGenerationTest extends CBORTestBase
1518

1619
public void testIntArray() throws Exception
1720
{
18-
_testIntArray(false);
19-
_testIntArray(true);
21+
_testIntArray();
2022
}
2123

2224
public void testLongArray() throws Exception
2325
{
24-
_testLongArray(false);
25-
_testLongArray(true);
26+
_testLongArray();
2627
}
2728

2829
public void testDoubleArray() throws Exception
2930
{
30-
_testDoubleArray(false);
31-
_testDoubleArray(true);
31+
_testDoubleArray();
3232
}
3333

34-
private void _testIntArray(boolean useBytes) throws Exception {
34+
public void testMinimalIntValues2() throws Exception
35+
{
36+
// Array with 2 values that can't be passed as `int`s but DO fit
37+
// CBOR 5-byte int (sign + 32-bits)
38+
final long[] input = new long[] {
39+
0xffffffffL, // max value that fits
40+
-0xffffffffL - 1 // min value that fits
41+
};
42+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
43+
CBORGenerator gen = FACTORY.createGenerator(bytes);
44+
assertTrue(gen.isEnabled(CBORGenerator.Feature.WRITE_MINIMAL_INTS));
45+
gen.writeArray(input, 0, 2);
46+
gen.close();
47+
48+
// With default settings, should get:
49+
byte[] encoded = bytes.toByteArray();
50+
assertEquals(11, encoded.length);
51+
52+
// then verify contents
53+
54+
CBORParser p = FACTORY.createParser(encoded);
55+
assertToken(JsonToken.START_ARRAY, p.nextToken());
56+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
57+
assertEquals(NumberType.LONG, p.getNumberType());
58+
assertEquals(input[0], p.getLongValue());
59+
assertToken(JsonToken.VALUE_NUMBER_INT, p.nextToken());
60+
assertEquals(NumberType.LONG, p.getNumberType());
61+
assertEquals(input[1], p.getLongValue());
62+
assertToken(JsonToken.END_ARRAY, p.nextToken());
63+
p.close();
64+
65+
// but then also check without minimization
66+
}
67+
68+
private void _testIntArray() throws Exception {
3569
// first special cases of 0, 1 values
3670
_testIntArray(0, 0, 0);
3771
_testIntArray(0, 1, 1);
@@ -52,7 +86,7 @@ private void _testIntArray(boolean useBytes) throws Exception {
5286
_testIntArray(7777, 0, 1);
5387
}
5488

55-
private void _testLongArray(boolean useBytes) throws Exception {
89+
private void _testLongArray() throws Exception {
5690
// first special cases of 0, 1 values
5791
_testLongArray(0, 0, 0);
5892
_testLongArray(0, 1, 1);
@@ -73,7 +107,7 @@ private void _testLongArray(boolean useBytes) throws Exception {
73107
_testLongArray(6110, 0, 1);
74108
}
75109

76-
private void _testDoubleArray(boolean useBytes) throws Exception {
110+
private void _testDoubleArray() throws Exception {
77111
// first special cases of 0, 1 values
78112
_testDoubleArray(0, 0, 0);
79113
_testDoubleArray(0, 1, 1);

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

+34-4
Original file line numberDiff line numberDiff line change
@@ -71,21 +71,48 @@ public void testMinimalIntValues() throws Exception
7171

7272
out = new ByteArrayOutputStream();
7373
gen = cborGenerator(out);
74-
gen.writeNumber(Integer.MAX_VALUE);
74+
gen.writeNumber((long) Integer.MAX_VALUE);
7575
gen.close();
7676
_verifyBytes(out.toByteArray(),
7777
(byte) (CBORConstants.PREFIX_TYPE_INT_POS + 26),
7878
(byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF);
7979

8080
out = new ByteArrayOutputStream();
8181
gen = cborGenerator(out);
82-
gen.writeNumber(Integer.MIN_VALUE);
82+
gen.writeNumber((long) Integer.MIN_VALUE);
8383
gen.close();
8484
_verifyBytes(out.toByteArray(),
8585
(byte) (CBORConstants.PREFIX_TYPE_INT_NEG + 26),
8686
(byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF);
8787
}
8888

89+
// [dataformats-binary#201]
90+
public void testMinimalIntValues2() throws Exception
91+
{
92+
ByteArrayOutputStream out;
93+
CBORGenerator gen;
94+
95+
out = new ByteArrayOutputStream();
96+
gen = cborGenerator(out);
97+
// -1 if coerced as int, BUT cbor encoding can fit in 32-bit integer since
98+
// sign is indicated by prefix
99+
gen.writeNumber(0xffffffffL);
100+
gen.close();
101+
_verifyBytes(out.toByteArray(),
102+
(byte) (CBORConstants.PREFIX_TYPE_INT_POS + 26),
103+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF);
104+
105+
out = new ByteArrayOutputStream();
106+
gen = cborGenerator(out);
107+
// similarly for negative numbers, we have 32-bit value bits, not 31
108+
// as with Java int
109+
gen.writeNumber(-0xffffffffL - 1);
110+
gen.close();
111+
_verifyBytes(out.toByteArray(),
112+
(byte) (CBORConstants.PREFIX_TYPE_INT_NEG + 26),
113+
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF);
114+
}
115+
89116
public void testIntValues() throws Exception
90117
{
91118
// first, single-byte
@@ -156,12 +183,15 @@ public void testLongValues() throws Exception
156183
{
157184
ByteArrayOutputStream out = new ByteArrayOutputStream();
158185
CBORGenerator gen = cborGenerator(out);
159-
long l = -1L + Integer.MIN_VALUE;
186+
187+
// 07-Apr-2020, tatu: Since minimization enabled by default,
188+
// need to use value that can not be minimized
189+
long l = ((long) Integer.MIN_VALUE) << 2;
160190
gen.writeNumber(l);
161191
gen.close();
162192
byte[] b = out.toByteArray();
163-
assertEquals((byte) (CBORConstants.PREFIX_TYPE_INT_NEG + 27), b[0]);
164193
assertEquals(9, b.length);
194+
assertEquals((byte) (CBORConstants.PREFIX_TYPE_INT_NEG + 27), b[0]);
165195
// could test full contents, but for now this shall suffice
166196
assertEquals(0, b[1]);
167197
assertEquals(0, b[2]);

release-notes/CREDITS-2.x

+6
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,9 @@ Binh Tran (ankel@github)
126126
* Reported, contributed fix for #192: (ion) Allow `IonObjectMapper` with class name annotation introspector
127127
to deserialize generic subtypes
128128
(2.11.0)
129+
130+
Jonas Konrad (yawkat@github)
131+
* Reported, contributed fix for #201: `CBORGenerator.Feature.WRITE_MINIMAL_INTS` does not write
132+
most compact form for all integers
133+
(2.11.0)
134+

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Project: jackson-datatypes-binaryModules:
1919
(contributed by Bryan H)
2020
#198: `jackson-databind` should not be full dependency for (cbor, protobuf, smile)
2121
modules
22+
#201: `CBORGenerator.Feature.WRITE_MINIMAL_INTS` does not write most compact form
23+
for all integers
24+
(reported by Jonas K)
2225
- `AvroGenerator` overrides `getOutputContext()` properly
2326

2427
2.10.3 (03-Mar-2020)

0 commit comments

Comments
 (0)