From 554f8db0f940b2a53f974852a2af194739d65200 Mon Sep 17 00:00:00 2001 From: Tatu Saloranta Date: Thu, 18 Aug 2016 23:09:52 -0700 Subject: [PATCH] Fix #307 --- release-notes/CREDITS | 6 +- release-notes/VERSION | 6 + .../jackson/core/json/UTF8JsonGenerator.java | 114 ++++++++++++------ .../core/json/RawValueWithSurrogatesTest.java | 89 ++++++++++++++ 4 files changed, 179 insertions(+), 36 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/core/json/RawValueWithSurrogatesTest.java diff --git a/release-notes/CREDITS b/release-notes/CREDITS index f05c38998f..463d97e4ab 100644 --- a/release-notes/CREDITS +++ b/release-notes/CREDITS @@ -79,8 +79,12 @@ Lokesh Kumar N (LokeshN@github) * Contributed #209: Make use of `_allowMultipleMatches` in `FilteringParserDelegate` (2.7.4) - Tanguy Leroux (tlrx@github) * Reported, contributed fix for #280: FilteringGeneratorDelegate.writeUTF8String() should delegate to writeUTF8String() (2.7.5) + +Mike Naseef (mtnaseef@github) + * Reported #307: JsonGenerationException: Split surrogate on writeRaw() input thrown for + input of a certain size + (2.7.7) diff --git a/release-notes/VERSION b/release-notes/VERSION index cdf032d6d8..1d372a3e42 100644 --- a/release-notes/VERSION +++ b/release-notes/VERSION @@ -14,6 +14,12 @@ JSON library. === Releases === ------------------------------------------------------------------------ +2.7.7 (not yet released) + +#307: JsonGenerationException: Split surrogate on writeRaw() input thrown for + input of a certain size + (reported by Mike N) + 2.7.6 (23-Jul-2016) - Clean up of FindBugs reported possible issues. diff --git a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java index 8f17ea32b0..a638519a7b 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -515,39 +515,50 @@ public void writeUTF8String(byte[] text, int offset, int len) throws IOException */ @Override - public void writeRaw(String text) - throws IOException, JsonGenerationException - { - int start = 0; - int len = text.length(); - while (len > 0) { - char[] buf = _charBuffer; - final int blen = buf.length; - final int len2 = (len < blen) ? len : blen; - text.getChars(start, start+len2, buf, 0); - writeRaw(buf, 0, len2); - start += len2; - len -= len2; - } + public void writeRaw(String text) throws IOException { + writeRaw(text, 0, text.length()); } @Override - public void writeRaw(String text, int offset, int len) - throws IOException, JsonGenerationException + public void writeRaw(String text, int offset, int len) throws IOException { + final char[] buf = _charBuffer; + + // minor optimization: see if we can just get and copy + if (len <= buf.length) { + text.getChars(offset, offset+len, buf, 0); + _writeRawSegment(buf, 0, len); + return; + } + + // If not, need segmented approach. For speed, let's also use input buffer + // size that is guaranteed to fit in output buffer; each char can expand to + // at most 3 bytes, so at most 1/3 of buffer size. + final int maxChunk = (_outputEnd >> 2) + (_outputEnd >> 4); // == (1/4 + 1/16) == 5/16 + final int maxBytes = maxChunk * 3; + while (len > 0) { - char[] buf = _charBuffer; - final int blen = buf.length; - final int len2 = (len < blen) ? len : blen; + int len2 = Math.min(maxChunk, len); text.getChars(offset, offset+len2, buf, 0); - writeRaw(buf, 0, len2); + if ((_outputTail + maxBytes) > _outputEnd) { + _flushBuffer(); + } + // If this is NOT the last segment and if the last character looks like + // split surrogate second half, drop it + if (len > 0) { + char ch = buf[len2-1]; + if ((ch >= SURR1_FIRST) && (ch <= SURR1_LAST)) { + --len2; + } + } + _writeRawSegment(buf, 0, len2); offset += len2; len -= len2; } } @Override - public void writeRaw(SerializableString text) throws IOException, JsonGenerationException + public void writeRaw(SerializableString text) throws IOException { byte[] raw = text.asUnquotedUTF8(); if (raw.length > 0) { @@ -567,8 +578,7 @@ public void writeRawValue(SerializableString text) throws IOException { // @TODO: rewrite for speed... @Override - public final void writeRaw(char[] cbuf, int offset, int len) - throws IOException, JsonGenerationException + public final void writeRaw(char[] cbuf, int offset, int len) throws IOException { // First: if we have 3 x charCount spaces, we know it'll fit just fine { @@ -610,8 +620,7 @@ public final void writeRaw(char[] cbuf, int offset, int len) } @Override - public void writeRaw(char ch) - throws IOException, JsonGenerationException + public void writeRaw(char ch) throws IOException { if ((_outputTail + 3) >= _outputEnd) { _flushBuffer(); @@ -631,14 +640,14 @@ public void writeRaw(char ch) * Helper method called when it is possible that output of raw section * to output may cross buffer boundary */ - private final void _writeSegmentedRaw(char[] cbuf, int offset, int len) - throws IOException, JsonGenerationException + private final void _writeSegmentedRaw(char[] cbuf, int offset, int len) throws IOException { final int end = _outputEnd; final byte[] bbuf = _outputBuffer; + final int inputEnd = offset + len; main_loop: - while (offset < len) { + while (offset < inputEnd) { inner_loop: while (true) { int ch = (int) cbuf[offset]; @@ -650,7 +659,7 @@ private final void _writeSegmentedRaw(char[] cbuf, int offset, int len) _flushBuffer(); } bbuf[_outputTail++] = (byte) ch; - if (++offset >= len) { + if (++offset >= inputEnd) { break main_loop; } } @@ -662,11 +671,45 @@ private final void _writeSegmentedRaw(char[] cbuf, int offset, int len) bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6)); bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f)); } else { - offset = _outputRawMultiByteChar(ch, cbuf, offset, len); + offset = _outputRawMultiByteChar(ch, cbuf, offset, inputEnd); } } } - + + /** + * Helper method that is called for segmented write of raw content + * when explicitly outputting a segment of longer thing. + * Caller has to take care of ensuring there's no split surrogate + * pair at the end (that is, last char can not be first part of a + * surrogate char pair). + * + * @since 2.8.2 + */ + private void _writeRawSegment(char[] cbuf, int offset, int end) throws IOException + { + main_loop: + while (offset < end) { + inner_loop: + while (true) { + int ch = (int) cbuf[offset]; + if (ch > 0x7F) { + break inner_loop; + } + _outputBuffer[_outputTail++] = (byte) ch; + if (++offset >= end) { + break main_loop; + } + } + char ch = cbuf[offset++]; + if (ch < 0x800) { // 2-byte? + _outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6)); + _outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f)); + } else { + offset = _outputRawMultiByteChar(ch, cbuf, offset, end); + } + } + } + /* /********************************************************** /* Output method implementations, base64-encoded binary @@ -1873,18 +1916,19 @@ private final int _readMore(InputStream in, * 1- and 2-byte UTF-8 encodings, when outputting "raw" * text (meaning it is not to be escaped or quoted) */ - private final int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputLen) + private final int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputEnd) throws IOException { // Let's handle surrogates gracefully (as 4 byte output): if (ch >= SURR1_FIRST) { if (ch <= SURR2_LAST) { // yes, outside of BMP // Do we have second part? - if (inputOffset >= inputLen || cbuf == null) { // nope... have to note down - _reportError("Split surrogate on writeRaw() input (last character)"); + if (inputOffset >= inputEnd || cbuf == null) { // nope... have to note down + _reportError(String.format( +"Split surrogate on writeRaw() input (last character): first character 0x%4x", ch)); } _outputSurrogates(ch, cbuf[inputOffset]); - return (inputOffset+1); + return inputOffset+1; } } final byte[] bbuf = _outputBuffer; diff --git a/src/test/java/com/fasterxml/jackson/core/json/RawValueWithSurrogatesTest.java b/src/test/java/com/fasterxml/jackson/core/json/RawValueWithSurrogatesTest.java new file mode 100644 index 0000000000..e94e4edbc9 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/json/RawValueWithSurrogatesTest.java @@ -0,0 +1,89 @@ +package com.fasterxml.jackson.core.json; + +import java.io.ByteArrayOutputStream; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; + +public class RawValueWithSurrogatesTest + extends com.fasterxml.jackson.core.BaseTest +{ + final String SURROGATES_307; + { + // This one fails: + String msg ="{'xxxxxxx':{'xxxx':'xxxxxxxxx','xx':'xxxxxxxxxxxxxxxxxxx','xxxxxxxxx':'xxxx://xxxxxxx.xxx'," ++"'xxxxxx':{'xxxx':'xxxxxxxxxxx','xxxxxxxx':{'xxxxxxxxxxx':'xx','xxxxxxxxxx':'xx-xx'}}," ++"'xxxxx':[{'xxxx':'xxxx','xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'}]}," ++"'xxxxxxxxxxx':[{'xxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}}," ++"{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'" ++",'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}}," ++"{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':" ++"[{'xxxxxx':3,'xxxxxx':123,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':24,'xxxxxx':4,'xxxx':'xxxxx'}},{'xxxxxx':0,'xxxxxx':123," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':123,'xxxxxx':1,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':1,'xxxxxx':123," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}}," ++"{'xxxxxx':x,'xxxxxx':123,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx," ++"'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'," ++"'xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]}]}"; + // This one works: +// String msg ="{'xxx':{'xxxx':'xxxxxxxxx','xx':'xxxxxxxxxxxxxxxxxxx','xxxxxxxxx':'xxxx://xxxxxxx.xxx','xxxxxx':{'xxxx':'xxxxxxxxxxx','xxxxxxxx':{'xxxxxxxxxxx':'xx','xxxxxxxxxx':'xx-xx'}},'xxxxx':[{'xxxx':'xxxx','xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍'}]},'xxxxxxxxxxx':[{'xxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]},{'xxxxxxxxxxx':'xxxxx','xxxxxxxx':[{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xx,'xxxxxx':x,'xxxx':'xxxxx'}},{'xxxxxx':x,'xxxxxx':xxx,'xxxx':'xx xxxxxxxxxxx: xxxxxxx xxxxxx xxxxxxxxxxxxx xxxxx xxxxxx. xxxxx xxxxxx xxxxx xxxxx. xx xxx xx xxxx xxx xxxx. xxxx xxxxx xxx xxxxxxxx xxxxx xxxxxx xxxxxxx😆👍','xxxxxx':{'xxxxxx':xxx,'xxxxxx':x,'xxxx':'xxxxx'}}]}]}"; + + SURROGATES_307 = aposToQuotes(msg); + } + + private final JsonFactory JSON_F = new JsonFactory(); + + // for [jackson-core#307] + public void testRawWithSurrogatesString() throws Exception { + _testRawWithSurrogatesString(false); + } + + // for [jackson-core#307] + public void testRawWithSurrogatesCharArray() throws Exception { + _testRawWithSurrogatesString(true); + } + + private void _testRawWithSurrogatesString(boolean useCharArray) throws Exception + { + // boundaries are not exact, may vary, so use this: + + final int OFFSET = 3; + final int COUNT = 100; + + for (int i = OFFSET; i < COUNT; ++i) { + StringBuilder sb = new StringBuilder(1000); + for (int j = 0; j < i; ++j) { + sb.append(' '); + } + sb.append(SURROGATES_307); + final String text = sb.toString(); + ByteArrayOutputStream out = new ByteArrayOutputStream(1000); + JsonGenerator g = JSON_F.createGenerator(out); + if (useCharArray) { + char[] ch = text.toCharArray(); + g.writeRawValue(ch, OFFSET, ch.length - OFFSET); + } else { + g.writeRawValue(text, OFFSET, text.length() - OFFSET); + } + g.close(); + byte[] b = out.toByteArray(); + assertNotNull(b); + } + } +}