diff --git a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java
index 8e47a469d..4a3e97e55 100644
--- a/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java
+++ b/yaml/src/main/java/com/fasterxml/jackson/dataformat/yaml/YAMLGenerator.java
@@ -19,8 +19,7 @@
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.io.IOContext;
-public class YAMLGenerator extends GeneratorBase
-{
+public class YAMLGenerator extends GeneratorBase {
/**
* Enumeration that defines all togglable features for YAML generators
*/
@@ -62,7 +61,7 @@ public enum Feature implements FormatFeature // since 2.9
/**
* Options passed to SnakeYAML that determines whether longer textual content
* gets automatically split into multiple lines or not.
- *
+ *
* Feature is enabled by default to conform to SnakeYAML defaults as well as
* backwards compatibility with 2.5 and earlier versions.
*
@@ -73,7 +72,7 @@ public enum Feature implements FormatFeature // since 2.9
/**
* Whether strings will be rendered without quotes (true) or
* with quotes (false, default).
- *
+ *
* Minimized quote usage makes for more human readable output; however, content is
* limited to printable characters according to the rules of
* literal block style.
@@ -85,7 +84,7 @@ public enum Feature implements FormatFeature // since 2.9
/**
* Whether numbers stored as strings will be rendered with quotes (true) or
* without quotes (false, default) when MINIMIZE_QUOTES is enabled.
- *
+ *
* Minimized quote usage makes for more human readable output; however, content is
* limited to printable characters according to the rules of
* literal block style.
@@ -108,7 +107,7 @@ public enum Feature implements FormatFeature // since 2.9
/**
* Feature enabling of which adds indentation for array entry generation
* (default indentation being 2 spaces).
- *
+ *
* Default value is `false` for backwards compatibility
*
* @since 2.9
@@ -134,8 +133,7 @@ public enum Feature implements FormatFeature // since 2.9
* Method that calculates bit set (flags) of all features that
* are enabled by default.
*/
- public static int collectDefaults()
- {
+ public static int collectDefaults() {
int flags = 0;
for (Feature f : values()) {
if (f.enabledByDefault()) {
@@ -151,11 +149,19 @@ private Feature(boolean defaultState) {
}
@Override
- public boolean enabledByDefault() { return _defaultState; }
+ public boolean enabledByDefault() {
+ return _defaultState;
+ }
+
@Override
- public boolean enabledIn(int flags) { return (flags & _mask) != 0; }
+ public boolean enabledIn(int flags) {
+ return (flags & _mask) != 0;
+ }
+
@Override
- public int getMask() { return _mask; }
+ public int getMask() {
+ return _mask;
+ }
}
/*
@@ -231,10 +237,9 @@ private Feature(boolean defaultState) {
*/
public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
- ObjectCodec codec, Writer out,
- org.yaml.snakeyaml.DumperOptions.Version version)
- throws IOException
- {
+ ObjectCodec codec, Writer out,
+ org.yaml.snakeyaml.DumperOptions.Version version)
+ throws IOException {
super(jsonFeatures, codec);
_ioContext = ctxt;
_formatFeatures = yamlFeatures;
@@ -245,7 +250,7 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
_emitter = new Emitter(_writer, _outputOptions);
// should we start output now, or try to defer?
_emitter.emit(new StreamStartEvent(null, null));
- Map noTags = Collections.emptyMap();
+ Map noTags = Collections.emptyMap();
boolean startMarker = Feature.WRITE_DOC_START_MARKER.enabledIn(yamlFeatures);
@@ -255,8 +260,7 @@ public YAMLGenerator(IOContext ctxt, int jsonFeatures, int yamlFeatures,
}
protected DumperOptions buildDumperOptions(int jsonFeatures, int yamlFeatures,
- org.yaml.snakeyaml.DumperOptions.Version version)
- {
+ org.yaml.snakeyaml.DumperOptions.Version version) {
DumperOptions opt = new DumperOptions();
// would we want canonical?
if (Feature.CANONICAL_OUTPUT.enabledIn(_formatFeatures)) {
@@ -305,8 +309,7 @@ public Version version() {
* Not sure what to do here; could reset indentation to some value maybe?
*/
@Override
- public YAMLGenerator useDefaultPrettyPrinter()
- {
+ public YAMLGenerator useDefaultPrettyPrinter() {
return this;
}
@@ -352,7 +355,9 @@ public boolean canUseSchema(FormatSchema schema) {
}
@Override
- public boolean canWriteFormattedNumbers() { return true; }
+ public boolean canWriteFormattedNumbers() {
+ return true;
+ }
//@Override public void setSchema(FormatSchema schema)
@@ -396,8 +401,7 @@ public YAMLGenerator configure(Feature f, boolean state) {
*/
@Override
- public final void writeFieldName(String name) throws IOException
- {
+ public final void writeFieldName(String name) throws IOException {
if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
@@ -406,8 +410,7 @@ public final void writeFieldName(String name) throws IOException
@Override
public final void writeFieldName(SerializableString name)
- throws IOException
- {
+ throws IOException {
// Object is a value, need to verify it's allowed
if (_writeContext.writeFieldName(name.getValue()) == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
@@ -417,8 +420,7 @@ public final void writeFieldName(SerializableString name)
@Override
public final void writeStringField(String fieldName, String value)
- throws IOException
- {
+ throws IOException {
if (_writeContext.writeFieldName(fieldName) == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
@@ -427,8 +429,7 @@ public final void writeStringField(String fieldName, String value)
}
private final void _writeFieldName(String name)
- throws IOException
- {
+ throws IOException {
_writeScalar(name, "string", STYLE_NAME);
}
@@ -439,14 +440,12 @@ private final void _writeFieldName(String name)
*/
@Override
- public final void flush() throws IOException
- {
+ public final void flush() throws IOException {
_writer.flush();
}
@Override
- public void close() throws IOException
- {
+ public void close() throws IOException {
if (!isClosed()) {
_emitter.emit(new DocumentEndEvent(null, null, false));
_emitter.emit(new StreamEndEvent(null, null));
@@ -462,8 +461,7 @@ public void close() throws IOException
*/
@Override
- public final void writeStartArray() throws IOException
- {
+ public final void writeStartArray() throws IOException {
_verifyValueWrite("start an array");
_writeContext = _writeContext.createChildArrayContext();
Boolean style = _outputOptions.getDefaultFlowStyle().getStyleBoolean();
@@ -474,14 +472,13 @@ public final void writeStartArray() throws IOException
_objectId = null;
}
_emitter.emit(new SequenceStartEvent(anchor, yamlTag,
- implicit, null, null, style));
+ implicit, null, null, style));
}
@Override
- public final void writeEndArray() throws IOException
- {
+ public final void writeEndArray() throws IOException {
if (!_writeContext.inArray()) {
- _reportError("Current context not Array but "+_writeContext.typeDesc());
+ _reportError("Current context not Array but " + _writeContext.typeDesc());
}
// just to make sure we don't "leak" type ids
_typeId = null;
@@ -490,8 +487,7 @@ public final void writeEndArray() throws IOException
}
@Override
- public final void writeStartObject() throws IOException
- {
+ public final void writeStartObject() throws IOException {
_verifyValueWrite("start an object");
_writeContext = _writeContext.createChildObjectContext();
Boolean style = _outputOptions.getDefaultFlowStyle().getStyleBoolean();
@@ -506,10 +502,9 @@ public final void writeStartObject() throws IOException
}
@Override
- public final void writeEndObject() throws IOException
- {
+ public final void writeEndObject() throws IOException {
if (!_writeContext.inObject()) {
- _reportError("Current context not Object but "+_writeContext.typeDesc());
+ _reportError("Current context not Object but " + _writeContext.typeDesc());
}
// just to make sure we don't "leak" type ids
_typeId = null;
@@ -524,8 +519,7 @@ public final void writeEndObject() throws IOException
*/
@Override
- public void writeString(String text) throws IOException,JsonGenerationException
- {
+ public void writeString(String text) throws IOException, JsonGenerationException {
if (text == null) {
writeNull();
return;
@@ -533,7 +527,7 @@ public void writeString(String text) throws IOException,JsonGenerationException
_verifyValueWrite("write String value");
Character style = STYLE_QUOTED;
if (Feature.MINIMIZE_QUOTES.enabledIn(_formatFeatures) && !isBooleanContent(text)) {
- // If this string could be interpreted as a number, it must be quoted.
+ // If this string could be interpreted as a number, it must be quoted.
if (Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS.enabledIn(_formatFeatures)
&& PLAIN_NUMBER_P.matcher(text).matches()) {
style = STYLE_QUOTED;
@@ -553,29 +547,25 @@ private boolean isBooleanContent(String text) {
}
@Override
- public void writeString(char[] text, int offset, int len) throws IOException
- {
+ public void writeString(char[] text, int offset, int len) throws IOException {
writeString(new String(text, offset, len));
}
@Override
public final void writeString(SerializableString sstr)
- throws IOException
- {
+ throws IOException {
writeString(sstr.toString());
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int len)
- throws IOException
- {
+ throws IOException {
_reportUnsupportedOperation();
}
@Override
public final void writeUTF8String(byte[] text, int offset, int len)
- throws IOException
- {
+ throws IOException {
writeString(new String(text, offset, len, "UTF-8"));
}
@@ -627,15 +617,14 @@ public void writeRawValue(char[] text, int offset, int len) throws IOException {
*/
@Override
- public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException
- {
+ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException {
if (data == null) {
writeNull();
return;
}
_verifyValueWrite("write Binary value");
- if (offset > 0 || (offset+len) != data.length) {
- data = Arrays.copyOfRange(data, offset, offset+len);
+ if (offset > 0 || (offset + len) != data.length) {
+ data = Arrays.copyOfRange(data, offset, offset + len);
}
_writeScalarBinary(b64variant, data);
}
@@ -647,22 +636,19 @@ public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int l
*/
@Override
- public void writeBoolean(boolean state) throws IOException
- {
+ public void writeBoolean(boolean state) throws IOException {
_verifyValueWrite("write boolean value");
_writeScalar(state ? "true" : "false", "bool", STYLE_SCALAR);
}
@Override
- public void writeNumber(int i) throws IOException
- {
+ public void writeNumber(int i) throws IOException {
_verifyValueWrite("write number");
_writeScalar(String.valueOf(i), "int", STYLE_SCALAR);
}
@Override
- public void writeNumber(long l) throws IOException
- {
+ public void writeNumber(long l) throws IOException {
// First: maybe 32 bits is enough?
if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) {
writeNumber((int) l);
@@ -673,8 +659,7 @@ public void writeNumber(long l) throws IOException
}
@Override
- public void writeNumber(BigInteger v) throws IOException
- {
+ public void writeNumber(BigInteger v) throws IOException {
if (v == null) {
writeNull();
return;
@@ -684,22 +669,19 @@ public void writeNumber(BigInteger v) throws IOException
}
@Override
- public void writeNumber(double d) throws IOException
- {
+ public void writeNumber(double d) throws IOException {
_verifyValueWrite("write number");
_writeScalar(String.valueOf(d), "double", STYLE_SCALAR);
}
@Override
- public void writeNumber(float f) throws IOException
- {
+ public void writeNumber(float f) throws IOException {
_verifyValueWrite("write number");
_writeScalar(String.valueOf(f), "float", STYLE_SCALAR);
}
@Override
- public void writeNumber(BigDecimal dec) throws IOException
- {
+ public void writeNumber(BigDecimal dec) throws IOException {
if (dec == null) {
writeNull();
return;
@@ -710,8 +692,7 @@ public void writeNumber(BigDecimal dec) throws IOException
}
@Override
- public void writeNumber(String encodedValue) throws IOException,JsonGenerationException, UnsupportedOperationException
- {
+ public void writeNumber(String encodedValue) throws IOException, JsonGenerationException, UnsupportedOperationException {
if (encodedValue == null) {
writeNull();
return;
@@ -721,8 +702,7 @@ public void writeNumber(String encodedValue) throws IOException,JsonGenerationEx
}
@Override
- public void writeNull() throws IOException
- {
+ public void writeNull() throws IOException {
_verifyValueWrite("write null value");
// no real type for this, is there?
_writeScalar("null", "object", STYLE_SCALAR);
@@ -750,16 +730,14 @@ public boolean canWriteTypeId() {
@Override
public void writeTypeId(Object id)
- throws IOException
- {
+ throws IOException {
// should we verify there's no preceding type id?
_typeId = String.valueOf(id);
}
@Override
public void writeObjectRef(Object id)
- throws IOException
- {
+ throws IOException {
_verifyValueWrite("write Object reference");
AliasEvent evt = new AliasEvent(String.valueOf(id), null, null);
_emitter.emit(evt);
@@ -767,8 +745,7 @@ public void writeObjectRef(Object id)
@Override
public void writeObjectId(Object id)
- throws IOException
- {
+ throws IOException {
// should we verify there's no preceding id?
_objectId = String.valueOf(id);
}
@@ -781,11 +758,10 @@ public void writeObjectId(Object id)
@Override
protected final void _verifyValueWrite(String typeMsg)
- throws IOException
- {
+ throws IOException {
int status = _writeContext.writeValue();
if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
- _reportError("Can not "+typeMsg+", expecting field name");
+ _reportError("Can not " + typeMsg + ", expecting field name");
}
}
@@ -806,26 +782,65 @@ protected void _releaseBuffers() {
// ... and sometimes we specifically DO want explicit tag:
private final static ImplicitTuple EXPLICIT_TAGS = new ImplicitTuple(false, false);
- protected void _writeScalar(String value, String type, Character style) throws IOException
- {
+ protected void _writeScalar(String value, String type, Character style) throws IOException {
_emitter.emit(_scalarEvent(value, style));
}
private void _writeScalarBinary(Base64Variant b64variant,
- byte[] data) throws IOException
- {
+ byte[] data) throws IOException {
// 15-Dec-2017, tatu: as per [dataformats-text#62], can not use SnakeYAML's internal
// codec. Also: force use of linefeed variant if using default
if (b64variant == Base64Variants.getDefaultVariant()) {
b64variant = Base64Variants.MIME;
}
- String encoded = b64variant.encode(data);
+ String encoded = encode(b64variant, data);
_emitter.emit(new ScalarEvent(null, TAG_BINARY, EXPLICIT_TAGS, encoded,
null, null, STYLE_BASE64));
}
- protected ScalarEvent _scalarEvent(String value, Character style)
- {
+ // NOTE: copied and slightly modified from Jackson core Base64Variant.encode(byte[] input, boolean addQuotes)
+ // the line breaks are printed without escaping and according to the USE_PLATFORM_LINE_BREAKS setting
+ private String encode(Base64Variant b64variant, byte[] input) {
+ int inputEnd = input.length;
+ StringBuilder sb;
+ {
+ // let's approximate... 33% overhead, ~= 3/8 (0.375)
+ int outputLen = inputEnd + (inputEnd >> 2) + (inputEnd >> 3);
+ sb = new StringBuilder(outputLen);
+ }
+
+ int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+
+ // Ok, first we loop through all full triplets of data:
+ int inputPtr = 0;
+ int safeInputEnd = inputEnd-3; // to get only full triplets
+
+ while (inputPtr <= safeInputEnd) {
+ // First, mash 3 bytes into lsb of 32-bit int
+ int b24 = ((int) input[inputPtr++]) << 8;
+ b24 |= ((int) input[inputPtr++]) & 0xFF;
+ b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
+ b64variant.encodeBase64Chunk(sb, b24);
+ if (--chunksBeforeLF <= 0) {
+ sb.append(_outputOptions.getLineBreak().getString());
+ chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
+ }
+ }
+
+ // And then we may have 1 or 2 leftover bytes to encode
+ int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
+ if (inputLeft > 0) { // yes, but do we have room for output?
+ int b24 = ((int) input[inputPtr++]) << 16;
+ if (inputLeft == 2) {
+ b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
+ }
+ b64variant.encodeBase64Partial(sb, b24, inputLeft);
+ }
+
+ return sb.toString();
+ }
+
+ protected ScalarEvent _scalarEvent(String value, Character style) {
String yamlTag = _typeId;
if (yamlTag != null) {
_typeId = null;
diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java
index 0b70b4fd5..1594a7799 100644
--- a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java
+++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/deser/BinaryReadTest.java
@@ -1,14 +1,23 @@
package com.fasterxml.jackson.dataformat.yaml.deser;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Random;
import org.junit.Assert;
-import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.dataformat.yaml.ModuleTestBase;
+import static org.junit.Assert.assertArrayEquals;
+
public class BinaryReadTest extends ModuleTestBase
{
private final ObjectMapper MAPPER = newObjectMapper();
@@ -41,4 +50,28 @@ public void testBinaryViaTree() throws Exception
final byte[] expectedFileHeader = new byte[]{'G', 'I', 'F', '8', '9', 'a'};
Assert.assertArrayEquals(expectedFileHeader, actualFileHeader);
}
+
+ public void testReadLongBinary() throws Exception {
+ final byte[] data = new byte[1000];
+ new Random().nextBytes(data);
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ JsonFactory factory = MAPPER.getFactory();
+ JsonGenerator gen = factory.createGenerator(os);
+
+ gen.writeStartObject();
+ gen.writeBinaryField("data", data);
+ gen.writeEndObject();
+ gen.close();
+
+ try (JsonParser parser = factory.createParser(os.toByteArray())) {
+ assertEquals(JsonToken.START_OBJECT, parser.nextToken());
+ assertEquals(JsonToken.FIELD_NAME, parser.nextToken());
+ assertEquals("data", parser.currentName());
+ assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken());
+ assertArrayEquals(data, parser.getBinaryValue());
+ assertEquals(JsonToken.END_OBJECT, parser.nextToken());
+ assertNull(parser.nextToken());
+ }
+ }
}
diff --git a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java
index da8b86fe7..4d770afb0 100644
--- a/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java
+++ b/yaml/src/test/java/com/fasterxml/jackson/dataformat/yaml/ser/BinaryWriteTest.java
@@ -1,8 +1,13 @@
package com.fasterxml.jackson.dataformat.yaml.ser;
+import java.io.StringWriter;
+import java.util.Arrays;
+
import org.junit.Assert;
-import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.ModuleTestBase;
@@ -26,4 +31,28 @@ public void testBinaryViaTree() throws Exception
final byte[] b = data.binaryValue();
Assert.assertArrayEquals(srcPayload, b);
}
+
+ public void testWriteLongBinary() throws Exception {
+ final int length = 200;
+ final byte[] data = new byte[length];
+ Arrays.fill(data, (byte) 1);
+
+ StringWriter w = new StringWriter();
+ JsonGenerator gen = MAPPER.getFactory().createGenerator(w);
+
+ gen.writeStartObject();
+ gen.writeBinaryField("array", data);
+ gen.writeEndObject();
+ gen.close();
+
+ String yaml = w.toString();
+ Assert.assertEquals("---\n" +
+ "array: !!binary |-\n" +
+ " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" +
+ " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" +
+ " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB\n" +
+ " AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\n", yaml);
+
+ }
+
}