From 54970737ae9e30a9ad6331f9c613f0ae3c1f4cb6 Mon Sep 17 00:00:00 2001 From: Logan Widick Date: Thu, 9 Feb 2017 16:21:58 -0600 Subject: [PATCH 1/4] Added proposed fix for Issue #17 --- .../fasterxml/jackson/core/JsonGenerator.java | 20 ++++- .../jackson/core/json/UTF8JsonGenerator.java | 58 +++++++++++++- .../core/json/WriterBasedJsonGenerator.java | 77 ++++++++++++++++++- .../core/util/JsonGeneratorDelegate.java | 12 ++- 4 files changed, 152 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java index da75a61129..02401d5444 100644 --- a/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java @@ -4,7 +4,9 @@ */ package com.fasterxml.jackson.core; -import static com.fasterxml.jackson.core.JsonTokenId.*; +import com.fasterxml.jackson.core.JsonParser.NumberType; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.util.VersionUtil; import java.io.*; import java.math.BigDecimal; @@ -13,9 +15,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import com.fasterxml.jackson.core.JsonParser.NumberType; -import com.fasterxml.jackson.core.io.CharacterEscapes; -import com.fasterxml.jackson.core.util.VersionUtil; +import static com.fasterxml.jackson.core.JsonTokenId.*; /** * Base class that defines public API for writing JSON content. @@ -932,6 +932,18 @@ public void writeArray(double[] array, int offset, int length) throws IOExceptio */ public abstract void writeString(String text) throws IOException; + /** + * Method for outputting a String value. Depending on context + * this means either array element, (object) field value or + * a stand alone String; but in all cases, String will be + * surrounded in double quotes, and contents will be properly + * escaped as required by JSON specification. + * If the reader is null, then write a null. + * If len is < 0, then write all contents of the reader. + * Otherwise, write only len characters. + */ + public abstract void writeString(Reader reader, int len) throws IOException; + /** * Method for outputting a String value. Depending on context * this means either array element, (object) field value or 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 80559d4cd8..edc1f3b5aa 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -1,12 +1,18 @@ package com.fasterxml.jackson.core.json; -import java.io.*; +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.io.CharTypes; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.core.io.NumberOutput; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; import java.math.BigDecimal; import java.math.BigInteger; -import com.fasterxml.jackson.core.*; -import com.fasterxml.jackson.core.io.*; - public class UTF8JsonGenerator extends JsonGeneratorImpl { @@ -461,6 +467,50 @@ public void writeString(String text) throws IOException _outputBuffer[_outputTail++] = _quoteChar; } + @Override + public void writeString(Reader reader, int len) throws IOException { + _verifyValueWrite(WRITE_STRING); + if (reader == null) { + _writeNull(); + return; + } + //Adjust length for calculations + if(len < 0){ + len = Integer.MAX_VALUE; + } + + //TODO: Check that this use is OK + final char[] buf = _charBuffer; + + //Add leading quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _outputBuffer[_outputTail++] = _quoteChar; + + //read + while(len > 0){ + int toRead = Math.min(len, buf.length); + if(toRead <= 0){ + break; + } + int numRead = reader.read(buf, 0, toRead); + if(numRead <= 0){ + break; + } + _writeStringSegments(buf, 0, numRead); + + //decrease tracker + len -= numRead; + } + + //Add trailing quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _outputBuffer[_outputTail++] = _quoteChar; + } + @Override public void writeString(char[] text, int offset, int len) throws IOException { diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index ccf4a8ac5e..551dbe9660 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -1,12 +1,18 @@ package com.fasterxml.jackson.core.json; -import java.io.*; +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.io.CharTypes; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.IOContext; +import com.fasterxml.jackson.core.io.NumberOutput; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; -import com.fasterxml.jackson.core.*; -import com.fasterxml.jackson.core.io.*; - /** * {@link JsonGenerator} that outputs JSON content using a {@link java.io.Writer} * which handles character encoding. @@ -69,6 +75,17 @@ public class WriterBasedJsonGenerator */ protected char[] _entityBuffer; + /** + * Intermediate buffer in which characters of a String are copied + * before being encoded. + */ + protected char[] _charBuffer; + + /** + * Length of _charBuffer + */ + protected final int _charBufferLength; + /** * When custom escapes are used, this member variable is used * internally to hold a reference to currently used escape @@ -88,6 +105,8 @@ public WriterBasedJsonGenerator(IOContext ctxt, int features, _writer = w; _outputBuffer = ctxt.allocConcatBuffer(); _outputEnd = _outputBuffer.length; + _charBuffer = ctxt.allocConcatBuffer(); + _charBufferLength = _charBuffer.length; } /* @@ -371,6 +390,51 @@ public void writeString(String text) throws IOException _outputBuffer[_outputTail++] = _quoteChar; } + @Override + public void writeString(Reader reader, int len) throws IOException { + _verifyValueWrite(WRITE_STRING); + if (reader == null) { + _writeNull(); + return; + } + //Adjust length for calculations + if(len < 0){ + len = Integer.MAX_VALUE; + } + + //TODO: Check that this use is OK + final char[] buf = _charBuffer; + + //Add leading quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _outputBuffer[_outputTail++] = _quoteChar; + + //read + while(len > 0){ + int toRead = Math.min(len, buf.length); + if(toRead <= 0){ + break; + } + int numRead = reader.read(buf, 0, toRead); + if(numRead <= 0){ + break; + } + + _writeString(buf, 0, numRead); + + //decrease tracker + len -= numRead; + } + + //Add trailing quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _outputBuffer[_outputTail++] = _quoteChar; + } + @Override public void writeString(char[] text, int offset, int len) throws IOException { @@ -890,6 +954,11 @@ protected void _releaseBuffers() _outputBuffer = null; _ioContext.releaseConcatBuffer(buf); } + char[] cbuf = _charBuffer; + if (cbuf != null) { + _charBuffer = null; + _ioContext.releaseConcatBuffer(cbuf); + } } /* diff --git a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java index f5ee92852b..0af228f48c 100644 --- a/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java +++ b/src/main/java/com/fasterxml/jackson/core/util/JsonGeneratorDelegate.java @@ -1,13 +1,14 @@ package com.fasterxml.jackson.core.util; +import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.io.CharacterEscapes; + import java.io.IOException; import java.io.InputStream; +import java.io.Reader; import java.math.BigDecimal; import java.math.BigInteger; -import com.fasterxml.jackson.core.*; -import com.fasterxml.jackson.core.io.CharacterEscapes; - public class JsonGeneratorDelegate extends JsonGenerator { /** @@ -246,6 +247,11 @@ public void writeArray(double[] array, int offset, int length) throws IOExceptio @Override public void writeString(String text) throws IOException { delegate.writeString(text); } + @Override + public void writeString(Reader reader, int len) throws IOException { + delegate.writeString(reader, len); + } + @Override public void writeString(char[] text, int offset, int len) throws IOException { delegate.writeString(text, offset, len); } From f975512c867e810b8b9e28ae638a9867f3ef5e1d Mon Sep 17 00:00:00 2001 From: Logan Widick Date: Thu, 9 Feb 2017 17:05:13 -0600 Subject: [PATCH 2/4] Added first draft of unit tests (copied from pre-existing string generation ones and adapted to use Writers instead of Strings). Modified one of the generators to manually allocate a buffer rather than call the same ctxt.allocXXXX method again in accordance with the instructions in the exception I was previously getting. --- .../core/json/WriterBasedJsonGenerator.java | 8 +- .../json/StringGenerationFromWriterTest.java | 277 ++++++++++++++++++ 2 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index 551dbe9660..ad6ec12a84 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -105,7 +105,7 @@ public WriterBasedJsonGenerator(IOContext ctxt, int features, _writer = w; _outputBuffer = ctxt.allocConcatBuffer(); _outputEnd = _outputBuffer.length; - _charBuffer = ctxt.allocConcatBuffer(); + _charBuffer = new char[_outputBuffer.length]; _charBufferLength = _charBuffer.length; } @@ -954,11 +954,7 @@ protected void _releaseBuffers() _outputBuffer = null; _ioContext.releaseConcatBuffer(buf); } - char[] cbuf = _charBuffer; - if (cbuf != null) { - _charBuffer = null; - _ioContext.releaseConcatBuffer(cbuf); - } + _charBuffer = null; } /* diff --git a/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java new file mode 100644 index 0000000000..da761e87dc --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java @@ -0,0 +1,277 @@ +package com.fasterxml.jackson.core.json; + +import com.fasterxml.jackson.core.*; + +import java.io.ByteArrayOutputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Random; + +/** + * Set of basic unit tests for verifying that the string + * generation, including character escaping, works as expected. + */ +public class StringGenerationFromWriterTest + extends BaseTest +{ + final static String[] SAMPLES = new String[] { + "\"test\"", + "\n", "\\n", "\r\n", "a\\b", "tab:\nok?", + "a\tb\tc\n\fdef\t \tg\"\"\"h\"\\ijklmn\b", + "\"\"\"", "\\r)'\"", + "Longer text & other stuff:\twith some\r\n\r\n random linefeeds etc added in to cause some \"special\" handling \\\\ to occur...\n" + }; + + private final JsonFactory FACTORY = new JsonFactory(); + + public void testBasicEscaping() throws Exception + { + doTestBasicEscaping(); + } + + // for [core#194] + public void testMediumStringsBytes() throws Exception + { + for (int mode : ALL_BINARY_MODES) { + for (int size : new int[] { 1100, 2300, 3800, 7500, 19000 }) { + _testMediumStrings(mode, size); + } + } + } + + // for [core#194] + public void testMediumStringsChars() throws Exception + { + for (int mode : ALL_TEXT_MODES) { + for (int size : new int[] { 1100, 2300, 3800, 7500, 19000 }) { + _testMediumStrings(mode, size); + } + } + } + + public void testLongerRandomSingleChunk() throws Exception + { + /* Let's first generate 100k of pseudo-random characters, favoring + * 7-bit ascii range + */ + for (int mode : ALL_TEXT_MODES) { + for (int round = 0; round < 80; ++round) { + String content = generateRandom(75000+round); + _testLongerRandom(mode, content); + } + } + } + + public void testLongerRandomMultiChunk() throws Exception + { + /* Let's first generate 100k of pseudo-random characters, favoring + * 7-bit ascii range + */ + for (int mode : ALL_TEXT_MODES) { + for (int round = 0; round < 70; ++round) { + String content = generateRandom(73000+round); + _testLongerRandomMulti(mode, content, round); + } + } + } + + /* + /********************************************************** + /* Internal methods + /********************************************************** + */ + + private String _generareMediumText(int minLen) + { + StringBuilder sb = new StringBuilder(minLen + 1000); + Random rnd = new Random(minLen); + do { + switch (rnd.nextInt() % 4) { + case 0: + sb.append(" foo"); + break; + case 1: + sb.append(" bar"); + break; + case 2: + sb.append(String.valueOf(sb.length())); + break; + default: + sb.append(" \"stuff\""); + break; + } + } while (sb.length() < minLen); + return sb.toString(); + } + + private String generateRandom(int len) + { + StringBuilder sb = new StringBuilder(len+1000); // pad for surrogates + Random r = new Random(len); + for (int i = 0; i < len; ++i) { + if (r.nextBoolean()) { // non-ascii + int value = r.nextInt() & 0xFFFF; + // Otherwise easy, except that need to ensure that + // surrogates are properly paired: and, also + // their values do not exceed 0x10FFFF + if (value >= 0xD800 && value <= 0xDFFF) { + // Let's discard first value, then, and produce valid pair + int fullValue = (r.nextInt() & 0xFFFFF); + sb.append((char) (0xD800 + (fullValue >> 10))); + value = 0xDC00 + (fullValue & 0x3FF); + } + sb.append((char) value); + } else { // ascii + sb.append((char) (r.nextInt() & 0x7F)); + } + } + return sb.toString(); + } + + private void _testMediumStrings(int readMode, int length) throws Exception + { + String text = _generareMediumText(length); + StringWriter sw = new StringWriter(); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + JsonGenerator gen = (readMode != MODE_READER) ? FACTORY.createGenerator(bytes) + : FACTORY.createGenerator(sw); + gen.writeStartArray(); + + StringReader reader = new StringReader(text); + gen.writeString(reader, -1); + gen.writeEndArray(); + gen.close(); + + JsonParser p; + if (readMode == MODE_READER) { + p = FACTORY.createParser(sw.toString()); + } else { + p = createParser(FACTORY, readMode, bytes.toByteArray()); + } + assertToken(JsonToken.START_ARRAY, p.nextToken()); + assertToken(JsonToken.VALUE_STRING, p.nextToken()); + assertEquals(text, p.getText()); + assertToken(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + + private void doTestBasicEscaping() throws Exception + { + for (int i = 0; i < SAMPLES.length; ++i) { + String VALUE = SAMPLES[i]; + StringWriter sw = new StringWriter(); + JsonGenerator gen = FACTORY.createGenerator(sw); + gen.writeStartArray(); + StringReader reader = new StringReader(VALUE); + gen.writeString(reader, -1); + gen.writeEndArray(); + gen.close(); + String docStr = sw.toString(); + JsonParser p = createParserUsingReader(docStr); + assertEquals(JsonToken.START_ARRAY, p.nextToken()); + JsonToken t = p.nextToken(); + assertEquals(JsonToken.VALUE_STRING, t); + assertEquals(VALUE, p.getText()); + assertEquals(JsonToken.END_ARRAY, p.nextToken()); + assertEquals(null, p.nextToken()); + p.close(); + } + } + + private void _testLongerRandom(int readMode, String text) + throws Exception + { + ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length()); + JsonGenerator gen = FACTORY.createGenerator(bow, JsonEncoding.UTF8); + gen.writeStartArray(); + StringReader reader = new StringReader(text); + gen.writeString(reader, -1); + gen.writeEndArray(); + gen.close(); + byte[] docData = bow.toByteArray(); + JsonParser p = createParser(FACTORY, readMode, docData); + assertEquals(JsonToken.START_ARRAY, p.nextToken()); + JsonToken t = p.nextToken(); + assertEquals(JsonToken.VALUE_STRING, t); + String act = p.getText(); + if (!text.equals(act)) { + if (text.length() != act.length()) { + fail("Expected string length "+text.length()+", actual "+act.length()); + } + int i = 0; + for (int len = text.length(); i < len; ++i) { + if (text.charAt(i) != act.charAt(i)) { + break; + } + } + fail("Strings differ at position #"+i+" (len "+text.length()+"): expected char 0x"+Integer.toHexString(text.charAt(i))+", actual 0x"+Integer.toHexString(act.charAt(i))); + } + assertEquals(JsonToken.END_ARRAY, p.nextToken()); + p.close(); + } + + private void _testLongerRandomMulti(int readMode, String text, int round) + throws Exception + { + ByteArrayOutputStream bow = new ByteArrayOutputStream(text.length()); + JsonGenerator gen = FACTORY.createGenerator(bow, JsonEncoding.UTF8); + gen.writeStartArray(); + StringReader reader = new StringReader(text); + gen.writeString(reader, -1); + gen.writeEndArray(); + gen.close(); + + gen = FACTORY.createGenerator(bow, JsonEncoding.UTF8); + gen.writeStartArray(); + gen.writeStartArray(); + + Random rnd = new Random(text.length()); + int offset = 0; + + while (offset < text.length()) { + int shift = 1 + ((rnd.nextInt() & 0xFFFFF) % 12); // 1 - 12 + int len = (1 << shift) + shift; // up to 4k + if ((offset + len) >= text.length()) { + len = text.length() - offset; + } else { + // Need to avoid splitting surrogates though + char c = text.charAt(offset+len-1); + if (c >= 0xD800 && c < 0xDC00) { + ++len; + } + } + reader = new StringReader(text.substring(offset, offset+len)); + gen.writeString(reader, -1); + offset += len; + } + + gen.writeEndArray(); + gen.close(); + byte[] docData = bow.toByteArray(); + JsonParser p = createParser(FACTORY, readMode, docData); + assertEquals(JsonToken.START_ARRAY, p.nextToken()); + + offset = 0; + while (p.nextToken() == JsonToken.VALUE_STRING) { + // Let's verify, piece by piece + String act = p.getText(); + String exp = text.substring(offset, offset+act.length()); + if (act.length() != exp.length()) { + fail("String segment ["+offset+" - "+(offset+act.length())+"[ differs; exp length "+exp+", actual "+act); + } + if (!act.equals(exp)) { + int i = 0; + while (act.charAt(i) == exp.charAt(i)) { + ++i; + } + fail("String segment ["+offset+" - "+(offset+act.length())+"[ different at offset #"+i + +"; exp char 0x"+Integer.toHexString(exp.charAt(i)) + +", actual 0x"+Integer.toHexString(act.charAt(i))); + } + offset += act.length(); + } + assertEquals(JsonToken.END_ARRAY, p.currentToken()); + p.close(); + } +} From 78885dd58bbabb217f9f1d672ce7f1ca7d9e3df5 Mon Sep 17 00:00:00 2001 From: Logan Widick Date: Fri, 10 Feb 2017 10:35:43 -0600 Subject: [PATCH 3/4] Added error checking for null reader, and for not enough input from reader. Changed the name of a test case file. Added failure test cases for reading from null reader, and for reading too little. --- .../jackson/core/json/UTF8JsonGenerator.java | 24 ++-- .../core/json/WriterBasedJsonGenerator.java | 67 +++++----- .../json/GeneratorFailFromReaderTest.java | 114 ++++++++++++++++++ ...va => StringGenerationFromReaderTest.java} | 2 +- 4 files changed, 164 insertions(+), 43 deletions(-) create mode 100644 src/test/java/com/fasterxml/jackson/core/json/GeneratorFailFromReaderTest.java rename src/test/java/com/fasterxml/jackson/core/json/{StringGenerationFromWriterTest.java => StringGenerationFromReaderTest.java} (99%) 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 edc1f3b5aa..ac76e27b2f 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java @@ -471,14 +471,11 @@ public void writeString(String text) throws IOException public void writeString(Reader reader, int len) throws IOException { _verifyValueWrite(WRITE_STRING); if (reader == null) { - _writeNull(); - return; - } - //Adjust length for calculations - if(len < 0){ - len = Integer.MAX_VALUE; + _reportError("null reader"); } + int toRead = (len >= 0) ? len : Integer.MAX_VALUE; + //TODO: Check that this use is OK final char[] buf = _charBuffer; @@ -489,19 +486,22 @@ public void writeString(Reader reader, int len) throws IOException { _outputBuffer[_outputTail++] = _quoteChar; //read - while(len > 0){ - int toRead = Math.min(len, buf.length); + while(toRead > 0){ + int toReadNow = Math.min(toRead, buf.length); if(toRead <= 0){ break; } - int numRead = reader.read(buf, 0, toRead); + int numRead = reader.read(buf, 0, toReadNow); if(numRead <= 0){ break; } + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } _writeStringSegments(buf, 0, numRead); //decrease tracker - len -= numRead; + toRead -= numRead; } //Add trailing quote @@ -509,6 +509,10 @@ public void writeString(Reader reader, int len) throws IOException { _flushBuffer(); } _outputBuffer[_outputTail++] = _quoteChar; + + if(toRead > 0 && len >= 0){ + _reportError("Didn't read enough from reader"); + } } @Override diff --git a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java index ad6ec12a84..149146ddf6 100644 --- a/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java +++ b/src/main/java/com/fasterxml/jackson/core/json/WriterBasedJsonGenerator.java @@ -394,45 +394,48 @@ public void writeString(String text) throws IOException public void writeString(Reader reader, int len) throws IOException { _verifyValueWrite(WRITE_STRING); if (reader == null) { - _writeNull(); - return; - } - //Adjust length for calculations - if(len < 0){ - len = Integer.MAX_VALUE; - } + _reportError("null reader"); + } else { + int toRead = (len >= 0) ? len : Integer.MAX_VALUE; - //TODO: Check that this use is OK - final char[] buf = _charBuffer; + //TODO: Check that this use is OK + final char[] buf = _charBuffer; - //Add leading quote - if ((_outputTail + len) >= _outputEnd) { - _flushBuffer(); - } - _outputBuffer[_outputTail++] = _quoteChar; - - //read - while(len > 0){ - int toRead = Math.min(len, buf.length); - if(toRead <= 0){ - break; - } - int numRead = reader.read(buf, 0, toRead); - if(numRead <= 0){ - break; + //Add leading quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); } + _outputBuffer[_outputTail++] = _quoteChar; - _writeString(buf, 0, numRead); + //read + while (toRead > 0) { + int toReadNow = Math.min(toRead, buf.length); + if (toRead <= 0) { + break; + } + int numRead = reader.read(buf, 0, toReadNow); + if (numRead <= 0) { + break; + } + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _writeString(buf, 0, numRead); - //decrease tracker - len -= numRead; - } + //decrease tracker + toRead -= numRead; + } - //Add trailing quote - if ((_outputTail + len) >= _outputEnd) { - _flushBuffer(); + //Add trailing quote + if ((_outputTail + len) >= _outputEnd) { + _flushBuffer(); + } + _outputBuffer[_outputTail++] = _quoteChar; + + if (toRead > 0 && len >= 0) { + _reportError("Didn't read enough from reader"); + } } - _outputBuffer[_outputTail++] = _quoteChar; } @Override diff --git a/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailFromReaderTest.java b/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailFromReaderTest.java new file mode 100644 index 0000000000..0f5120af23 --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/core/json/GeneratorFailFromReaderTest.java @@ -0,0 +1,114 @@ +package com.fasterxml.jackson.core.json; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.io.StringReader; + +public class GeneratorFailFromReaderTest + extends com.fasterxml.jackson.core.BaseTest +{ + private final JsonFactory F = new JsonFactory(); + + // [core#177] + // Also: should not try writing JSON String if field name expected + // (in future maybe take one as alias... but not yet) + public void testFailOnWritingStringNotFieldNameBytes() throws Exception { + _testFailOnWritingStringNotFieldName(F, false); + } + + // [core#177] + public void testFailOnWritingStringNotFieldNameChars() throws Exception { + _testFailOnWritingStringNotFieldName(F, true); + } + + public void testFailOnWritingStringFromReaderWithTooFewCharacters() throws Exception { + _testFailOnWritingStringFromReaderWithTooFewCharacters(F, true); + _testFailOnWritingStringFromReaderWithTooFewCharacters(F, false); + } + + public void testFailOnWritingStringFromNullReader() throws Exception { + _testFailOnWritingStringFromNullReader(F, true); + _testFailOnWritingStringFromNullReader(F, false); + } + + /* + /********************************************************** + /* Internal methods + /********************************************************** + */ + + + private void _testFailOnWritingStringNotFieldName(JsonFactory f, boolean useReader) throws Exception + { + JsonGenerator gen; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + if (useReader) { + gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8")); + } else { + gen = f.createGenerator(bout, JsonEncoding.UTF8); + } + gen.writeStartObject(); + + try { + StringReader reader = new StringReader("a"); + gen.writeString(reader, -1); + gen.flush(); + String json = bout.toString("UTF-8"); + fail("Should not have let "+gen.getClass().getName()+".writeString() be used in place of 'writeFieldName()': output = "+json); + } catch (JsonProcessingException e) { + verifyException(e, "can not write a String"); + } + gen.close(); + } + + private void _testFailOnWritingStringFromReaderWithTooFewCharacters(JsonFactory f, boolean useReader) throws Exception{ + JsonGenerator gen; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + if (useReader) { + gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8")); + } else { + gen = f.createGenerator(bout, JsonEncoding.UTF8); + } + gen.writeStartObject(); + + try { + String testStr = "aaaaaaaaa"; + StringReader reader = new StringReader(testStr); + gen.writeFieldName("a"); + gen.writeString(reader, testStr.length() + 1); + gen.flush(); + String json = bout.toString("UTF-8"); + fail("Should not have let "+gen.getClass().getName()+".writeString() ': output = "+json); + } catch (JsonProcessingException e) { + verifyException(e, "Didn't read enough from reader"); + } + gen.close(); + } + + private void _testFailOnWritingStringFromNullReader(JsonFactory f, boolean useReader) throws Exception{ + JsonGenerator gen; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + if (useReader) { + gen = f.createGenerator(new OutputStreamWriter(bout, "UTF-8")); + } else { + gen = f.createGenerator(bout, JsonEncoding.UTF8); + } + gen.writeStartObject(); + + try { + gen.writeFieldName("a"); + gen.writeString(null, -1); + gen.flush(); + String json = bout.toString("UTF-8"); + fail("Should not have let "+gen.getClass().getName()+".writeString() ': output = "+json); + } catch (JsonProcessingException e) { + verifyException(e, "null reader"); + } + gen.close(); + } +} diff --git a/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromReaderTest.java similarity index 99% rename from src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java rename to src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromReaderTest.java index da761e87dc..d890b97f23 100644 --- a/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromWriterTest.java +++ b/src/test/java/com/fasterxml/jackson/core/json/StringGenerationFromReaderTest.java @@ -11,7 +11,7 @@ * Set of basic unit tests for verifying that the string * generation, including character escaping, works as expected. */ -public class StringGenerationFromWriterTest +public class StringGenerationFromReaderTest extends BaseTest { final static String[] SAMPLES = new String[] { From 4ba30eb74f9826e5a4389f418614390004d8687f Mon Sep 17 00:00:00 2001 From: Logan Widick Date: Fri, 10 Feb 2017 10:56:07 -0600 Subject: [PATCH 4/4] Created a default writeString(reader, int) implementation that reports unsupported so that the new method doesn't have to be implemented in all formats right away. --- .../fasterxml/jackson/core/base/GeneratorBase.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java index d2d05f2b8f..128a4d7ae5 100644 --- a/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java +++ b/src/main/java/com/fasterxml/jackson/core/base/GeneratorBase.java @@ -1,14 +1,16 @@ package com.fasterxml.jackson.core.base; -import java.io.*; -import java.math.BigDecimal; - import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.json.DupDetector; import com.fasterxml.jackson.core.json.JsonWriteContext; import com.fasterxml.jackson.core.json.PackageVersion; import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; + /** * This base class implements part of API that a JSON generator exposes * to applications, adds shared internal methods that sub-classes @@ -339,6 +341,12 @@ public int writeBinary(Base64Variant b64variant, InputStream data, int dataLengt return 0; } + @Override + public void writeString(Reader reader, int len) throws IOException { + // Let's implement this as "unsupported" to make it easier to add new parser impls + _reportUnsupportedOperation(); + } + /* /********************************************************** /* Public API, write methods, primitive