ProtobufGenerator.java
package com.fasterxml.jackson.dataformat.protobuf;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.base.GeneratorBase;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.json.JsonWriteContext;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.dataformat.protobuf.schema.FieldType;
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufField;
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufMessage;
import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema;
import com.fasterxml.jackson.dataformat.protobuf.schema.WireType;
public class ProtobufGenerator extends GeneratorBase
{
/*
/**********************************************************
/* Constants
/**********************************************************
*/
/**
* Since our context object does NOT implement standard write context, need
* to do something like use a placeholder...
*/
protected final static JsonWriteContext BOGUS_WRITE_CONTEXT = JsonWriteContext.createRootContext(null);
/**
* This instance is used as a placeholder for cases where we do not know
* actual field and want to simply skip over any values that caller tries
* to write for it.
*/
protected final static ProtobufField UNKNOWN_FIELD = ProtobufField.unknownField();
/**
* This is used as a placeholder for case where we don't have an actual message
* to use, but know (from context) that one is expected.
*/
protected final static ProtobufMessage UNKNOWN_MESSAGE = ProtobufMessage.bogusMessage("<unknown>");
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* @since 2.16
*/
protected final StreamWriteConstraints _streamWriteConstraints;
protected ProtobufSchema _schema;
/*
/**********************************************************
/* Output state
/**********************************************************
*/
/**
* Reference to the root context since that is needed for serialization
*/
protected ProtobufWriteContext _rootContext;
protected boolean _inObject;
/**
* Flag that indicates whether values should be written with tag or not;
* false for packed arrays, true for others.
*/
protected boolean _writeTag;
/**
* Flag that is set when the whole content is complete, can
* be output.
*/
protected boolean _complete;
/**
* Type of protobuf message that is currently being output: usually
* matches write context, but for arrays may indicate "parent" of array.
*/
protected ProtobufMessage _currMessage;
/**
* Field to be output next; set when {@link JsonToken#FIELD_NAME} is written,
* cleared once value has been written
*/
protected ProtobufField _currField;
/*
/**********************************************************
/* Output buffering
/**********************************************************
*/
/**
* Ultimate destination
*/
final protected OutputStream _output;
/**
* Object used in cases where we need to buffer content to calculate length-prefix.
*/
protected ByteAccumulator _buffered;
/**
* Current context, in form we can use it.
*/
protected ProtobufWriteContext _pbContext;
/**
* Currently active output buffer, place where appends occur.
*/
protected byte[] _currBuffer;
// TODO: remove work around in 2.8?
/**
* The first allocated (or recycled) buffer instance; needed to avoid
* issue [dataformat-protobuf#14].
*/
protected byte[] _origCurrBuffer;
protected int _currStart;
protected int _currPtr;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public ProtobufGenerator(IOContext ctxt, int jsonFeatures,
ObjectCodec codec, OutputStream output)
throws IOException
{
super(jsonFeatures, codec, ctxt, BOGUS_WRITE_CONTEXT);
_streamWriteConstraints = ctxt.streamWriteConstraints();
_output = output;
_pbContext = _rootContext = ProtobufWriteContext.createNullContext();
_currBuffer = _origCurrBuffer = ctxt.allocWriteEncodingBuffer();
}
public void setSchema(ProtobufSchema schema)
{
if (_schema == schema) {
return;
}
_schema = schema;
// start with temporary root...
// _currentContext = _rootContext = ProtobufWriteContext.createRootContext(this, schema);
_pbContext = _rootContext = ProtobufWriteContext.createRootContext(schema.getRootType());
}
/*
/**********************************************************
/* Versioned
/**********************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************
/* Capability introspection
/**********************************************************
*/
@Override // since 2.10
public boolean canWriteBinaryNatively() {
return true;
}
@Override // @since 2.12
public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
return DEFAULT_BINARY_WRITE_CAPABILITIES;
}
@Override
public boolean canUseSchema(FormatSchema schema) {
return (schema instanceof ProtobufSchema);
}
/*
/**********************************************************
/* Overridden methods, configuration
/**********************************************************
*/
@Override
public StreamWriteConstraints streamWriteConstraints() {
return _streamWriteConstraints;
}
/**
* Not sure whether to throw an exception or just do no-op; for now,
* latter.
*/
@Override
public ProtobufGenerator useDefaultPrettyPrinter() {
return this;
}
@Override
public ProtobufGenerator setPrettyPrinter(PrettyPrinter pp) {
return this;
}
@Override
public Object getOutputTarget() {
return _output;
}
/**
* Calculating actual amount of buffering is somewhat complicated, and can not
* necessarily give 100% accurate answer due to presence of VInt encoding for
* length indicators. So, for now, we'll respond "don't know": this may be
* improved if and as needed.
*/
@Override
public int getOutputBuffered() {
return -1;
}
@Override public ProtobufSchema getSchema() {
return _schema;
}
@Override
public void setSchema(FormatSchema schema)
{
if (!(schema instanceof ProtobufSchema)) {
throw new IllegalArgumentException("Can not use FormatSchema of type "
+schema.getClass().getName());
}
setSchema((ProtobufSchema) schema);
}
/*
/**********************************************************
/* Overridden methods, output context (and related)
/**********************************************************
*/
@Override // since 2.13
public Object currentValue() {
return _pbContext.getCurrentValue();
}
@Override // since 2.13
public void assignCurrentValue(Object v) {
_pbContext.setCurrentValue(v);
}
@Deprecated // since 2.17
@Override
public Object getCurrentValue() { return currentValue(); }
@Deprecated // since 2.17
@Override
public void setCurrentValue(Object v) { assignCurrentValue(v); }
/*
/**********************************************************************
/* Overridden methods; writing field names
/**********************************************************************
*/
/* And then methods overridden to make final, streamline some
* aspects...
*/
@Override
public final void writeFieldName(String name) throws IOException {
if (!_inObject) {
_reportError("Can not write field name: current context not Object but "+_pbContext.typeDesc());
}
ProtobufField f = _currField;
// important: use current field only if NOT repeated field; repeated
// field means an array until START_OBJECT
if (f != null && _pbContext.notArray()) {
f = f.nextIf(name);
if (f == null) {
f = _currMessage.field(name);
}
} else {
f = _currMessage.firstIf(name);
}
if (f == null) {
// May be ok, if we have said so
if ((_currMessage == UNKNOWN_MESSAGE)
|| isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
f = UNKNOWN_FIELD;
} else {
_reportError("Unrecognized field '"+name+"' (in Message of type "+_currMessage.getName()
+"); known fields are: "+_currMessage.fieldsAsString());
}
}
_pbContext.setField(f);
_currField = f;
}
@Override
public final void writeFieldName(SerializableString sstr) throws IOException {
if (!_inObject) {
_reportError("Can not write field name: current context not Object but "+_pbContext.typeDesc());
}
ProtobufField f = _currField;
final String name = sstr.getValue();
// important: use current field only if NOT repeated field; repeated
// field means an array until START_OBJECT
// NOTE: not ideal -- depends on if it really is sibling field of an array,
// or an entry within
if (f != null && _pbContext.notArray()) {
f = f.nextIf(name);
if (f == null) {
f = _currMessage.field(name);
}
} else {
f = _currMessage.firstIf(name);
}
if (f == null) {
// May be ok, if we have said so
if ((_currMessage == UNKNOWN_MESSAGE)
|| isEnabled(JsonGenerator.Feature.IGNORE_UNKNOWN)) {
f = UNKNOWN_FIELD;
} else {
_reportError("Unrecognized field '"+name+"' (in Message of type "+_currMessage.getName()
+"); known fields are: "+_currMessage.fieldsAsString());
}
}
_pbContext.setField(f);
_currField = f;
}
/*
/**********************************************************
/* Public API: low-level I/O
/**********************************************************
*/
@Override
public final void flush() throws IOException
{
// can only flush if we do not need accumulation for length prefixes
if (_buffered == null) {
int start = _currStart;
int len = _currPtr - start;
if (len > 0) {
_currStart = 0;
_currPtr = 0;
_output.write(_currBuffer, start, len);
}
}
if (isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)) {
_output.flush();
}
}
@Override
public void close() throws IOException
{
if (!isClosed()) {
if (isEnabled(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT)) {
ProtobufWriteContext ctxt;
while ((ctxt = _pbContext) != null) {
if (ctxt.inArray()) {
writeEndArray();
} else if (ctxt.inObject()) {
writeEndObject();
} else {
break;
}
}
}
// May need to finalize...
if (!_complete) {
_complete();
}
if (_output != null) {
if (_ioContext.isResourceManaged() || isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)) {
_output.close();
} else if (isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)) {
// 14-Jan-2019, tatu: [dataformats-binary#155]: unless prevented via feature
// If we can't close it, we should at least flush
_output.flush();
}
}
// Internal buffer(s) generator has can now be released as well
_releaseBuffers();
super.close();
}
}
/*
/**********************************************************
/* Public API: structural output
/**********************************************************
*/
@Override
public final void writeStartArray() throws IOException
{
// First: arrays only legal as Message (~= Object) fields:
if (!_inObject) {
_reportError("Current context not an OBJECT, can not write arrays");
}
if (_currField == null) { // just a sanity check
_reportError("Can not write START_ARRAY without field (message type "+_currMessage.getName()+")");
return; // never gets here but code analyzers can't see that
}
if (!_currField.isArray()) {
_reportError("Can not write START_ARRAY: field '"+_currField.name+"' not declared as 'repeated'");
}
// NOTE: do NOT clear _currField; needed for actual element type
_pbContext = _pbContext.createChildArrayContext();
streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth());
_writeTag = !_currField.packed;
/* Unpacked vs packed: if unpacked, nothing special is needed, since it
* is equivalent to just replicating same field N times.
* With packed, need length prefix, all that stuff, so need accumulator
*/
if (!_writeTag) { // packed
// note: tag SHOULD be written for array itself, but not contents
_startBuffering(_currField.typedTag);
}
}
@Override
public final void writeEndArray() throws IOException
{
if (!_pbContext.inArray()) {
_reportError("Current context not Array but "+_pbContext.typeDesc());
}
_pbContext = _pbContext.getParent();
if (_pbContext.inRoot()) {
if (!_complete) {
_complete();
}
_inObject = false;
} else {
_inObject = _pbContext.inObject();
}
// no arrays inside arrays, so parent can't be array and so:
_writeTag = true;
// but, did we just end up packed array?
if (_currField.packed) {
_finishBuffering();
}
}
@Override
public final void writeStartObject() throws IOException
{
if (_currField == null) {
// root?
if (!_pbContext.inRoot()) {
_reportError("Can not write START_OBJECT without field (message type "+_currMessage.getName()+")");
}
_currMessage = _schema.getRootType();
// note: no buffering on root
} else {
// but also, field value must be Message if so
if (!_currField.isObject) {
_reportError("Can not write START_OBJECT: type of field '"+_currField.name+"' not Message but: "+_currField.type);
}
_currMessage = _currField.getMessageType();
// and we need to start buffering, or add more nesting
// but we may or may not want to write tag for object
if (_writeTag) {
_startBuffering(_currField.typedTag);
} else {
_startBuffering();
}
}
if (_inObject) {
_pbContext = _pbContext.createChildObjectContext(_currMessage);
_currField = null;
} else { // must be array, then
_pbContext = _pbContext.createChildObjectContext(_currMessage);
// but do NOT clear next field here
_inObject = true;
}
streamWriteConstraints().validateNestingDepth(_pbContext.getNestingDepth());
// even if within array, object fields use tags
_writeTag = true;
}
@Override
public final void writeEndObject() throws IOException
{
if (!_inObject) {
_reportError("Current context not Object but "+_pbContext.typeDesc());
}
_pbContext = _pbContext.getParent();
if (_pbContext.inRoot()) {
if (!_complete) {
_complete();
}
} else {
_currMessage = _pbContext.getMessageType();
}
_currField = _pbContext.getField();
// possible that we might be within array, which might be packed:
boolean inObj = _pbContext.inObject();
_inObject = inObj;
_writeTag = inObj || !_pbContext.inArray() || !_currField.packed;
if (_buffered != null) { // null for root
_finishBuffering();
}
}
@Override
public void writeArray(int[] array, int offset, int length) throws IOException
{
_verifyArrayWrite(array);
_verifyOffsets(array.length, offset, length);
// one minor optimization: empty arrays do not produce anything
if (length > 0) {
// NOTE: as a short-cut, leave out construction of intermediate ARRAY
final int end = offset+length;
if (_currField.packed) {
_writePackedArray(array, offset, end);
} else {
_writeNonPackedArray(array, offset, end);
}
// and then pieces of END_ARRAY
_writeTag = true;
}
}
@Override
public void writeArray(long[] array, int offset, int length) throws IOException
{
_verifyArrayWrite(array);
_verifyOffsets(array.length, offset, length);
// one minor optimization: empty arrays do not produce anything
if (length > 0) {
// NOTE: as a short-cut, leave out construction of intermediate ARRAY
final int end = offset+length;
if (_currField.packed) {
_writePackedArray(array, offset, end);
} else {
_writeNonPackedArray(array, offset, end);
}
// and then pieces of END_ARRAY
_writeTag = true;
}
}
@Override
public void writeArray(double[] array, int offset, int length) throws IOException
{
_verifyArrayWrite(array);
_verifyOffsets(array.length, offset, length);
// one minor optimization: empty arrays do not produce anything
if (length > 0) {
// NOTE: as a short-cut, leave out construction of intermediate ARRAY
final int end = offset+length;
if (_currField.packed) {
_writePackedArray(array, offset, end);
} else {
_writeNonPackedArray(array, offset, end);
}
// and then pieces of END_ARRAY
_writeTag = true;
}
}
private void _verifyArrayWrite(Object array) throws IOException
{
if (array == null) {
throw new IllegalArgumentException("null array");
}
if (!_inObject) {
_reportError("Current context not an OBJECT, can not write arrays");
}
if (_currField == null) { // inlined _verifyValueWrite
_reportError("Can not write START_ARRAY without field (message type "+_currMessage.getName()+")");
return; // never gets here but need to help code analyzers
}
if (!_currField.isArray()) {
_reportError("Can not write START_ARRAY: field '"+_currField.name+"' not declared as 'repeated'");
}
}
private void _writePackedArray(int[] array, int i, int end) throws IOException
{
_startBuffering(_currField.typedTag);
final int type = _currField.wireType;
if (type == WireType.VINT) {
final boolean zigzag = _currField.usesZigZag;
for (; i < end; ++i) {
int v = array[i];
if (zigzag) {
v = ProtobufUtil.zigzagEncode(v);
}
_writeVIntNoTag(v);
}
} else if (type == WireType.FIXED_32BIT) {
for (; i < end; ++i) {
_writeInt32NoTag(array[i]);
}
} else if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64NoTag(array[i]);
}
} else {
_reportWrongWireType("int");
}
_finishBuffering();
}
private void _writePackedArray(long[] array, int i, int end) throws IOException
{
_startBuffering(_currField.typedTag);
final int type = _currField.wireType;
if (type == WireType.VINT) {
final boolean zigzag = _currField.usesZigZag;
for (; i < end; ++i) {
long v = array[i];
if (zigzag) {
v = ProtobufUtil.zigzagEncode(v);
}
_writeVLongNoTag(v);
}
} else if (type == WireType.FIXED_32BIT) {
for (; i < end; ++i) {
_writeInt32NoTag((int) array[i]);
}
} else if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64NoTag(array[i]);
}
} else {
_reportWrongWireType("int");
}
_finishBuffering();
}
private void _writePackedArray(double[] array, int i, int end) throws IOException
{
_startBuffering(_currField.typedTag);
final int type = _currField.wireType;
if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64NoTag(Double.doubleToLongBits( array[i]));
}
} else if (type == WireType.FIXED_32BIT) { // should we support such coercion?
for (; i < end; ++i) {
float f = (float) array[i];
_writeInt32NoTag(Float.floatToRawIntBits(f));
}
} else {
_reportWrongWireType("double");
}
_finishBuffering();
}
private void _writeNonPackedArray(int[] array, int i, int end) throws IOException
{
final int type = _currField.wireType;
if (type == WireType.VINT) {
final boolean zigzag = _currField.usesZigZag;
for (; i < end; ++i) {
int v = array[i];
if (zigzag) {
v = ProtobufUtil.zigzagEncode(v);
}
_writeVInt(v);
}
} else if (type == WireType.FIXED_32BIT) {
for (; i < end; ++i) {
_writeInt32(array[i]);
}
} else if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64(array[i]);
}
} else {
_reportWrongWireType("int");
}
}
private void _writeNonPackedArray(long[] array, int i, int end) throws IOException
{
final int type = _currField.wireType;
if (type == WireType.VINT) {
final boolean zigzag = _currField.usesZigZag;
for (; i < end; ++i) {
long v = array[i];
if (zigzag) {
v = ProtobufUtil.zigzagEncode(v);
}
_writeVLong(v);
}
} else if (type == WireType.FIXED_32BIT) {
for (; i < end; ++i) {
_writeInt32((int) array[i]);
}
} else if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64(array[i]);
}
} else {
_reportWrongWireType("int");
}
}
private void _writeNonPackedArray(double[] array, int i, int end) throws IOException
{
final int type = _currField.wireType;
if (type == WireType.FIXED_64BIT) {
for (; i < end; ++i) {
_writeInt64(Double.doubleToLongBits( array[i]));
}
} else if (type == WireType.FIXED_32BIT) { // should we support such coercion?
for (; i < end; ++i) {
float f = (float) array[i];
_writeInt32(Float.floatToRawIntBits(f));
}
} else {
_reportWrongWireType("double");
}
}
/*
/**********************************************************
/* Output method implementations, textual
/**********************************************************
*/
@Override
public void writeString(String text) throws IOException
{
if (text == null) {
writeNull();
return;
}
if (_currField.wireType != WireType.LENGTH_PREFIXED) {
_writeEnum(text);
return;
}
// Couple of choices; short (guaranteed to have length <= 127); medium (guaranteed
// to fit in single buffer); and large (something else)
final int clen = text.length();
// since max encoded = 3*42 == 126, could check for 42 (ta-dah!)
// ... or, speculate that we commonly get Ascii anyway, and just occasionally need to move
if (clen > 99) {
_encodeLongerString(text);
return;
}
if (clen == 0) {
_writeEmptyString();
return;
}
_verifyValueWrite();
_ensureRoom(clen+clen+clen+7); // up to 3 bytes per char; and possibly 2 bytes for length, 5 for tag
int ptr = _writeTag(_currPtr) + 1; // +1 to leave room for length indicator
final int start = ptr;
final byte[] buf = _currBuffer;
int i = 0;
while (true) {
int c = text.charAt(i);
if (c > 0x7F) {
break;
}
buf[ptr++] = (byte) c;
if (++i >= clen) { // done! Also, we know length is 7-bit
buf[start-1] = (byte) (ptr - start);
_currPtr = ptr;
return;
}
}
// no; non-ASCII stuff, slower loop
while (i < clen) {
int c = text.charAt(i++);
if (c <= 0x7F) {
buf[ptr++] = (byte) c;
continue;
}
// Nope, multi-byte:
if (c < 0x800) { // 2-byte
buf[ptr++] = (byte) (0xc0 | (c >> 6));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// 3 or 4 bytes (surrogate)
// Surrogates?
if (c < SURR1_FIRST || c > SURR2_LAST) { // nope, regular 3-byte character
buf[ptr++] = (byte) (0xe0 | (c >> 12));
buf[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// Yup, a surrogate pair
if (c > SURR1_LAST) { // must be from first range; second won't do
_throwIllegalSurrogate(c);
}
// ... meaning it must have a pair
if (i >= clen) {
_throwIllegalSurrogate(c);
}
c = _decodeSurrogate(c, text.charAt(i++));
if (c > 0x10FFFF) { // illegal in JSON as well as in XML
_throwIllegalSurrogate(c);
}
buf[ptr++] = (byte) (0xf0 | (c >> 18));
buf[ptr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
buf[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
}
// still fits in a single byte?
int blen = ptr-start;
if (blen <= 0x7F) { // expected case
buf[start-1] = (byte) blen;
} else { // but sometimes we got it wrong, need to move (bah)
System.arraycopy(buf, start, buf, start+1, blen);
buf[start-1] = (byte) (0x80 + (blen & 0x7F));
buf[start] = (byte) (blen >> 7);
++ptr;
}
_currPtr = ptr;
}
@Override
public void writeString(char[] text, int offset, int clen) throws IOException
{
if (text == null) {
writeNull();
return;
}
if (_currField.wireType != WireType.LENGTH_PREFIXED) {
_writeEnum(new String(text, offset, clen));
}
// Could guarantee with 42 chars or less; but let's do bit more speculative
if (clen > 99) {
_encodeLongerString(text, offset, clen);
return;
}
if (clen == 0) {
_writeEmptyString();
return;
}
_verifyValueWrite();
_ensureRoom(clen+clen+clen+7); // up to 3 bytes per char; and possibly 2 bytes for length, 5 for tag
int ptr = _writeTag(_currPtr) + 1; // +1 to leave room for length indicator
final int start = ptr;
final byte[] buf = _currBuffer;
final int end = offset + clen;
while (true) {
int c = text[offset];
if (c > 0x7F) {
break;
}
buf[ptr++] = (byte) c;
if (++offset >= end) { // done!
buf[start-1] = (byte) (ptr - start);
_currPtr = ptr;
return;
}
}
while (offset < end) {
int c = text[offset++];
if (c <= 0x7F) {
buf[ptr++] = (byte) c;
continue;
}
if (c < 0x800) {
buf[ptr++] = (byte) (0xc0 | (c >> 6));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
if (c < SURR1_FIRST || c > SURR2_LAST) {
buf[ptr++] = (byte) (0xe0 | (c >> 12));
buf[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
if (c > SURR1_LAST) {
_throwIllegalSurrogate(c);
}
// ... meaning it must have a pair
if (offset >= end) {
_throwIllegalSurrogate(c);
}
c = _decodeSurrogate(c, text[offset++]);
if (c > 0x10FFFF) { // illegal in JSON as well as in XML
_throwIllegalSurrogate(c);
}
buf[ptr++] = (byte) (0xf0 | (c >> 18));
buf[ptr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
buf[ptr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
buf[ptr++] = (byte) (0x80 | (c & 0x3f));
}
// still fits in a single byte?
int blen = ptr-start;
if (blen <= 0x7F) { // expected case
buf[start-1] = (byte) blen;
} else { // but sometimes we got it wrong, need to move (bah)
System.arraycopy(buf, start, buf, start+1, blen);
buf[start-1] = (byte) (0x80 + (blen & 0x7F));
buf[start] = (byte) (blen >> 7);
++ptr;
}
_currPtr = ptr;
}
@Override
public final void writeString(SerializableString sstr) throws IOException
{
_verifyValueWrite();
if (_currField.wireType == WireType.LENGTH_PREFIXED) {
byte[] b = sstr.asUnquotedUTF8();
_writeLengthPrefixed(b, 0, b.length);
} else if (_currField.type == FieldType.ENUM) {
int index = _currField.findEnumIndex(sstr);
if (index < 0) {
_reportEnumError(sstr);
}
_writeEnum(index);
} else {
_reportWrongWireType("string");
}
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int len) throws IOException
{
if (_currField.wireType != WireType.LENGTH_PREFIXED) {
_reportWrongWireType("string");
return;
}
_verifyValueWrite();
_writeLengthPrefixed(text, offset, len);
}
@Override
public final void writeUTF8String(byte[] text, int offset, int len) throws IOException
{
if (_currField.wireType != WireType.LENGTH_PREFIXED) {
_reportWrongWireType("string");
return;
}
_verifyValueWrite();
_writeLengthPrefixed(text, offset, len);
}
protected void _writeEmptyString() throws IOException
{
_verifyValueWrite();
_ensureRoom(6); // up to 5 bytes for tag + 1 for length
_currPtr = _writeTag(_currPtr);
_currBuffer[_currPtr++] = 0; // length
}
protected void _writeEnum(String str) throws IOException
{
if (_currField.type != FieldType.ENUM) {
_reportWrongWireType("string");
}
// !!! TODO: optimize
int index = _currField.findEnumIndex(str);
if (index < 0) {
_reportEnumError(str);
}
// basically, _writeVInt, but very likely to be very short; but if not:
final int tag = _currField.typedTag;
int ptr = _currPtr;
if (index > 0x7F || tag > 0x7F || (ptr + 1) >= _currBuffer.length) {
_writeVInt(index);
return;
}
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) tag;
buf[ptr++] = (byte) index;
_currPtr = ptr;
}
protected void _writeEnum(int index) throws IOException
{
// basically, _writeVInt, but very likely to be very short; but if not:
final int tag = _currField.typedTag;
int ptr = _currPtr;
if (index > 0x7F || tag > 0x7F || (ptr + 1) >= _currBuffer.length) {
_writeVInt(index);
return;
}
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) tag;
buf[ptr++] = (byte) index;
_currPtr = ptr;
}
protected void _reportEnumError(Object enumValue) throws IOException
{
_reportErrorF("No Enum '%s' found for property '%s'; valid values = %s"
+_currField.getEnumValues(), _currField.name, enumValue);
}
/*
/**********************************************************
/* Output method implementations, unprocessed ("raw")
/**********************************************************
*/
@Override
public void writeRaw(String text) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(String text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(char[] text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRaw(char c) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRawValue(String text) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRawValue(String text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
@Override
public void writeRawValue(char[] text, int offset, int len) throws IOException {
_reportUnsupportedOperation();
}
/*
/**********************************************************
/* Output method implementations, base64-encoded binary
/**********************************************************
*/
@Override
public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException
{
if (data == null) {
writeNull();
return;
}
_verifyValueWrite();
// Unlike with some other formats, let's NOT Base64 encoded even if nominally
// expecting String, and rather assume that if so, caller just provides as
// raw bytes of String
if (_currField.wireType != WireType.LENGTH_PREFIXED) {
_reportWrongWireType("binary");
return;
}
_ensureRoom(10);
_writeLengthPrefixed(data, offset, len);
}
/*
/**********************************************************
/* Output method implementations, primitive
/**********************************************************
*/
@Override
public void writeBoolean(boolean state) throws IOException
{
_verifyValueWrite();
// same as 'writeNumber(int)', really
final int type = _currField.wireType;
if (type == WireType.VINT) { // first, common case
int b;
if (_currField.usesZigZag) {
b = state ? 2 : 0;
} else {
b = state ? 1 : 0;
}
_writeVInt(b);
return;
}
if (type == WireType.FIXED_32BIT) {
_writeInt32(state ? 1 : 0);
return;
}
if (type == WireType.FIXED_64BIT) {
_writeInt64(state ? 1L : 0L);
return;
}
_reportWrongWireType("boolean");
}
@Override
public void writeNull() throws IOException
{
_verifyValueWrite();
if (_currField == UNKNOWN_FIELD) {
return;
}
// protobuf has no way of writing null does it?
// ...but should we try to add placeholders in arrays?
/* 10-Nov-2014, tatu: At least one problem: required fields...
* Let's complain about those? In future, could add placeholders
* for such cases.
*/
if (_currField.required) {
_reportError("Can not omit writing of `null` value for required field '"+_currField.name+"' (type "+_currField.type+")");
}
}
@Override
public void writeNumber(int v) throws IOException
{
_verifyValueWrite();
final int type = _currField.wireType;
if (type == WireType.VINT) { // first, common case
if (_currField.usesZigZag) {
v = ProtobufUtil.zigzagEncode(v);
}
_writeVInt(v);
return;
}
if (type == WireType.FIXED_32BIT) {
_writeInt32(v);
return;
}
if (type == WireType.FIXED_64BIT) {
_writeInt64(v);
return;
}
_reportWrongWireType("int");
}
@Override
public void writeNumber(long v) throws IOException
{
_verifyValueWrite();
final int type = _currField.wireType;
if (type == WireType.VINT) { // first, common case
if (_currField.usesZigZag) {
v = ProtobufUtil.zigzagEncode(v);
}
// is this ok?
_writeVLong(v);
return;
}
if (type == WireType.FIXED_32BIT) {
_writeInt32((int) v);
return;
}
if (type == WireType.FIXED_64BIT) {
_writeInt64(v);
return;
}
_reportWrongWireType("long");
}
@Override
public void writeNumber(BigInteger v) throws IOException
{
if (v == null) {
writeNull();
return;
}
if (_currField == UNKNOWN_FIELD) {
return;
}
// !!! TODO: better scheme to detect overflow or something
writeNumber(v.longValue());
}
@Override
public void writeNumber(double d) throws IOException
{
_verifyValueWrite();
final int type = _currField.wireType;
if (type == WireType.FIXED_32BIT) {
// should we coerce like this?
float f = (float) d;
_writeInt32(Float.floatToRawIntBits(f));
return;
}
if (type == WireType.FIXED_64BIT) {
_writeInt64(Double.doubleToLongBits(d));
return;
}
if (_currField.type == FieldType.STRING) {
_encodeLongerString(String.valueOf(d));
return;
}
_reportWrongWireType("double");
}
@Override
public void writeNumber(float f) throws IOException
{
_verifyValueWrite();
final int type = _currField.wireType;
if (type == WireType.FIXED_32BIT) {
_writeInt32(Float.floatToRawIntBits(f));
return;
}
if (type == WireType.FIXED_64BIT) {
_writeInt64(Double.doubleToLongBits((double) f));
return;
}
if (_currField.type == FieldType.STRING) {
_encodeLongerString(String.valueOf(f));
return;
}
_reportWrongWireType("float");
}
@Override
public void writeNumber(BigDecimal v) throws IOException
{
if (v == null) {
writeNull();
return;
}
if (_currField == UNKNOWN_FIELD) {
return;
}
// !!! TODO: better handling here... exception or write as string or... ?
writeNumber(v.doubleValue());
}
@Override
public void writeNumber(String encodedValue) throws IOException {
throw new UnsupportedOperationException("Can not write 'untyped' numbers");
}
protected final void _verifyValueWrite() throws IOException {
if (_currField == null) {
_reportError("Can not write value without indicating field first (in message of type "+_currMessage.getName()+")");
}
}
/*
/**********************************************************
/* Implementations for methods from base class
/**********************************************************
*/
@Override
protected void _verifyValueWrite(String typeMsg) throws IOException {
_throwInternal();
}
@Override
protected void _releaseBuffers() {
byte[] b = _currBuffer;
if (b != null) {
_currBuffer = null;
byte[] b2 = _origCurrBuffer;
// 07-Mar-2016, tatu: Crude, but until jackson-core 2.8, need
// to work around an issue by only returning current buffer
// if it is early same as original, or larger
byte[] toRelease = ((b == b2) || (b.length > b2.length)) ? b : b2;
_ioContext.releaseWriteEncodingBuffer(toRelease);
}
}
/*
/**********************************************************
/* Internal text/binary writes
/**********************************************************
*/
private final static Charset UTF8 = Charset.forName("UTF-8");
protected void _encodeLongerString(char[] text, int offset, int clen) throws IOException
{
_verifyValueWrite();
byte[] b = new String(text, offset, clen).getBytes(UTF8);
_writeLengthPrefixed(b, 0, b.length);
}
protected void _encodeLongerString(String text) throws IOException
{
byte[] b = text.getBytes(UTF8);
_writeLengthPrefixed(b, 0, b.length);
}
protected void _writeLengthPrefixed(byte[] data, int offset, int len) throws IOException
{
// 15-Jun-2017, tatu: [dataformats-binary#94]: need to ensure there is actually
// enough space for simple add; if not, need more checking
_ensureRoom(10); // max tag 5 bytes, ditto max length
int ptr = _writeTag(_currPtr);
ptr = ProtobufUtil.appendLengthLength(len, _currBuffer, ptr);
// and then loop until we are done
while (len > 0) {
int max = Math.min(len, _currBuffer.length - ptr);
System.arraycopy(data, offset, _currBuffer, ptr, max);
ptr += max;
if ((len -= max) == 0) {
_currPtr = ptr;
break;
}
offset += max;
ByteAccumulator acc = _buffered;
final int start = _currStart;
_currStart = 0;
int toFlush = ptr - start;
ptr = 0;
// without accumulation, we know buffer is free for reuse
if (acc == null) {
if (toFlush > 0) {
_output.write(_currBuffer, start, toFlush);
}
ptr = 0;
continue;
}
// but with buffered, need to append, allocate new buffer (since old
// almost certainly contains buffered data)
if (toFlush > 0) {
acc.append(_currBuffer, start, toFlush);
}
_currBuffer = ProtobufUtil.allocSecondary(_currBuffer);
}
}
/*
/**********************************************************
/* Internal scalar value writes
/**********************************************************
*/
private final void _writeVInt(int v) throws IOException
{
// Max tag length 5 bytes, then at most 5 bytes
_ensureRoom(10);
int ptr = _writeTag(_currPtr);
if (v < 0) {
_currPtr = _writeVIntMax(v, ptr);
return;
}
final byte[] buf = _currBuffer;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) (0x80 + (v & 0x7F));
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
// and now must have at most 3 bits (since negatives were offlined)
buf[ptr++] = (byte) (v & 0x7F);
}
}
}
}
_currPtr = ptr;
}
// @since 2.8
private final void _writeVIntNoTag(int v) throws IOException
{
// Max at most 5 bytes
_ensureRoom(5);
int ptr = _currPtr;
if (v < 0) {
_currPtr = _writeVIntMax(v, ptr);
return;
}
final byte[] buf = _currBuffer;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) (0x80 + (v & 0x7F));
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
// and now must have at most 3 bits (since negatives were offlined)
buf[ptr++] = (byte) (v & 0x7F);
}
}
}
}
_currPtr = ptr;
}
// off-lined version for 5-byte VInts
private final int _writeVIntMax(int v, int ptr) throws IOException
{
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>>= 7;
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
v >>= 7;
buf[ptr++] = (byte) v;
return ptr;
}
private final void _writeVLong(long v) throws IOException
{
// Max tag length 5 bytes, then at most 10 bytes
_ensureRoom(16);
int ptr = _writeTag(_currPtr);
if (v < 0L) {
_currPtr = _writeVLongMax(v, ptr);
return;
}
// first, 4 bytes or less?
if (v <= 0x0FFFFFFF) {
int i = (int) v;
final byte[] buf = _currBuffer;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
do {
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
} while (i > 0x7F);
buf[ptr++] = (byte) i;
}
_currPtr = ptr;
return;
}
// nope, so we know 28 LSBs are to be written first
int i = (int) v;
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
v >>>= 28;
// still got 36 bits, chop of LSB
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
// but then can switch to int for remaining max 28 bits
i = (int) (v >> 7);
do {
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
} while (i > 0x7F);
buf[ptr++] = (byte) i;
}
_currPtr = ptr;
}
// @since 2.8
private final void _writeVLongNoTag(long v) throws IOException
{
// Max: 10 bytes
_ensureRoom(10);
int ptr = _currPtr;
if (v < 0L) {
_currPtr = _writeVLongMax(v, ptr);
return;
}
// first, 4 bytes or less?
if (v <= 0x0FFFFFFF) {
int i = (int) v;
final byte[] buf = _currBuffer;
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
do {
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
} while (i > 0x7F);
buf[ptr++] = (byte) i;
}
_currPtr = ptr;
return;
}
// nope, so we know 28 LSBs are to be written first
int i = (int) v;
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
v >>>= 28;
// still got 36 bits, chop of LSB
if (v <= 0x7F) {
buf[ptr++] = (byte) v;
} else {
buf[ptr++] = (byte) ((v & 0x7F) + 0x80);
// but then can switch to int for remaining max 28 bits
i = (int) (v >> 7);
do {
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
} while (i > 0x7F);
buf[ptr++] = (byte) i;
}
_currPtr = ptr;
}
// off-lined version for 10-byte VLongs
private final int _writeVLongMax(long v, int ptr) throws IOException
{
final byte[] buf = _currBuffer;
// first, LSB 28 bits
int i = (int) v;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
// then next 28 (for 56 so far)
i = (int) (v >>> 28);
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
// and last 2 (for 7 bits, and 1 bit, respectively)
i = (int) (v >>> 56);
buf[ptr++] = (byte) ((i & 0x7F) + 0x80);
i >>= 7;
buf[ptr++] = (byte) i;
return ptr;
}
private final void _writeInt32(int v) throws IOException
{
_ensureRoom(9); // max tag 5 bytes
int ptr = _writeTag(_currPtr);
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
_currPtr = ptr;
}
private final void _writeInt32NoTag(int v) throws IOException
{
_ensureRoom(4);
int ptr = _currPtr;
final byte[] buf = _currBuffer;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
_currPtr = ptr;
}
private final void _writeInt64(long v64) throws IOException
{
_ensureRoom(13); // max tag 5 bytes
int ptr = _writeTag(_currPtr);
final byte[] buf = _currBuffer;
int v = (int) v64;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v = (int) (v64 >> 32);
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
_currPtr = ptr;
}
private final void _writeInt64NoTag(long v64) throws IOException
{
_ensureRoom(8);
int ptr = _currPtr;
final byte[] buf = _currBuffer;
int v = (int) v64;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v = (int) (v64 >> 32);
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
_currPtr = ptr;
}
/*
/**********************************************************
/* Helper methods, buffering
/**********************************************************
*/
private final int _writeTag(int ptr)
{
if (_writeTag) {
final byte[] buf = _currBuffer;
int tag = _currField.typedTag;
if (tag <= 0x7F) {
buf[ptr++] = (byte) tag;
} else {
// Note: caller must have ensured space for at least 5 bytes
do {
buf[ptr++] = (byte) ((tag & 0x7F) + 0x80);
tag >>= 7;
} while (tag > 0x7F);
buf[ptr++] = (byte) tag;
}
}
return ptr;
}
/**
* Method called when buffering an entry that should be prefixed
* with a type tag.
*/
private final void _startBuffering(int typedTag) throws IOException
{
// need to ensure room for tag id, length (10 bytes); might as well ask for bit more
_ensureRoom(20);
// and leave the gap of 10 bytes
int ptr = _currPtr;
int start = _currStart;
// root level content to flush first?
if (_buffered == null) {
int len = ptr - start;
if (len > 0) {
ptr = start = 0;
_output.write(_currBuffer, start, len);
}
}
_buffered = new ByteAccumulator(_buffered, typedTag, _currBuffer, ptr, _currStart);
ptr += 10;
_currStart = ptr;
_currPtr = ptr;
}
/**
* Method called when buffering an entry that should not be prefixed
* with a type tag.
*/
private final void _startBuffering() throws IOException
{
// since no tag written, could skimp on space needed
_ensureRoom(16);
int ptr = _currPtr;
/* 02-Jun-2015, tatu: It would seem like we should check for flushing here,
* similar to method above. But somehow that does not seem to be needed...
* Let's add it just to be safe, still.
*/
// 04-Apr-2017, tatu: Most likely this is because this can only happen when we are
// writing Objects as elements of packed array; and this can not be root-level
// value: and if so we must be buffering (to get length prefix)
/*
if (_buffered == null) {
int len = ptr - _currStart;
if (len > 0) {
ptr = 0;
_output.write(_currBuffer, _currStart, len);
}
}
*/
_buffered = new ByteAccumulator(_buffered, -1, _currBuffer, ptr, _currStart);
ptr += 5;
_currStart = ptr;
_currPtr = ptr;
}
/**
* Helper method called when the current buffering scope is closed;
* when packed array is completed (`writeEndArray()`) or record is
* completed (`writeEndObject()`).
*/
private final void _finishBuffering() throws IOException
{
final int start = _currStart;
final int newStart = _currPtr;
final int currLen = newStart - start;
ByteAccumulator acc = _buffered;
acc = acc.finish(_output, _currBuffer, start, currLen);
_buffered = acc;
if (acc == null) {
_currStart = 0;
_currPtr = 0;
return;
}
_currStart = newStart;
// already same, no need to change
// _currPtr = newStart;
}
protected final void _ensureRoom(int needed) throws IOException
{
// common case: we got it already
if ((_currPtr + needed) > _currBuffer.length) {
_ensureMore();
}
}
protected final void _ensureMore() throws IOException
{
// if not, either simple (flush), or
final int start = _currStart;
final int currLen = _currPtr - start;
_currStart = 0;
_currPtr = 0;
ByteAccumulator acc = _buffered;
if (acc == null) {
// without accumulation, we know buffer is free for reuse
if (currLen > 0) {
_output.write(_currBuffer, start, currLen);
}
return;
}
// but with buffered, need to append, allocate new buffer (since old
// almost certainly contains buffered data)
if (currLen > 0) {
acc.append(_currBuffer, start, currLen);
}
_currBuffer = ProtobufUtil.allocSecondary(_currBuffer);
}
protected void _complete() throws IOException
{
_complete = true;
final int start = _currStart;
final int currLen = _currPtr - start;
_currPtr = start;
ByteAccumulator acc = _buffered;
if (acc == null) {
if (currLen > 0) {
_output.write(_currBuffer, start, currLen);
_currStart = 0;
_currPtr = 0;
}
} else {
acc = acc.finish(_output, _currBuffer, start, currLen);
while (acc != null) {
acc = acc.finish(_output, _currBuffer);
}
_buffered = null;
}
}
/*
/**********************************************************
/* Helper methods, error reporting
/**********************************************************
*/
protected void _reportWrongWireType(String typeStr) throws IOException {
if (_currField == UNKNOWN_FIELD) {
return;
}
_reportErrorF("Can not write `string` value for '%s' (type %s)",
_currField.name, _currField.type);
}
private void _reportErrorF(String format, Object... args) throws JsonGenerationException {
_reportError(String.format(format, args));
}
private void _throwIllegalSurrogate(int code)
{
if (code > 0x10FFFF) { // over max?
throw new IllegalArgumentException("Illegal character point (0x"+Integer.toHexString(code)+") to output; max is 0x10FFFF as per RFC 4627");
}
if (code >= SURR1_FIRST) {
if (code <= SURR1_LAST) { // Unmatched first part (closing without second part?)
throw new IllegalArgumentException("Unmatched first part of surrogate pair (0x"+Integer.toHexString(code)+")");
}
throw new IllegalArgumentException("Unmatched second part of surrogate pair (0x"+Integer.toHexString(code)+")");
}
// should we ever get this?
throw new IllegalArgumentException("Illegal character point (0x"+Integer.toHexString(code)+") to output");
}
}