From fe25f7e14d92f5d4746549e7df22e9af35fdf54b Mon Sep 17 00:00:00 2001 From: Cowtowncoder Date: Wed, 28 Oct 2015 16:32:31 -0700 Subject: Fix #984 --- .../jackson/databind/util/TokenBuffer.java | 78 ++++++++++---- .../jackson/databind/util/TestTokenBuffer.java | 116 +++++++++++++++++++-- 2 files changed, 165 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java index fc3600ad9..fcf0c3b93 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/TokenBuffer.java @@ -436,7 +436,7 @@ public class TokenBuffer copyCurrentStructure(p); return this; } - /* 28-Oct-2014, tatu: As per #592, need to support a special case of starting from + /* 28-Oct-2014, tatu: As per [databind#592], need to support a special case of starting from * FIELD_NAME, which is taken to mean that we are missing START_OBJECT, but need * to assume one did exist. */ @@ -669,7 +669,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (text == null) { writeNull(); } else { - _append(JsonToken.VALUE_STRING, text); + _appendValue(JsonToken.VALUE_STRING, text); } } @@ -683,7 +683,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (text == null) { writeNull(); } else { - _append(JsonToken.VALUE_STRING, text); + _appendValue(JsonToken.VALUE_STRING, text); } } @@ -728,7 +728,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); @Override public void writeRawValue(String text) throws IOException { - _append(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text)); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text)); } @Override @@ -736,12 +736,12 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (offset > 0 || len != text.length()) { text = text.substring(offset, offset+len); } - _append(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text)); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new RawValue(text)); } @Override public void writeRawValue(char[] text, int offset, int len) throws IOException { - _append(JsonToken.VALUE_EMBEDDED_OBJECT, new String(text, offset, len)); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, new String(text, offset, len)); } /* @@ -752,27 +752,27 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); @Override public void writeNumber(short i) throws IOException { - _append(JsonToken.VALUE_NUMBER_INT, Short.valueOf(i)); + _appendValue(JsonToken.VALUE_NUMBER_INT, Short.valueOf(i)); } @Override public void writeNumber(int i) throws IOException { - _append(JsonToken.VALUE_NUMBER_INT, Integer.valueOf(i)); + _appendValue(JsonToken.VALUE_NUMBER_INT, Integer.valueOf(i)); } @Override public void writeNumber(long l) throws IOException { - _append(JsonToken.VALUE_NUMBER_INT, Long.valueOf(l)); + _appendValue(JsonToken.VALUE_NUMBER_INT, Long.valueOf(l)); } @Override public void writeNumber(double d) throws IOException { - _append(JsonToken.VALUE_NUMBER_FLOAT, Double.valueOf(d)); + _appendValue(JsonToken.VALUE_NUMBER_FLOAT, Double.valueOf(d)); } @Override public void writeNumber(float f) throws IOException { - _append(JsonToken.VALUE_NUMBER_FLOAT, Float.valueOf(f)); + _appendValue(JsonToken.VALUE_NUMBER_FLOAT, Float.valueOf(f)); } @Override @@ -780,7 +780,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (dec == null) { writeNull(); } else { - _append(JsonToken.VALUE_NUMBER_FLOAT, dec); + _appendValue(JsonToken.VALUE_NUMBER_FLOAT, dec); } } @@ -789,7 +789,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (v == null) { writeNull(); } else { - _append(JsonToken.VALUE_NUMBER_INT, v); + _appendValue(JsonToken.VALUE_NUMBER_INT, v); } } @@ -798,17 +798,17 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); /* 03-Dec-2010, tatu: related to [JACKSON-423], should try to keep as numeric * identity as long as possible */ - _append(JsonToken.VALUE_NUMBER_FLOAT, encodedValue); + _appendValue(JsonToken.VALUE_NUMBER_FLOAT, encodedValue); } @Override public void writeBoolean(boolean state) throws IOException { - _append(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE); + _appendValue(state ? JsonToken.VALUE_TRUE : JsonToken.VALUE_FALSE); } @Override public void writeNull() throws IOException { - _append(JsonToken.VALUE_NULL); + _appendValue(JsonToken.VALUE_NULL); } /* @@ -826,7 +826,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); } Class raw = value.getClass(); if (raw == byte[].class || (value instanceof RawValue)) { - _append(JsonToken.VALUE_EMBEDDED_OBJECT, value); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value); return; } if (_objectCodec == null) { @@ -834,7 +834,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); * err out, or just embed? For now, do latter. */ // throw new JsonMappingException("No ObjectCodec configured for TokenBuffer, writeObject() called"); - _append(JsonToken.VALUE_EMBEDDED_OBJECT, value); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, value); } else { _objectCodec.writeValue(this, value); } @@ -850,7 +850,7 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); if (_objectCodec == null) { // as with 'writeObject()', is codec optional? - _append(JsonToken.VALUE_EMBEDDED_OBJECT, node); + _appendValue(JsonToken.VALUE_EMBEDDED_OBJECT, node); } else { _objectCodec.writeTree(this, node); } @@ -1082,6 +1082,46 @@ sb.append("NativeObjectIds=").append(_hasNativeObjectIds).append(","); } } + /** + * Similar to {@link #_append(JsonToken)} but also updates context with + * knowledge that a scalar value was written + * + * @since 2.6.4 + */ + protected final void _appendValue(JsonToken type) + { + _writeContext.writeValue(); + Segment next = _hasNativeId + ? _last.append(_appendAt, type, _objectId, _typeId) + : _last.append(_appendAt, type); + if (next == null) { + ++_appendAt; + } else { + _last = next; + _appendAt = 1; // since we added first at 0 + } + } + + /** + * Similar to {@link #_append(JsonToken,Object)} but also updates context with + * knowledge that a scalar value was written + * + * @since 2.6.4 + */ + protected final void _appendValue(JsonToken type, Object value) + { + _writeContext.writeValue(); + Segment next = _hasNativeId + ? _last.append(_appendAt, type, value, _objectId, _typeId) + : _last.append(_appendAt, type, value); + if (next == null) { + ++_appendAt; + } else { + _last = next; + _appendAt = 1; + } + } + protected final void _appendRaw(int rawType, Object value) { Segment next = _hasNativeId diff --git a/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java b/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java index 842d617eb..4e14bbf3e 100644 --- a/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java +++ b/src/test/java/com/fasterxml/jackson/databind/util/TestTokenBuffer.java @@ -10,6 +10,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class TestTokenBuffer extends BaseMapTest { + private final ObjectMapper MAPPER = objectMapper(); + /* /********************************************************** /* Basic TokenBuffer tests @@ -219,8 +221,6 @@ public class TestTokenBuffer extends BaseMapTest // deal with public void testWithUUID() throws IOException { - ObjectMapper mapper = new ObjectMapper(); - for (String value : new String[] { "00000007-0000-0000-0000-000000000000", "76e6d183-5f68-4afa-b94a-922c1fdb83f8", @@ -229,16 +229,16 @@ public class TestTokenBuffer extends BaseMapTest "591b2869-146e-41d7-8048-e8131f1fdec5", "82994ac2-7b23-49f2-8cc5-e24cf6ed77be", }) { - TokenBuffer buf = new TokenBuffer(mapper, false); // no ObjectCodec + TokenBuffer buf = new TokenBuffer(MAPPER, false); // no ObjectCodec UUID uuid = UUID.fromString(value); - mapper.writeValue(buf, uuid); + MAPPER.writeValue(buf, uuid); buf.close(); // and bring it back - UUID out = mapper.readValue(buf.asParser(), UUID.class); + UUID out = MAPPER.readValue(buf.asParser(), UUID.class); assertEquals(uuid.toString(), out.toString()); - // second part: As per [#362], should NOT use binary with TokenBuffer + // second part: As per [databind#362], should NOT use binary with TokenBuffer JsonParser jp = buf.asParser(); assertEquals(JsonToken.VALUE_STRING, jp.nextToken()); String str = jp.getText(); @@ -246,7 +246,104 @@ public class TestTokenBuffer extends BaseMapTest jp.close(); } } - + + /* + /********************************************************** + /* Tests for read/output contexts + /********************************************************** + */ + + // for [databind#984]: ensure output context handling identical + public void testOutputContext() throws IOException + { + TokenBuffer buf = new TokenBuffer(null, false); // no ObjectCodec + StringWriter w = new StringWriter(); + JsonGenerator gen = MAPPER.getFactory().createGenerator(w); + + // test content: [{"a":1,"b":{"c":2}},{"a":2,"b":{"c":3}}] + + buf.writeStartArray(); + gen.writeStartArray(); + _verifyOutputContext(buf, gen); + + buf.writeStartObject(); + gen.writeStartObject(); + _verifyOutputContext(buf, gen); + + buf.writeFieldName("a"); + gen.writeFieldName("a"); + _verifyOutputContext(buf, gen); + + buf.writeNumber(1); + gen.writeNumber(1); + _verifyOutputContext(buf, gen); + + buf.writeFieldName("b"); + gen.writeFieldName("b"); + _verifyOutputContext(buf, gen); + + buf.writeStartObject(); + gen.writeStartObject(); + _verifyOutputContext(buf, gen); + + buf.writeFieldName("c"); + gen.writeFieldName("c"); + _verifyOutputContext(buf, gen); + + buf.writeNumber(2); + gen.writeNumber(2); + _verifyOutputContext(buf, gen); + + buf.writeEndObject(); + gen.writeEndObject(); + _verifyOutputContext(buf, gen); + + buf.writeEndObject(); + gen.writeEndObject(); + _verifyOutputContext(buf, gen); + + buf.writeEndArray(); + gen.writeEndArray(); + _verifyOutputContext(buf, gen); + + buf.close(); + gen.close(); + } + + private void _verifyOutputContext(JsonGenerator gen1, JsonGenerator gen2) + { + _verifyOutputContext(gen1.getOutputContext(), gen2.getOutputContext()); + } + + private void _verifyOutputContext(JsonStreamContext ctxt1, JsonStreamContext ctxt2) + { + if (ctxt1 == null) { + if (ctxt2 == null) { + return; + } + fail("Context 1 null, context 2 not null: "+ctxt2); + } else if (ctxt2 == null) { + fail("Context 2 null, context 1 not null: "+ctxt1); + } + if (!ctxt1.getTypeDesc().equals(ctxt2.getTypeDesc())) { + fail("Different output context: token-buffer's = "+ctxt1+", json-generator's: "+ctxt2); + } + + if (ctxt1.inObject()) { + assertTrue(ctxt2.inObject()); + String str1 = ctxt1.getCurrentName(); + String str2 = ctxt2.getCurrentName(); + + if ((str1 != str2) && !str1.equals(str2)) { + fail("Expected name '"+str2+"' (JsonParser), TokenBuffer had '"+str1+"'"); + } + } else if (ctxt1.inArray()) { + assertTrue(ctxt2.inArray()); + assertEquals(ctxt1.getCurrentIndex(), ctxt2.getCurrentIndex()); + } + _verifyOutputContext(ctxt1.getParent(), ctxt2.getParent()); + } + /* /********************************************************** /* Tests to verify interaction of TokenBuffer and JsonParserSequence @@ -335,7 +432,7 @@ public class TestTokenBuffer extends BaseMapTest buf4.close(); } - // [Issue#743] + // [databind#743] public void testRawValues() throws Exception { final String RAW = "{\"a\":1}"; @@ -350,7 +447,6 @@ public class TestTokenBuffer extends BaseMapTest buf.close(); // then verify it would be serialized just fine - ObjectMapper mapper = objectMapper(); - assertEquals(RAW, mapper.writeValueAsString(buf)); + assertEquals(RAW, MAPPER.writeValueAsString(buf)); } } -- cgit v1.2.3