From cc5eb178f20b499bd6cef15380a4181cc1632399 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Mon, 23 May 2016 22:47:26 -0700 Subject: [PATCH] Fix #16 --- cbor/release-notes/VERSION | 1 + .../dataformat/cbor/CBORGenerator.java | 114 +++++++++-- .../dataformat/cbor/ArrayGenerationTest.java | 178 ++++++++++++++++++ .../jackson/dataformat/cbor/CBORTestBase.java | 1 - .../cbor/GeneratorLongStringTest.java | 3 + .../cbor/parse/BasicParserTest.java | 39 +++- .../smile/parse/UnicodeHandlingTest.java | 3 +- 7 files changed, 316 insertions(+), 23 deletions(-) create mode 100644 cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/ArrayGenerationTest.java diff --git a/cbor/release-notes/VERSION b/cbor/release-notes/VERSION index d8f440fbb..b03523781 100644 --- a/cbor/release-notes/VERSION +++ b/cbor/release-notes/VERSION @@ -6,6 +6,7 @@ Project: jackson-dataformat-cbor 2.8.0 (not yet released) +#16: Implement `JsonGenerator.writeArray()` methods added in `jackson-core` (2.8) #17: Support parsing of `BigInteger`, `BigDecimal`, not just generating #18: Fail to report error for trying to write field name outside Object (root level) diff --git a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java index 123f22ffb..da347fcdd 100644 --- a/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/com/fasterxml/jackson/dataformat/cbor/CBORGenerator.java @@ -513,8 +513,31 @@ public final void writeEndObject() throws IOException { } @Override // since 2.8 - public void writeArray(int[] array, int offset, int length) - throws IOException + public void writeArray(int[] array, int offset, int length) throws IOException + { + _verifyOffsets(array.length, offset, length); + // short-cut, do not create child array context etc + _verifyValueWrite("write int array"); + _writeLengthMarker(PREFIX_TYPE_ARRAY, length); + for (int i = offset, end = offset+length; i < end; ++i) { + _writeNumberNoCheck(array[i]); + } + } + + @Override // since 2.8 + public void writeArray(long[] array, int offset, int length) throws IOException + { + _verifyOffsets(array.length, offset, length); + // short-cut, do not create child array context etc + _verifyValueWrite("write int array"); + _writeLengthMarker(PREFIX_TYPE_ARRAY, length); + for (int i = offset, end = offset+length; i < end; ++i) { + _writeNumberNoCheck(array[i]); + } + } + + @Override // since 2.8 + public void writeArray(double[] array, int offset, int length) throws IOException { _verifyOffsets(array.length, offset, length); // short-cut, do not create child array context etc @@ -567,10 +590,59 @@ private final void _writeNumberNoCheck(int i) throws IOException { _outputBuffer[_outputTail++] = b0; } + private final void _writeNumberNoCheck(long l) throws IOException { + if (_cfgMinimalInts) { + if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) { + _writeNumberNoCheck((int) l); + return; + } + } + _ensureRoomForOutput(9); + if (l < 0L) { + l += 1; + l = -l; + _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_NEG + 27); + } else { + _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + 27); + } + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } + + private final void _writeNumberNoCheck(double d) throws IOException { + _verifyValueWrite("write number"); + _ensureRoomForOutput(11); + // 17-Apr-2010, tatu: could also use 'doubleToIntBits', but it seems + // more accurate to use exact representation; and possibly faster. + // However, if there are cases where collapsing of NaN was needed (for + // non-Java clients), this can be changed + long l = Double.doubleToRawLongBits(d); + _outputBuffer[_outputTail++] = BYTE_FLOAT64; + + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } + /* - * /********************************************************** /* Output - * method implementations, textual - * /********************************************************** + /*********************************************************** + /* Output method implementations, textual + /*********************************************************** */ @Override @@ -1094,11 +1166,10 @@ protected final void _ensureSpace(int needed) throws IOException { } protected final void _writeString(char[] text, int offset, int len) - throws IOException { - if (len <= MAX_SHORT_STRING_CHARS) { // possibly short strings (not - // necessarily) - _ensureSpace(MAX_SHORT_STRING_BYTES); // can afford approximate - // length + throws IOException + { + if (len <= MAX_SHORT_STRING_CHARS) { // possibly short strings (not necessarily) + _ensureSpace(MAX_SHORT_STRING_BYTES); // can afford approximate length int actual = _encode(_outputTail + 1, text, offset, offset + len); final byte[] buf = _outputBuffer; int ix = _outputTail; @@ -1115,8 +1186,7 @@ protected final void _writeString(char[] text, int offset, int len) return; } if (len <= MAX_MEDIUM_STRING_CHARS) { - _ensureSpace(MAX_MEDIUM_STRING_BYTES); // short enough, can - // approximate + _ensureSpace(MAX_MEDIUM_STRING_BYTES); // short enough, can approximate int actual = _encode(_outputTail + 2, text, offset, offset + len); final byte[] buf = _outputBuffer; int ix = _outputTail; @@ -1139,7 +1209,7 @@ protected final void _writeString(char[] text, int offset, int len) _ensureSpace(MAX_LONG_STRING_BYTES); // calculate accurate length to // avoid extra flushing int ix = _outputTail; - int actual = _encode(ix + 3, text, offset, offset + len); + int actual = _encode(ix + 3, text, offset, offset+len); final byte[] buf = _outputBuffer; buf[ix++] = BYTE_STRING_2BYTE_LEN; buf[ix++] = (byte) (actual >> 8); @@ -1151,16 +1221,21 @@ protected final void _writeString(char[] text, int offset, int len) } protected final void _writeChunkedString(char[] text, int offset, int len) - throws IOException { + throws IOException + { // need to use a marker first _writeByte(BYTE_STRING_INDEFINITE); while (len > MAX_LONG_STRING_CHARS) { - _ensureSpace(MAX_LONG_STRING_BYTES); // marker and single-byte - // length? + _ensureSpace(MAX_LONG_STRING_BYTES); // marker and single-byte length? int ix = _outputTail; - int actual = _encode(_outputTail + 3, text, offset, offset - + MAX_LONG_STRING_CHARS); + // 23-May-2016, tatu: Make sure NOT to try to split surrogates in half + int end = offset + MAX_LONG_STRING_CHARS; + char c = text[end-1]; + if (c >= SURR1_FIRST && c <= SURR1_LAST) { + --end; + } + int actual = _encode(_outputTail + 3, text, offset, end); final byte[] buf = _outputBuffer; buf[ix++] = BYTE_STRING_2BYTE_LEN; buf[ix++] = (byte) (actual >> 8); @@ -1223,8 +1298,7 @@ private final int _shortUTF8Encode2(char[] str, int i, int end, } // 3 or 4 bytes (surrogate) // Surrogates? - if (c < SURR1_FIRST || c > SURR2_LAST) { // nope, regular 3-byte - // character + if (c < SURR1_FIRST || c > SURR2_LAST) { // nope, regular 3-byte character outBuf[outputPtr++] = (byte) (0xe0 | (c >> 12)); outBuf[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f)); outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f)); diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/ArrayGenerationTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/ArrayGenerationTest.java new file mode 100644 index 000000000..cdec128b1 --- /dev/null +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/ArrayGenerationTest.java @@ -0,0 +1,178 @@ +package com.fasterxml.jackson.dataformat.cbor; + +import java.io.ByteArrayOutputStream; + +import com.fasterxml.jackson.core.*; + +/** + * Basic testing for scalar-array write methods added in 2.8. + */ +public class ArrayGenerationTest extends CBORTestBase +{ + private final CBORFactory FACTORY = new CBORFactory(); + + public void testIntArray() throws Exception + { + _testIntArray(false); + _testIntArray(true); + } + + public void testLongArray() throws Exception + { + _testLongArray(false); + _testLongArray(true); + } + + public void testDoubleArray() throws Exception + { + _testDoubleArray(false); + _testDoubleArray(true); + } + + private void _testIntArray(boolean useBytes) throws Exception { + // first special cases of 0, 1 values + _testIntArray(0, 0, 0); + _testIntArray(0, 1, 1); + + _testIntArray(1, 0, 0); + _testIntArray(1, 1, 1); + + // and then some bigger data + _testIntArray(15, 0, 0); + _testIntArray(15, 2, 3); + _testIntArray(39, 0, 0); + _testIntArray(39, 4, 0); + _testIntArray(271, 0, 0); + _testIntArray(271, 0, 4); + _testIntArray(666, 0, 0); + _testIntArray(789, 0, 4); + _testIntArray(5009, 0, 0); + _testIntArray(7777, 0, 1); + } + + private void _testLongArray(boolean useBytes) throws Exception { + // first special cases of 0, 1 values + _testLongArray(0, 0, 0); + _testLongArray(0, 1, 1); + + _testLongArray(1, 0, 0); + _testLongArray(1, 1, 1); + + // and then some bigger data + _testLongArray(15, 0, 0); + _testLongArray(15, 2, 3); + _testLongArray(39, 0, 0); + _testLongArray(39, 4, 0); + _testLongArray(271, 0, 0); + _testLongArray(271, 0, 4); + _testLongArray(911, 0, 0); + _testLongArray(1121, 0, 1); + _testLongArray(5009, 0, 0); + _testLongArray(6110, 0, 1); + } + + private void _testDoubleArray(boolean useBytes) throws Exception { + // first special cases of 0, 1 values + _testDoubleArray(0, 0, 0); + _testDoubleArray(0, 1, 1); + + _testDoubleArray(1, 0, 0); + _testDoubleArray(1, 1, 1); + + // and then some bigger data + _testDoubleArray(15, 0, 0); + _testDoubleArray(15, 2, 3); + _testDoubleArray(39, 0, 0); + _testDoubleArray(39, 4, 0); + _testDoubleArray(271, 0, 0); + _testDoubleArray(271, 0, 4); + _testDoubleArray(744, 0, 0); + _testDoubleArray(999, 0, 4); + _testDoubleArray(5009, 0, 0); + _testDoubleArray(7256, 0, 1); + } + + private void _testIntArray(int elements, int pre, int post) throws Exception + { + int[] values = new int[elements+pre+post]; + for (int i = pre, end = pre+elements; i < end; ++i) { + values[i] = i-pre; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + JsonGenerator gen = FACTORY.createGenerator(bytes); + gen.writeArray(values, pre, elements); + gen.close(); + + JsonParser p = FACTORY.createParser(bytes.toByteArray()); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + for (int i = 0; i < elements; ++i) { + if ((i & 1) == 0) { // alternate + JsonToken t = p.nextToken(); + if (t != JsonToken.VALUE_NUMBER_INT) { + fail("Expected number, got "+t+", element #"+i); + } + int act = p.getIntValue(); + if (act != i) { + fail("Entry #"+i+", expected "+i+", got "+act); + } + } else { + assertEquals(i, p.nextIntValue(-1)); + } + } + assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + + private void _testLongArray(int elements, int pre, int post) throws Exception + { + long[] values = new long[elements+pre+post]; + for (int i = pre, end = pre+elements; i < end; ++i) { + values[i] = i-pre; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + JsonGenerator gen = FACTORY.createGenerator(bytes); + gen.writeArray(values, pre, elements); + gen.close(); + JsonParser p = FACTORY.createParser(bytes.toByteArray()); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + for (int i = 0; i < elements; ++i) { + if ((i & 1) == 0) { // alternate + JsonToken t = p.nextToken(); + if (t != JsonToken.VALUE_NUMBER_INT) { + fail("Expected number, got "+t+", element #"+i); + } + long act = p.getLongValue(); + if (act != i) { + fail("Entry #"+i+", expected "+i+", got "+act); + } + } else { + assertEquals(i, p.nextLongValue(-1)); + } + } + assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + + private void _testDoubleArray(int elements, int pre, int post) throws Exception + { + double[] values = new double[elements+pre+post]; + for (int i = pre, end = pre+elements; i < end; ++i) { + values[i] = i-pre; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + JsonGenerator gen = FACTORY.createGenerator(bytes); + gen.writeArray(values, pre, elements); + gen.close(); + JsonParser p = FACTORY.createParser(bytes.toByteArray()); + assertToken(JsonToken.START_ARRAY, p.nextToken()); + for (int i = 0; i < elements; ++i) { + JsonToken t = p.nextToken(); + if (t != JsonToken.VALUE_NUMBER_FLOAT) { + fail("Expected floating-point number, got "+t+", element #"+i); + } + assertEquals((double) i, p.getDoubleValue()); + } + assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } +} diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java index 00b46f633..c91557005 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/CBORTestBase.java @@ -14,7 +14,6 @@ public abstract class CBORTestBase extends junit.framework.TestCase { - /* /********************************************************** /* Factory methods diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorLongStringTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorLongStringTest.java index 99efc0c04..3d971facc 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorLongStringTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/GeneratorLongStringTest.java @@ -71,6 +71,9 @@ private void _verifyStrings(JsonFactory f, byte[] input, List strings) assertToken(JsonToken.START_ARRAY, p.nextToken()); for (int i = 0, len = strings.size(); i < len; ++i) { assertToken(JsonToken.VALUE_STRING, p.nextToken()); + if ((i % 3) == 0) { // just for fun, try calling finish every now and then + p.finishToken(); + } assertEquals(strings.get(i), p.getText()); } assertToken(JsonToken.END_ARRAY, p.nextToken()); diff --git a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/BasicParserTest.java b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/BasicParserTest.java index 999689c03..867ec937c 100644 --- a/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/BasicParserTest.java +++ b/cbor/src/test/java/com/fasterxml/jackson/dataformat/cbor/parse/BasicParserTest.java @@ -235,7 +235,7 @@ private void _debugDiff(String expected, String actual) +Integer.toHexString(expected.charAt(i))+", got 0x" +Integer.toHexString(actual.charAt(i))); } - + public void testStringField() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); CBORGenerator generator = cborGenerator(out); @@ -247,7 +247,9 @@ public void testStringField() throws IOException { CBORParser parser = cborParser(out.toByteArray()); assertEquals(JsonToken.START_OBJECT, parser.nextToken()); assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals("a", parser.getCurrentName()); assertEquals(JsonToken.VALUE_STRING, parser.nextToken()); + assertEquals("a", parser.getCurrentName()); assertEquals("b", parser.getText()); assertEquals(1, parser.getTextLength()); assertEquals(JsonToken.END_OBJECT, parser.nextToken()); @@ -259,6 +261,41 @@ public void testStringField() throws IOException { parser.close(); } + public void testNestedObject() throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + CBORGenerator generator = cborGenerator(out); + generator.writeStartObject(); + generator.writeFieldName("ob"); + generator.writeStartObject(); + generator.writeNumberField("num", 3); + generator.writeEndObject(); + generator.writeFieldName("arr"); + generator.writeStartArray(); + generator.writeEndArray(); + generator.writeEndObject(); + generator.close(); + + CBORParser parser = cborParser(out.toByteArray()); + assertEquals(JsonToken.START_OBJECT, parser.nextToken()); + + assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals("ob", parser.getCurrentName()); + assertEquals(JsonToken.START_OBJECT, parser.nextToken()); + assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals("num", parser.getCurrentName()); + assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken()); + assertEquals(JsonToken.END_OBJECT, parser.nextToken()); + + assertEquals(JsonToken.FIELD_NAME, parser.nextToken()); + assertEquals("arr", parser.getCurrentName()); + assertEquals(JsonToken.START_ARRAY, parser.nextToken()); + assertEquals(JsonToken.END_ARRAY, parser.nextToken()); + + assertEquals(JsonToken.END_OBJECT, parser.nextToken()); + parser.close(); + } + public void testBufferRelease() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/UnicodeHandlingTest.java b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/UnicodeHandlingTest.java index 8ebb12595..d1970deaa 100644 --- a/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/UnicodeHandlingTest.java +++ b/smile/src/test/java/com/fasterxml/jackson/dataformat/smile/parse/UnicodeHandlingTest.java @@ -22,9 +22,10 @@ public void testShortUnicodeWithSurrogates() throws IOException public void testLongUnicodeWithSurrogates() throws IOException { + _testLongUnicodeWithSurrogates(700, false); + _testLongUnicodeWithSurrogates(900, true); _testLongUnicodeWithSurrogates(9600, false); _testLongUnicodeWithSurrogates(9600, true); - _testLongUnicodeWithSurrogates(900, false); } private void _testLongUnicodeWithSurrogates(int length,