SmileGenerator.java
package com.fasterxml.jackson.dataformat.smile;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.*;
import com.fasterxml.jackson.core.json.DupDetector;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.core.base.GeneratorBase;
import static com.fasterxml.jackson.dataformat.smile.SmileConstants.*;
/**
* {@link JsonGenerator} implementation for Smile-encoded content
* (see <a href="http://wiki.fasterxml.com/SmileFormatSpec">Smile Format Specification</a>)
*/
public class SmileGenerator
extends GeneratorBase
{
// @since 2.16
protected final static int DEFAULT_NAME_BUFFER_LENGTH = 64;
// @since 2.16
protected final static int DEFAULT_STRING_VALUE_BUFFER_LENGTH = 64;
/**
* Enumeration that defines all togglable features for Smile generators.
*/
public enum Feature
implements FormatFeature // since 2.7
{
/**
* Whether to write 4-byte header sequence when starting output or not.
* If disabled, no header is written; this may be useful in embedded cases
* where context is enough to know that content is encoded using this format.
* Note, however, that omitting header means that default settings for
* shared names/string values can not be changed.
*<p>
* Default setting is true, meaning that header will be written.
*/
WRITE_HEADER(true),
/**
* Whether write byte marker that signifies end of logical content segment
* ({@link SmileConstants#BYTE_MARKER_END_OF_CONTENT}) when
* {@link #close} is called or not. This can be useful when outputting
* multiple adjacent logical content segments (documents) into single
* physical output unit (file).
*<p>
* Default setting is false meaning that such marker is not written.
*/
WRITE_END_MARKER(false),
/**
* Whether to use simple 7-bit per byte encoding for binary content when output.
* This is necessary ensure that byte 0xFF will never be included in content output.
* For other data types this limitation is handled automatically. This setting is enabled
* by default, however, overhead for binary data (14% size expansion, processing overhead)
* is non-negligible. If no binary data is output, feature has no effect.
*<p>
* Default setting is true, indicating that binary data is quoted as 7-bit bytes
* instead of written raw.
*/
ENCODE_BINARY_AS_7BIT(true),
/**
* Whether generator should check if it can "share" field names during generating
* content or not. If enabled, can replace repeating field names with back references,
* which are more compact and should faster to decode. Downside is that there is some
* overhead for writing (need to track existing values, check), as well as decoding.
*<p>
* Since field names tend to repeat quite often, this setting is enabled by default.
*/
CHECK_SHARED_NAMES(true),
/**
* Whether generator should check if it can "share" short (at most 64 bytes encoded)
* String value during generating
* content or not. If enabled, can replace repeating Short String values with back references,
* which are more compact and should faster to decode. Downside is that there is some
* overhead for writing (need to track existing values, check), as well as decoding.
*<p>
* Since efficiency of this option depends a lot on type of content being produced,
* this option is disabled by default, and should only be enabled if it is likely that
* same values repeat relatively often.
*/
CHECK_SHARED_STRING_VALUES(false),
/**
* Feature that determines if an invalid surrogate encoding found in the
* incoming String should fail with an exception or silently be output
* as the Unicode 'REPLACEMENT CHARACTER' (U+FFFD) or not; if not,
* an exception will be thrown to indicate invalid content.
*<p>
* Default value is {@code false} (for backwards compatibility) meaning that
* an invalid surrogate will result in exception ({@link IllegalArgumentException}
*
* @since 2.13
*/
LENIENT_UTF_ENCODING(false),
;
protected final boolean _defaultState;
protected final int _mask;
/**
* Method that calculates bit set (flags) of all features that
* are enabled by default.
*/
public static int collectDefaults()
{
int flags = 0;
for (Feature f : values()) {
if (f.enabledByDefault()) {
flags |= f.getMask();
}
}
return flags;
}
private Feature(boolean defaultState) {
_defaultState = defaultState;
_mask = (1 << ordinal());
}
@Override public boolean enabledByDefault() { return _defaultState; }
@Override public int getMask() { return _mask; }
@Override public boolean enabledIn(int flags) { return (flags & _mask) != 0; }
}
/**
* Helper class used for keeping track of possibly shareable String
* references (for field names and/or short String values)
*/
public final static class SharedStringNode
{
public final String value;
public final int index;
public SharedStringNode next;
public SharedStringNode(String value, int index, SharedStringNode next)
{
this.value = value;
this.index = index;
this.next = next;
}
}
/**
* To simplify certain operations, we require output buffer length
* to allow outputting of contiguous 256 character UTF-8 encoded String
* value. Length of the longest UTF-8 code point (from Java char) is 3 bytes,
* and we need both initial token byte and single-byte end marker
* so we get following value.
*<p>
* Note: actually we could live with shorter one; absolute minimum would
* be for encoding 64-character Strings.
*/
private final static int MIN_BUFFER_LENGTH = (3 * 256) + 2;
protected final static byte TOKEN_BYTE_LONG_STRING_ASCII = TOKEN_MISC_LONG_TEXT_ASCII;
protected final static byte TOKEN_BYTE_INT_32 = (byte) (SmileConstants.TOKEN_PREFIX_INTEGER + TOKEN_MISC_INTEGER_32);
protected final static byte TOKEN_BYTE_INT_64 = (byte) (SmileConstants.TOKEN_PREFIX_INTEGER + TOKEN_MISC_INTEGER_64);
protected final static byte TOKEN_BYTE_BIG_INTEGER = (byte) (SmileConstants.TOKEN_PREFIX_INTEGER + TOKEN_MISC_INTEGER_BIG);
protected final static byte TOKEN_BYTE_FLOAT_32 = (byte) (SmileConstants.TOKEN_PREFIX_FP | TOKEN_MISC_FLOAT_32);
protected final static byte TOKEN_BYTE_FLOAT_64 = (byte) (SmileConstants.TOKEN_PREFIX_FP | TOKEN_MISC_FLOAT_64);
protected final static byte TOKEN_BYTE_BIG_DECIMAL = (byte) (SmileConstants.TOKEN_PREFIX_FP | TOKEN_MISC_FLOAT_BIG);
protected final static long MIN_INT_AS_LONG = (long) Integer.MIN_VALUE;
protected final static long MAX_INT_AS_LONG = (long) Integer.MAX_VALUE;
/**
* The replacement character to use to fix invalid Unicode sequences
* (mismatched surrogate pair).
*
* @since 2.13
*/
protected final static int REPLACEMENT_CHAR = 0xfffd;
/*
/**********************************************************************
/* Configuration
/**********************************************************************
*/
/**
* @since 2.16
*/
protected final StreamWriteConstraints _streamWriteConstraints;
protected final OutputStream _out;
/**
* Bit flag composed of bits that indicate which
* {@link com.fasterxml.jackson.dataformat.smile.SmileGenerator.Feature}s
* are enabled.
*/
protected int _formatFeatures;
/*
/**********************************************************************
/* Output state
/**********************************************************************
*/
// @since 2.10 (named _smileContext before 2.13)
protected SmileWriteContext _streamWriteContext;
/*
/**********************************************************************
/* Output buffering
/**********************************************************************
*/
/**
* Intermediate buffer in which contents are buffered before
* being written using {@link #_out}.
*/
protected byte[] _outputBuffer;
/**
* Pointer to the next available byte in {@link #_outputBuffer}
*/
protected int _outputTail = 0;
/**
* Offset to index after the last valid index in {@link #_outputBuffer}.
* Typically same as length of the buffer.
*/
protected final int _outputEnd;
/**
* Let's keep track of how many bytes have been output, may prove useful
* when debugging. This does <b>not</b> include bytes buffered in
* the output buffer, just bytes that have been written using underlying
* stream writer.
*/
protected int _bytesWritten;
/*
/**********************************************************************
/* Shared String detection
/**********************************************************************
*/
/**
* Raw data structure used for checking whether field name to
* write can be output using back reference or not.
*/
protected SharedStringNode[] _seenNames;
/**
* Number of entries in {@link #_seenNames}; -1 if no shared name
* detection is enabled
*/
protected int _seenNameCount;
/**
* Raw data structure used for checking whether String value to
* write can be output using back reference or not.
*/
protected SharedStringNode[] _seenStringValues;
/**
* Number of entries in {@link #_seenStringValues}; -1 if no shared text value
* detection is enabled
*/
protected int _seenStringValueCount;
/**
* Flag that indicates whether the output buffer is recyclable (and
* needs to be returned to recycler once we are done) or not.
*/
protected boolean _bufferRecyclable;
/*
/**********************************************************************
/* Life-cycle
/**********************************************************************
*/
/**
* @since 2.16
*/
public SmileGenerator(IOContext ioCtxt, int stdFeatures, int smileFeatures,
ObjectCodec codec, OutputStream out)
{
super(stdFeatures, codec, ioCtxt, /*WriteContext*/ null);
DupDetector dups = JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(stdFeatures)
? DupDetector.rootDetector(this)
: null;
// NOTE: we passed `null` for default write context
_streamWriteContext = SmileWriteContext.createRootContext(dups);
_formatFeatures = smileFeatures;
_streamWriteConstraints = ioCtxt.streamWriteConstraints();
_out = out;
_bufferRecyclable = true;
_outputBuffer = ioCtxt.allocWriteEncodingBuffer();
_outputEnd = _outputBuffer.length;
// let's just sanity check to prevent nasty odd errors
if (_outputEnd < MIN_BUFFER_LENGTH) {
throw new IllegalStateException(String.format(
"Internal encoding buffer length (%d) too short, must be at least %d",
_outputEnd, MIN_BUFFER_LENGTH));
}
if (!Feature.CHECK_SHARED_NAMES.enabledIn(smileFeatures)) {
_seenNames = null;
_seenNameCount = -1;
} else {
_seenNames = new SharedStringNode[DEFAULT_NAME_BUFFER_LENGTH];
_seenNameCount = 0;
}
if (!Feature.CHECK_SHARED_STRING_VALUES.enabledIn(smileFeatures)) {
_seenStringValues = null;
_seenStringValueCount = -1;
} else {
_seenStringValues = new SharedStringNode[DEFAULT_STRING_VALUE_BUFFER_LENGTH];
_seenStringValueCount = 0;
}
}
/**
* @since 2.16
*/
public SmileGenerator(IOContext ioCtxt, int stdFeatures, int smileFeatures,
ObjectCodec codec, OutputStream out,
byte[] outputBuffer, int offset, boolean bufferRecyclable)
{
super(stdFeatures, codec, ioCtxt, null);
DupDetector dups = JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(stdFeatures)
? DupDetector.rootDetector(this)
: null;
// NOTE: we passed `null` for default write context
_streamWriteContext = SmileWriteContext.createRootContext(dups);
_formatFeatures = smileFeatures;
_streamWriteConstraints = ioCtxt.streamWriteConstraints();
_out = out;
_bufferRecyclable = bufferRecyclable;
_outputTail = offset;
_outputBuffer = outputBuffer;
_outputEnd = _outputBuffer.length;
// let's just sanity check to prevent nasty odd errors
if (_outputEnd < MIN_BUFFER_LENGTH) {
throw new IllegalStateException(String.format(
"Internal encoding buffer length (%d) too short, must be at least %d",
_outputEnd, MIN_BUFFER_LENGTH));
}
if (!Feature.CHECK_SHARED_NAMES.enabledIn(smileFeatures)) {
_seenNames = null;
_seenNameCount = -1;
} else {
_seenNames = new SharedStringNode[DEFAULT_NAME_BUFFER_LENGTH];
_seenNameCount = 0;
}
if (!Feature.CHECK_SHARED_STRING_VALUES.enabledIn(smileFeatures)) {
_seenStringValues = null;
_seenStringValueCount = -1;
} else {
_seenStringValues = new SharedStringNode[DEFAULT_STRING_VALUE_BUFFER_LENGTH];
_seenStringValueCount = 0;
}
}
/**
* Method that can be called to explicitly write Smile document header.
* Note that usually you do not need to call this for first document to output,
* but rather only if you intend to write multiple root-level documents
* with same generator (and even in that case this is optional thing to do).
* As a result usually only {@link SmileFactory} calls this method.
*/
public void writeHeader() throws IOException
{
int last = HEADER_BYTE_4;
if (Feature.CHECK_SHARED_NAMES.enabledIn(_formatFeatures)) {
last |= SmileConstants.HEADER_BIT_HAS_SHARED_NAMES;
}
if (Feature.CHECK_SHARED_STRING_VALUES.enabledIn(_formatFeatures)) {
last |= SmileConstants.HEADER_BIT_HAS_SHARED_STRING_VALUES;
}
if (!Feature.ENCODE_BINARY_AS_7BIT.enabledIn(_formatFeatures)) {
last |= SmileConstants.HEADER_BIT_HAS_RAW_BINARY;
}
_writeBytes(HEADER_BYTE_1, HEADER_BYTE_2, HEADER_BYTE_3, (byte) last);
}
/*
/**********************************************************
/* Versioned
/**********************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************
/* Capability introspection
/**********************************************************
*/
@Override
public boolean canWriteBinaryNatively() {
return true;
}
@Override // @since 2.12
public JacksonFeatureSet<StreamWriteCapability> getWriteCapabilities() {
return DEFAULT_BINARY_WRITE_CAPABILITIES;
}
/*
/**********************************************************
/* Overridden methods, configuration
/**********************************************************
*/
/**
* No way (or need) to indent anything, so let's block any attempts.
* (should we throw an exception instead?)
*/
@Override
public JsonGenerator useDefaultPrettyPrinter() {
return this;
}
/**
* No way (or need) to indent anything, so let's block any attempts.
* (should we throw an exception instead?)
*/
@Override
public JsonGenerator setPrettyPrinter(PrettyPrinter pp) {
return this;
}
@Override
public Object getOutputTarget() {
return _out;
}
@Override
public int getOutputBuffered() {
return _outputTail;
}
// public JsonParser overrideStdFeatures(int values, int mask)
@Override
public int getFormatFeatures() {
return _formatFeatures;
}
@Override
public JsonGenerator overrideFormatFeatures(int values, int mask) {
_formatFeatures = (_formatFeatures & ~mask) | (values & mask);
return this;
}
/*
/**********************************************************
/* Overridden methods, output context (and related)
/**********************************************************
*/
@Override
public JsonStreamContext getOutputContext() {
return _streamWriteContext;
}
@Override // since 2.13
public Object currentValue() {
return _streamWriteContext.getCurrentValue();
}
@Override // since 2.13
public void assignCurrentValue(Object v) {
_streamWriteContext.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, write methods
/**********************************************************
*/
/* And then methods overridden to make final, streamline some
* aspects...
*/
@Override
public final void writeFieldName(String name) throws IOException
{
if (!_streamWriteContext.writeFieldName(name)) {
_reportError("Can not write a field name, expecting a value");
}
_writeFieldName(name);
}
@Override
public final void writeFieldName(SerializableString name)
throws IOException
{
// Object is a value, need to verify it's allowed
if (!_streamWriteContext.writeFieldName(name.getValue())) {
_reportError("Can not write a field name, expecting a value");
}
_writeFieldName(name);
}
/*
/**********************************************************
/* Extended API, configuration
/**********************************************************
*/
public SmileGenerator enable(Feature f) {
_formatFeatures |= f.getMask();
return this;
}
public SmileGenerator disable(Feature f) {
_formatFeatures &= ~f.getMask();
return this;
}
public final boolean isEnabled(Feature f) {
return (_formatFeatures & f.getMask()) != 0;
}
public SmileGenerator configure(Feature f, boolean state) {
if (state) {
enable(f);
} else {
disable(f);
}
return this;
}
@Override
public StreamWriteConstraints streamWriteConstraints() {
return _streamWriteConstraints;
}
/*
/**********************************************************
/* Extended API, other
/**********************************************************
*/
/**
* Method for directly inserting specified byte in output at
* current position.
*<p>
* NOTE: only use this method if you really know what you are doing.
*/
public void writeRaw(byte b) throws IOException
{
/* 08-Jan-2014, tatu: Should we just rather throw an exception? For now,
* allow... maybe have a feature to cause an exception.
*/
_writeByte(b);
}
/**
* Method for directly inserting specified bytes in output at
* current position.
*<p>
* NOTE: only use this method if you really know what you are doing.
*/
public void writeBytes(byte[] data, int offset, int len) throws IOException
{
_writeBytes(data, offset, len);
}
/*
/**********************************************************
/* Output method implementations, structural
/**********************************************************
*/
@Override
public final void writeStartArray() throws IOException
{
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_writeByte(TOKEN_LITERAL_START_ARRAY);
}
@Override // since 2.12
public final void writeStartArray(Object forValue) throws IOException
{
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_writeByte(TOKEN_LITERAL_START_ARRAY);
}
@Override // since 2.12
public final void writeStartArray(Object forValue, int elementsToWrite) throws IOException
{
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_writeByte(TOKEN_LITERAL_START_ARRAY);
}
@Deprecated // since 2.12
@Override
public final void writeStartArray(int size) throws IOException
{
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_writeByte(TOKEN_LITERAL_START_ARRAY);
}
@Override
public final void writeEndArray() throws IOException
{
if (!_streamWriteContext.inArray()) {
_reportError("Current context not Array but "+_streamWriteContext.typeDesc());
}
_writeByte(TOKEN_LITERAL_END_ARRAY);
_streamWriteContext = _streamWriteContext.getParent();
}
@Override
public final void writeStartObject() throws IOException
{
_verifyValueWrite("start an object");
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_writeByte(TOKEN_LITERAL_START_OBJECT);
}
@Override // since 2.8
public final void writeStartObject(Object forValue) throws IOException
{
_verifyValueWrite("start an object");
SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
_streamWriteContext = ctxt;
_writeByte(TOKEN_LITERAL_START_OBJECT);
}
@Override // since 2.14
public void writeStartObject(Object forValue, int elementsToWrite) throws IOException {
_verifyValueWrite("start an object");
SmileWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
_streamWriteContext = ctxt;
_writeByte(TOKEN_LITERAL_START_OBJECT);
}
@Override
public final void writeEndObject() throws IOException
{
if (!_streamWriteContext.inObject()) {
_reportError("Current context not Object but "+_streamWriteContext.typeDesc());
}
_streamWriteContext = _streamWriteContext.getParent();
_writeByte(TOKEN_LITERAL_END_OBJECT);
}
@Override // since 2.8
public void writeArray(int[] array, int offset, int length)
throws IOException
{
_verifyOffsets(array.length, offset, length);
// short-cut, do not create child array context etc
_verifyValueWrite("write int array");
_writeByte(TOKEN_LITERAL_START_ARRAY);
int ptr = _outputTail;
final int outputEnd = _outputEnd;
for (int i = offset, end = offset+length; i < end; ++i) {
// TODO: optimize boundary checks for common case
if ((ptr + 6) >= outputEnd) { // at most 6 bytes per element
_outputTail = ptr;
_flushBuffer();
ptr = _outputTail;
}
ptr = _writeNumberNoChecks(ptr, array[i]);
}
_outputTail = ptr;
_writeByte(TOKEN_LITERAL_END_ARRAY);
}
@Override // since 2.8
public void writeArray(long[] array, int offset, int length)
throws IOException
{
_verifyOffsets(array.length, offset, length);
// short-cut, do not create child array context etc
_verifyValueWrite("write int array");
_writeByte(TOKEN_LITERAL_START_ARRAY);
int ptr = _outputTail;
final int outputEnd = _outputEnd;
for (int i = offset, end = offset+length; i < end; ++i) {
if ((ptr + 11) >= outputEnd) { // at most 11 bytes per element
_outputTail = ptr;
_flushBuffer();
ptr = _outputTail;
}
ptr = _writeNumberNoChecks(ptr, array[i]);
}
_outputTail = ptr;
_writeByte(TOKEN_LITERAL_END_ARRAY);
}
@Override // since 2.8
public void writeArray(double[] array, int offset, int length)
throws IOException
{
_verifyOffsets(array.length, offset, length);
// short-cut, do not create child array context etc
_verifyValueWrite("write int array");
_writeByte(TOKEN_LITERAL_START_ARRAY);
int ptr = _outputTail;
final int outputEnd = _outputEnd;
for (int i = offset, end = offset+length; i < end; ++i) {
if ((ptr + 10) >= outputEnd) { // at most 11 bytes per element
_outputTail = ptr;
_flushBuffer();
ptr = _outputTail;
}
ptr = _writeNumberNoChecks(ptr, array[i]);
}
_outputTail = ptr;
_writeByte(TOKEN_LITERAL_END_ARRAY);
}
private final void _writeFieldName(String name) throws IOException
{
int len = name.length();
if (len == 0) {
_writeByte(TOKEN_KEY_EMPTY_STRING);
return;
}
// First: is it something we can share?
if (_seenNameCount >= 0) {
int ix = _findSeenName(name);
if (ix >= 0) {
_writeSharedNameReference(ix);
return;
}
}
if (len > MAX_SHORT_NAME_ANY_BYTES) { // can not be a 'short' String; off-line (rare case)
_writeNonShortFieldName(name, len);
return;
}
// first: ensure we have enough space
if ((_outputTail + MIN_BUFFER_FOR_POSSIBLE_SHORT_STRING) >= _outputEnd) {
_flushBuffer();
}
// then let's copy String chars to char buffer, faster than using getChar (measured, profiled)
int origOffset = _outputTail;
++_outputTail; // to reserve space for type token
int byteLen = _shortUTF8Encode(name, 0, len);
byte typeToken;
// ASCII?
if (byteLen == len) {
if (byteLen <= MAX_SHORT_NAME_ASCII_BYTES) { // yes, is short indeed
typeToken = (byte) ((TOKEN_PREFIX_KEY_ASCII - 1) + byteLen);
} else { // longer albeit ASCII
typeToken = TOKEN_KEY_LONG_STRING;
// and we will need String end marker byte
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
} else { // not all ASCII
if (byteLen <= MAX_SHORT_NAME_UNICODE_BYTES) { // yes, is short indeed
// note: since 2 is smaller allowed length, offset differs from one used for
typeToken = (byte) ((TOKEN_PREFIX_KEY_UNICODE - 2) + byteLen);
} else { // nope, longer non-ASCII Strings
typeToken = TOKEN_KEY_LONG_STRING;
// and we will need String end marker byte
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
}
// and then sneak in type token now that know the details
_outputBuffer[origOffset] = typeToken;
// Also, keep track if we can use back-references (shared names)
if (_seenNameCount >= 0) {
_addSeenName(name);
}
}
private final void _writeNonShortFieldName(final String name, final int len) throws IOException
{
_writeByte(TOKEN_KEY_LONG_STRING);
// can we still make a temp copy?
// but will encoded version fit in buffer?
int maxLen = len + len + len;
if (maxLen <= _outputBuffer.length) { // yes indeed
if ((_outputTail + maxLen) >= _outputEnd) {
_flushBuffer();
}
_shortUTF8Encode(name, 0, len);
} else { // nope, need bit slower variant
_mediumUTF8Encode(name, 0, len);
}
if (_seenNameCount >= 0) {
_addSeenName(name);
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
protected final void _writeFieldName(SerializableString name) throws IOException
{
final int charLen = name.charLength();
if (charLen == 0) {
_writeByte(TOKEN_KEY_EMPTY_STRING);
return;
}
// Then: is it something we can share?
if (_seenNameCount >= 0) {
int ix = _findSeenName(name.getValue());
if (ix >= 0) {
_writeSharedNameReference(ix);
return;
}
}
final byte[] bytes = name.asUnquotedUTF8();
final int byteLen = bytes.length;
if (byteLen != charLen) {
_writeFieldNameUnicode(name, bytes);
return;
}
// Common case: short ASCII name that fits in buffer as is
if (byteLen <= MAX_SHORT_NAME_ASCII_BYTES) {
// output buffer is bigger than what we need, always, so
if ((_outputTail + byteLen) >= _outputEnd) { // need marker byte and actual bytes
_flushBuffer();
}
_outputBuffer[_outputTail++] = (byte) ((TOKEN_PREFIX_KEY_ASCII - 1) + byteLen);
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
} else {
_writeLongAsciiFieldName(bytes);
}
// Also, keep track if we can use back-references (shared names)
if (_seenNameCount >= 0) {
_addSeenName(name.getValue());
}
}
private final void _writeLongAsciiFieldName(byte[] bytes)
throws IOException
{
final int byteLen = bytes.length;
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = TOKEN_KEY_LONG_STRING;
// Ok. Enough room?
if ((_outputTail + byteLen + 1) < _outputEnd) {
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
} else {
_flushBuffer();
// either way, do intermediate copy if name is relatively short
// Need to copy?
if (byteLen < MIN_BUFFER_LENGTH) {
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
} else {
// otherwise, just write as is
if (_outputTail > 0) {
_flushBuffer();
}
_out.write(bytes, 0, byteLen);
}
}
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
protected final void _writeFieldNameUnicode(SerializableString name, byte[] bytes)
throws IOException
{
final int byteLen = bytes.length;
// Common case: short Unicode name that fits in output buffer
if (byteLen <= MAX_SHORT_NAME_UNICODE_BYTES) {
if ((_outputTail + byteLen) >= _outputEnd) { // need marker byte and actual bytes
_flushBuffer();
}
// note: since 2 is smaller allowed length, offset differs from one used for
_outputBuffer[_outputTail++] = (byte) ((TOKEN_PREFIX_KEY_UNICODE - 2) + byteLen);
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
// Also, keep track if we can use back-references (shared names)
if (_seenNameCount >= 0) {
_addSeenName(name.getValue());
}
return;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = TOKEN_KEY_LONG_STRING;
// Ok. Enough room?
if ((_outputTail + byteLen + 1) < _outputEnd) {
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
} else {
_flushBuffer();
// either way, do intermediate copy if name is relatively short
// Need to copy?
if (byteLen < MIN_BUFFER_LENGTH) {
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
} else {
// otherwise, just write as is
if (_outputTail > 0) {
_flushBuffer();
}
_out.write(bytes, 0, byteLen);
}
}
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
// Also, keep track if we can use back-references (shared names)
if (_seenNameCount >= 0) {
_addSeenName(name.getValue());
}
}
private final void _writeSharedNameReference(int ix)
throws IOException
{
// 03-Mar-2011, tatu: Related to [JACKSON-525], let's add a sanity check here
if (ix >= _seenNameCount) {
throw new IllegalArgumentException("Internal error: trying to write shared name with index "+ix
+"; but have only seen "+_seenNameCount+" so far!");
}
if (ix < 64) {
_writeByte((byte) (TOKEN_PREFIX_KEY_SHARED_SHORT + ix));
} else {
_writeBytes(((byte) (TOKEN_PREFIX_KEY_SHARED_LONG + (ix >> 8))), (byte) ix);
}
}
/*
/**********************************************************
/* Output method implementations, textual
/**********************************************************
*/
@Override
public void writeString(String text) throws IOException
{
if (text == null) {
writeNull();
return;
}
_verifyValueWrite("write String value");
int len = text.length();
if (len == 0) {
_writeByte(TOKEN_LITERAL_EMPTY_STRING);
return;
}
// Longer string handling off-lined
if (len > MAX_SHARED_STRING_LENGTH_BYTES) {
_writeNonSharedString(text, len);
return;
}
// Then: is it something we can share?
if (_seenStringValueCount >= 0) {
int ix = _findSeenStringValue(text);
if (ix >= 0) {
_writeSharedStringValueReference(ix);
return;
}
}
// possibly short string (but not necessarily)
// first: ensure we have enough space
if ((_outputTail + MIN_BUFFER_FOR_POSSIBLE_SHORT_STRING) >= _outputEnd) {
_flushBuffer();
}
// then let's copy String chars to char buffer, faster than using getChar (measured, profiled)
int origOffset = _outputTail;
++_outputTail; // to leave room for type token
int byteLen = _shortUTF8Encode(text, 0, len);
if (byteLen <= MAX_SHORT_VALUE_STRING_BYTES) { // yes, is short indeed
// plus keep reference, if it could be shared:
if (_seenStringValueCount >= 0) {
_addSeenStringValue(text);
}
if (byteLen == len) { // and all ASCII
_outputBuffer[origOffset] = (byte) ((TOKEN_PREFIX_TINY_ASCII - 1) + byteLen);
} else { // not just ASCII
// note: since length 1 can not be used here, value range is offset by 2, not 1
_outputBuffer[origOffset] = (byte) ((TOKEN_PREFIX_TINY_UNICODE - 2) + byteLen);
}
} else { // nope, longer String
_outputBuffer[origOffset] = (byteLen == len) ? TOKEN_BYTE_LONG_STRING_ASCII
: SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE;
// and we will need String end marker byte
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
}
private final void _writeSharedStringValueReference(int ix) throws IOException
{
// 03-Mar-2011, tatu: Related to [JACKSON-525], let's add a sanity check here
if (ix >= _seenStringValueCount) {
throw new IllegalArgumentException("Internal error: trying to write shared String value with index "+ix
+"; but have only seen "+_seenStringValueCount+" so far!");
}
if (ix < 31) { // add 1, as byte 0 is omitted
_writeByte((byte) (TOKEN_PREFIX_SHARED_STRING_SHORT + 1 + ix));
} else {
_writeBytes(((byte) (TOKEN_PREFIX_SHARED_STRING_LONG + (ix >> 8))), (byte) ix);
}
}
/**
* Helper method called to handle cases where String value to write is known
* to be long enough not to be shareable.
*/
private final void _writeNonSharedString(final String text, final int len) throws IOException
{
// Expansion can be 3x for Unicode; and then there's type byte and end marker, so:
int maxLen = len + len + len + 2;
// Next: does it always fit within output buffer?
if (maxLen > _outputBuffer.length) { // nope
// can't rewrite type buffer, so can't speculate it might be all-ASCII
_writeByte(SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE);
_mediumUTF8Encode(text, 0, len);
_writeByte(BYTE_MARKER_END_OF_STRING);
return;
}
if ((_outputTail + maxLen) >= _outputEnd) {
_flushBuffer();
}
int origOffset = _outputTail;
// can't say for sure if it's ASCII or Unicode, so:
_writeByte(TOKEN_BYTE_LONG_STRING_ASCII);
int byteLen = _shortUTF8Encode(text, 0, len);
// If not ASCII, fix type:
if (byteLen > len) {
_outputBuffer[origOffset] = SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE;
}
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
@Override
public void writeString(char[] text, int offset, int len) throws IOException
{
// Shared strings are tricky; easiest to just construct String, call the other method
if (len <= MAX_SHARED_STRING_LENGTH_BYTES && _seenStringValueCount >= 0 && len > 0) {
writeString(new String(text, offset, len));
return;
}
_verifyValueWrite("write String value");
if (len == 0) {
_writeByte(TOKEN_LITERAL_EMPTY_STRING);
return;
}
if (len <= MAX_SHORT_VALUE_STRING_BYTES) { // possibly short strings (not necessarily)
// first: ensure we have enough space
if ((_outputTail + MIN_BUFFER_FOR_POSSIBLE_SHORT_STRING) >= _outputEnd) {
_flushBuffer();
}
int origOffset = _outputTail;
++_outputTail; // to leave room for type token
int byteLen = _shortUTF8Encode(text, offset, offset+len);
byte typeToken;
if (byteLen <= MAX_SHORT_VALUE_STRING_BYTES) { // yes, is short indeed
if (byteLen == len) { // and all ASCII
typeToken = (byte) ((TOKEN_PREFIX_TINY_ASCII - 1) + byteLen);
} else { // not just ASCII
typeToken = (byte) ((TOKEN_PREFIX_TINY_UNICODE - 2) + byteLen);
}
} else { // nope, longer non-ASCII Strings
typeToken = SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE;
// and we will need String end marker byte
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
}
// and then sneak in type token now that know the details
_outputBuffer[origOffset] = typeToken;
} else { // "long" String, never shared
// but might still fit within buffer?
int maxLen = len + len + len + 2;
if (maxLen <= _outputBuffer.length) { // yes indeed
if ((_outputTail + maxLen) >= _outputEnd) {
_flushBuffer();
}
int origOffset = _outputTail;
_writeByte(SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE);
int byteLen = _shortUTF8Encode(text, offset, offset+len);
// if it's ASCII, let's revise our type determination (to help decoder optimize)
if (byteLen == len) {
_outputBuffer[origOffset] = TOKEN_BYTE_LONG_STRING_ASCII;
}
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
} else {
_writeByte(SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE);
_mediumUTF8Encode(text, offset, offset+len);
_writeByte(BYTE_MARKER_END_OF_STRING);
}
}
}
@Override
public final void writeString(SerializableString sstr)
throws IOException
{
_verifyValueWrite("write String value");
// First: is it empty?
String str = sstr.getValue();
int len = str.length();
if (len == 0) {
_writeByte(TOKEN_LITERAL_EMPTY_STRING);
return;
}
// Second: something we can share?
if (len <= MAX_SHARED_STRING_LENGTH_BYTES && _seenStringValueCount >= 0) {
int ix = _findSeenStringValue(str);
if (ix >= 0) {
_writeSharedStringValueReference(ix);
return;
}
}
// If not, use pre-encoded version
byte[] raw = sstr.asUnquotedUTF8();
final int byteLen = raw.length;
if (byteLen <= MAX_SHORT_VALUE_STRING_BYTES) { // short string
// first: ensure we have enough space
if ((_outputTail + byteLen + 1) >= _outputEnd) {
_flushBuffer();
}
// ASCII or Unicode?
int typeToken = (byteLen == len)
? ((TOKEN_PREFIX_TINY_ASCII - 1) + byteLen)
: ((TOKEN_PREFIX_TINY_UNICODE - 2) + byteLen)
;
_outputBuffer[_outputTail++] = (byte) typeToken;
System.arraycopy(raw, 0, _outputBuffer, _outputTail, byteLen);
_outputTail += byteLen;
// plus keep reference, if it could be shared:
if (_seenStringValueCount >= 0) {
_addSeenStringValue(sstr.getValue());
}
} else { // "long" String, never shared
// but might still fit within buffer?
byte typeToken = (byteLen == len) ? TOKEN_BYTE_LONG_STRING_ASCII
: SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE;
_writeByte(typeToken);
_writeBytes(raw, 0, raw.length);
_writeByte(BYTE_MARKER_END_OF_STRING);
}
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int len)
throws IOException
{
_verifyValueWrite("write String value");
// first: is it empty String?
if (len == 0) {
_writeByte(TOKEN_LITERAL_EMPTY_STRING);
return;
}
// Sanity check: shared-strings incompatible with raw String writing
if (_seenStringValueCount >= 0) {
throw new UnsupportedOperationException("Can not use direct UTF-8 write methods when 'Feature.CHECK_SHARED_STRING_VALUES' enabled");
}
/* Other practical limitation is that we do not really know if it might be
* ASCII or not; and figuring it out is rather slow. So, best we can do is
* to declare we do not know it is ASCII (i.e. "is Unicode").
*/
if (len <= MAX_SHARED_STRING_LENGTH_BYTES) { // up to 65 Unicode bytes
// first: ensure we have enough space
if ((_outputTail + len) >= _outputEnd) { // bytes, plus one for type indicator
_flushBuffer();
}
/* 11-Feb-2011, tatu: As per [JACKSON-492], mininum length for "Unicode"
* String is 2; 1 byte length must be ASCII.
*/
if (len == 1) {
_outputBuffer[_outputTail++] = TOKEN_PREFIX_TINY_ASCII; // length of 1 cancels out (len-1)
_outputBuffer[_outputTail++] = text[offset];
} else {
_outputBuffer[_outputTail++] = (byte) ((TOKEN_PREFIX_TINY_UNICODE - 2) + len);
System.arraycopy(text, offset, _outputBuffer, _outputTail, len);
_outputTail += len;
}
} else { // "long" String
// but might still fit within buffer?
int maxLen = len + len + len + 2;
if (maxLen <= _outputBuffer.length) { // yes indeed
if ((_outputTail + maxLen) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE;
System.arraycopy(text, offset, _outputBuffer, _outputTail, len);
_outputTail += len;
_outputBuffer[_outputTail++] = BYTE_MARKER_END_OF_STRING;
} else {
_writeByte(SmileConstants.TOKEN_MISC_LONG_TEXT_UNICODE);
_writeBytes(text, offset, len);
_writeByte(BYTE_MARKER_END_OF_STRING);
}
}
}
@Override
public final void writeUTF8String(byte[] text, int offset, int len)
throws IOException
{
// Since no escaping is needed, same as 'writeRawUTF8String'
writeRawUTF8String(text, offset, len);
}
/*
/**********************************************************
/* Output method implementations, unprocessed ("raw")
/**********************************************************
*/
@Override
public void writeRaw(String text) throws IOException {
throw _notSupported();
}
@Override
public void writeRaw(String text, int offset, int len) throws IOException {
throw _notSupported();
}
@Override
public void writeRaw(char[] text, int offset, int len) throws IOException {
throw _notSupported();
}
@Override
public void writeRaw(char c) throws IOException {
throw _notSupported();
}
@Override
public void writeRawValue(String text) throws IOException {
throw _notSupported();
}
@Override
public void writeRawValue(String text, int offset, int len) throws IOException {
throw _notSupported();
}
@Override
public void writeRawValue(char[] text, int offset, int len) throws IOException {
throw _notSupported();
}
/*
/**********************************************************
/* 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("write Binary value");
if (isEnabled(Feature.ENCODE_BINARY_AS_7BIT)) {
_writeByte(TOKEN_MISC_BINARY_7BIT);
_write7BitBinaryWithLength(data, offset, len);
} else {
_writeByte(TOKEN_MISC_BINARY_RAW);
_writePositiveVInt(len);
// raw is dead simple of course:
_writeBytes(data, offset, len);
}
}
@Override
public int writeBinary(InputStream data, int dataLength)
throws IOException
{
// Smile requires knowledge of length in advance, since binary is length-prefixed
if (dataLength < 0) {
throw new UnsupportedOperationException("Must pass actual length for Smile encoded data");
}
_verifyValueWrite("write Binary value");
int missing;
if (isEnabled(Feature.ENCODE_BINARY_AS_7BIT)) {
_writeByte(TOKEN_MISC_BINARY_7BIT);
byte[] encodingBuffer = _ioContext.allocBase64Buffer();
try {
missing = _write7BitBinaryWithLength(data, dataLength, encodingBuffer);
} finally {
_ioContext.releaseBase64Buffer(encodingBuffer);
}
} else {
_writeByte(TOKEN_MISC_BINARY_RAW );
_writePositiveVInt(dataLength);
// raw is dead simple of course:
missing = _writeBytes(data, dataLength);
}
if (missing > 0) {
_reportError("Too few bytes available: missing "+missing+" bytes (out of "+dataLength+")");
}
return dataLength;
}
@Override
public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength)
throws IOException
{
return writeBinary(data, dataLength);
}
/*
/**********************************************************
/* Output method implementations, primitive
/**********************************************************
*/
@Override
public void writeBoolean(boolean state) throws IOException
{
_verifyValueWrite("write boolean value");
if (state) {
_writeByte(TOKEN_LITERAL_TRUE);
} else {
_writeByte(TOKEN_LITERAL_FALSE);
}
}
@Override
public void writeNull() throws IOException
{
_verifyValueWrite("write null value");
_writeByte(TOKEN_LITERAL_NULL);
}
@Override
public void writeNumber(int i) throws IOException
{
_verifyValueWrite("write number");
// First things first: let's zigzag encode number
i = SmileUtil.zigzagEncode(i);
// tiny (single byte) or small (type + 6-bit value) number?
if (i <= 0x3F && i >= 0) {
if (i <= 0x1F) { // tiny
_writeByte((byte) (TOKEN_PREFIX_SMALL_INT + i));
return;
}
// nope, just small, 2 bytes (type, 1-byte zigzag value) for 6 bit value
_writeBytes(TOKEN_BYTE_INT_32, (byte) (0x80 + i));
return;
}
// Ok: let's find minimal representation then
byte b0 = (byte) (0x80 + (i & 0x3F));
i >>>= 6;
if (i <= 0x7F) { // 13 bits is enough (== 3 byte total encoding)
_writeBytes(TOKEN_BYTE_INT_32, (byte) i, b0);
return;
}
byte b1 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_32, (byte) i, b1, b0);
return;
}
byte b2 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_32, (byte) i, b2, b1, b0);
return;
}
// no, need all 5 bytes
byte b3 = (byte) (i & 0x7F);
_writeBytes(TOKEN_BYTE_INT_32, (byte) (i >> 7), b3, b2, b1, b0);
}
// since 2.8: same as `writeNumber(int)` minus validity checks for
// value write AND boundary checks
private final int _writeNumberNoChecks(int ptr, int i) throws IOException
{
final byte[] output = _outputBuffer;
i = SmileUtil.zigzagEncode(i);
// tiny (single byte) or small (type + 6-bit value) number?
if (i <= 0x3F && i >= 0) {
if (i <= 0x1F) { // tiny
output[ptr++] = (byte) (TOKEN_PREFIX_SMALL_INT + i);
return ptr;
}
// nope, just small, 2 bytes (type, 1-byte zigzag value) for 6 bit value
output[ptr++] = TOKEN_BYTE_INT_32;
output[ptr++] = (byte) (0x80 + i);
return ptr;
}
output[ptr++] = TOKEN_BYTE_INT_32;
// Ok: let's find minimal representation then
byte b0 = (byte) (0x80 + (i & 0x3F));
i >>>= 6;
if (i <= 0x7F) { // 13 bits is enough (== 3 byte total encoding)
output[ptr++] = (byte) i;
output[ptr++] = b0;
return ptr;
}
byte b1 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
byte b2 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
// no, need all 5 bytes
byte b3 = (byte) (i & 0x7F);
output[ptr++] = (byte) (i >> 7);
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
@Override
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);
return;
}
_verifyValueWrite("write number");
// Then let's zigzag encode it
l = SmileUtil.zigzagEncode(l);
// Ok, well, we do know that 5 lowest-significant bytes are needed
int i = (int) l;
// 4 can be extracted from lower int
byte b0 = (byte) (0x80 + (i & 0x3F)); // sign bit set in the last byte
byte b1 = (byte) ((i >> 6) & 0x7F);
byte b2 = (byte) ((i >> 13) & 0x7F);
byte b3 = (byte) ((i >> 20) & 0x7F);
// fifth one is split between ints:
l >>>= 27;
byte b4 = (byte) (((int) l) & 0x7F);
// which may be enough?
i = (int) (l >> 7);
if (i == 0) {
_writeBytes(TOKEN_BYTE_INT_64, b4, b3, b2, b1, b0);
return;
}
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_64, (byte) i);
_writeBytes(b4, b3, b2, b1, b0);
return;
}
byte b5 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_64, (byte) i);
_writeBytes(b5, b4, b3, b2, b1, b0);
return;
}
byte b6 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_64, (byte) i, b6);
_writeBytes(b5, b4, b3, b2, b1, b0);
return;
}
byte b7 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_writeBytes(TOKEN_BYTE_INT_64, (byte) i, b7, b6);
_writeBytes(b5, b4, b3, b2, b1, b0);
return;
}
byte b8 = (byte) (i & 0x7F);
i >>= 7;
// must be done, with 10 bytes! (9 * 7 + 6 == 69 bits; only need 63)
_writeBytes(TOKEN_BYTE_INT_64, (byte) i, b8, b7, b6);
_writeBytes(b5, b4, b3, b2, b1, b0);
}
// since 2.8: same as `writeNumber(int)` minus validity checks for
// value write AND boundary checks
private final int _writeNumberNoChecks(int ptr, long l) throws IOException
{
// First: maybe 32 bits is enough?
if (l <= MAX_INT_AS_LONG && l >= MIN_INT_AS_LONG) {
return _writeNumberNoChecks(ptr, (int) l);
}
l = SmileUtil.zigzagEncode(l);
// Ok, well, we do know that 5 lowest-significant bytes are needed
int i = (int) l;
// 4 can be extracted from lower int
byte b0 = (byte) (0x80 + (i & 0x3F)); // sign bit set in the last byte
byte b1 = (byte) ((i >> 6) & 0x7F);
byte b2 = (byte) ((i >> 13) & 0x7F);
byte b3 = (byte) ((i >> 20) & 0x7F);
// fifth one is split between ints:
l >>>= 27;
byte b4 = (byte) (((int) l) & 0x7F);
final byte[] output = _outputBuffer;
output[ptr++] = TOKEN_BYTE_INT_64;
// which may be enough?
i = (int) (l >> 7);
if (i == 0) {
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
byte b5 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b5;
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
byte b6 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b6;
output[ptr++] = b5;
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
byte b7 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
output[ptr++] = (byte) i;
output[ptr++] = b7;
output[ptr++] = b6;
output[ptr++] = b5;
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
byte b8 = (byte) (i & 0x7F);
i >>= 7;
// must be done, with 10 bytes! (9 * 7 + 6 == 69 bits; only need 63)
output[ptr++] = (byte) i;
output[ptr++] = b8;
output[ptr++] = b7;
output[ptr++] = b6;
output[ptr++] = b5;
output[ptr++] = b4;
output[ptr++] = b3;
output[ptr++] = b2;
output[ptr++] = b1;
output[ptr++] = b0;
return ptr;
}
@Override
public void writeNumber(BigInteger v) throws IOException
{
if (v == null) {
writeNull();
return;
}
_verifyValueWrite("write number");
// quite simple: type, and then VInt-len prefixed 7-bit encoded binary data:
_writeByte(TOKEN_BYTE_BIG_INTEGER);
byte[] data = v.toByteArray();
_write7BitBinaryWithLength(data, 0, data.length);
}
@Override
public void writeNumber(double d) throws IOException
{
// Ok, now, we needed token type byte plus 10 data bytes (7 bits each)
_ensureRoomForOutput(11);
_verifyValueWrite("write number");
/* 17-Apr-2010, tatu: could also use 'doubleToIntBits', but it seems more accurate to use
* exact representation; and possibly faster. However, if there are cases
* where collapsing of NaN was needed (for non-Java clients), this can
* be changed
*/
long l = Double.doubleToRawLongBits(d);
_outputBuffer[_outputTail++] = TOKEN_BYTE_FLOAT_64;
// Handle first 29 bits (single bit first, then 4 x 7 bits)
int hi5 = (int) (l >>> 35);
_outputBuffer[_outputTail+4] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
_outputBuffer[_outputTail+3] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
_outputBuffer[_outputTail+2] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
_outputBuffer[_outputTail+1] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
_outputBuffer[_outputTail] = (byte) hi5;
_outputTail += 5;
// Then split byte (one that crosses lo/hi int boundary), 7 bits
{
int mid = (int) (l >> 28);
_outputBuffer[_outputTail++] = (byte) (mid & 0x7F);
}
// and then last 4 bytes (28 bits)
int lo4 = (int) l;
_outputBuffer[_outputTail+3] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
_outputBuffer[_outputTail+2] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
_outputBuffer[_outputTail+1] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
_outputBuffer[_outputTail] = (byte) (lo4 & 0x7F);
_outputTail += 4;
}
private final int _writeNumberNoChecks(int ptr, double d) throws IOException
{
long l = Double.doubleToRawLongBits(d);
final byte[] output = _outputBuffer;
output[ptr++] = TOKEN_BYTE_FLOAT_64;
// Handle first 29 bits (single bit first, then 4 x 7 bits)
int hi5 = (int) (l >>> 35);
output[ptr+4] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
output[ptr+3] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
output[ptr+2] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
output[ptr+1] = (byte) (hi5 & 0x7F);
hi5 >>= 7;
output[ptr] = (byte) hi5;
ptr += 5;
// Then split byte (one that crosses lo/hi int boundary), 7 bits
{
int mid = (int) (l >> 28);
output[ptr++] = (byte) (mid & 0x7F);
}
// and then last 4 bytes (28 bits)
int lo4 = (int) l;
output[ptr+3] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
output[ptr+2] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
output[ptr+1] = (byte) (lo4 & 0x7F);
lo4 >>= 7;
output[ptr] = (byte) (lo4 & 0x7F);
return ptr + 4;
}
@Override
public void writeNumber(float f) throws IOException
{
// Ok, now, we needed token type byte plus 5 data bytes (7 bits each)
_ensureRoomForOutput(6);
_verifyValueWrite("write number");
/* 17-Apr-2010, tatu: could also use 'floatToIntBits', but it seems more accurate to use
* exact representation; and possibly faster. However, if there are cases
* where collapsing of NaN was needed (for non-Java clients), this can
* be changed
*/
int i = Float.floatToRawIntBits(f);
_outputBuffer[_outputTail++] = TOKEN_BYTE_FLOAT_32;
_outputBuffer[_outputTail+4] = (byte) (i & 0x7F);
i >>= 7;
_outputBuffer[_outputTail+3] = (byte) (i & 0x7F);
i >>= 7;
_outputBuffer[_outputTail+2] = (byte) (i & 0x7F);
i >>= 7;
_outputBuffer[_outputTail+1] = (byte) (i & 0x7F);
i >>= 7;
_outputBuffer[_outputTail] = (byte) (i & 0x7F);
_outputTail += 5;
}
@Override
public void writeNumber(BigDecimal dec) throws IOException
{
if (dec == null) {
writeNull();
return;
}
_verifyValueWrite("write number");
_writeByte(TOKEN_BYTE_BIG_DECIMAL);
int scale = dec.scale();
// Ok, first output scale as VInt
_writeSignedVInt(scale);
BigInteger unscaled = dec.unscaledValue();
byte[] data = unscaled.toByteArray();
// And then binary data in "safe" mode (7-bit values)
_write7BitBinaryWithLength(data, 0, data.length);
}
@Override
public void writeNumber(String encodedValue) throws IOException
{
if (encodedValue == null) {
writeNull();
return;
}
// 28-May-2014, tatu: Let's actually try to support this method; should be doable
final int len = encodedValue.length();
boolean neg = encodedValue.startsWith("-");
// Let's see if it's integral or not
int i = neg ? 1 : 0;
if (i >= len) {
_writeIntegralNumber(encodedValue, neg);
return;
}
while (true) {
char c = encodedValue.charAt(i);
if (c > '9' || c < '0') {
break;
}
if (++i == len) {
_writeIntegralNumber(encodedValue, neg);
return;
}
}
_writeDecimalNumber(encodedValue);
}
protected void _writeIntegralNumber(String enc, boolean neg) throws IOException
{
int len = enc.length();
// 16-Dec-2023, tatu: Guard against too-big numbers
_streamReadConstraints().validateIntegerLength(len);
if (neg) {
--len;
}
// let's do approximate optimization
try {
if (len <= 9) {
// Avoid exception from empty String
if (len > 0) {
writeNumber(Integer.parseInt(enc));
}
} else if (len <= 18) {
writeNumber(Long.parseLong(enc));
} else {
writeNumber(NumberInput.parseBigInteger(enc, false));
}
return;
} catch (NumberFormatException e) { }
_reportError("Invalid String representation for Number ('"+enc
+"'); can not write using Smile format");
}
protected void _writeDecimalNumber(String enc) throws IOException
{
// 16-Dec-2023, tatu: Guard against too-big numbers
_streamReadConstraints().validateFPLength(enc.length());
// ... and check basic validity too
if (NumberInput.looksLikeValidNumber(enc)) {
try {
writeNumber(NumberInput.parseBigDecimal(enc, false));
return;
} catch (NumberFormatException e) { }
}
_reportError("Invalid String representation for Number ('"+enc
+"'); can not write using Smile format");
}
/*
/**********************************************************
/* Implementations for other methods
/**********************************************************
*/
@Override
protected final void _verifyValueWrite(String typeMsg)
throws IOException
{
if (!_streamWriteContext.writeValue()) {
_reportError("Can not "+typeMsg+", expecting field name");
}
}
/*
/**********************************************************
/* Low-level output handling
/**********************************************************
*/
@Override
public final void flush() throws IOException
{
_flushBuffer();
if (isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)) {
_out.flush();
}
}
@Override
public void close() throws IOException
{
if (!isClosed()) {
// First: let's see that we still have buffers...
if (_outputBuffer != null
&& isEnabled(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT)) {
while (true) {
JsonStreamContext ctxt = getOutputContext();
if (ctxt.inArray()) {
writeEndArray();
} else if (ctxt.inObject()) {
writeEndObject();
} else {
break;
}
}
}
if (isEnabled(Feature.WRITE_END_MARKER)) {
_writeByte(BYTE_MARKER_END_OF_CONTENT);
}
_flushBuffer();
if (_ioContext.isResourceManaged() || isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)) {
_out.close();
} else if (isEnabled(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)) {
// If we can't close it, we should at least flush
// 14-Jan-2019, tatu: [dataformats-binary#155]: unless prevented via feature
_out.flush();
}
// Internal buffer(s) generator has can now be released as well
_releaseBuffers();
super.close();
}
}
/*
/**********************************************************
/* Internal methods, UTF-8 encoding
/**********************************************************
*/
/**
* Helper method called when the whole character sequence is known to
* fit in the output buffer regardless of UTF-8 expansion.
*/
private final int _shortUTF8Encode(char[] str, int i, int end)
throws IOException
{
// First: let's see if it's all ASCII: that's rather fast
int ptr = _outputTail;
final byte[] outBuf = _outputBuffer;
do {
int c = str[i];
if (c > 0x7F) {
return _shortUTF8Encode2(str, i, end, ptr);
}
outBuf[ptr++] = (byte) c;
} while (++i < end);
int codedLen = ptr - _outputTail;
_outputTail = ptr;
return codedLen;
}
/**
* Helper method called when the whole character sequence is known to
* fit in the output buffer, but not all characters are single-byte (ASCII)
* characters.
*/
private final int _shortUTF8Encode2(char[] str, int i, int end, int outputPtr)
throws IOException
{
final byte[] outBuf = _outputBuffer;
while (i < end) {
int c = str[i++];
if (c <= 0x7F) {
outBuf[outputPtr++] = (byte) c;
continue;
}
// Nope, multi-byte:
if (c < 0x800) { // 2-byte
outBuf[outputPtr++] = (byte) (0xc0 | (c >> 6));
outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// 3 or 4 bytes (surrogate)
// Surrogates?
if (c < SURR1_FIRST || c > SURR2_LAST) { // nope, regular 3-byte character
outBuf[outputPtr++] = (byte) (0xe0 | (c >> 12));
outBuf[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// Yup, looks like a surrogate pair... but is it?
if ((c <= SURR1_LAST) && (i < end)) { // must be from first range and have another char
final int d = str[i];
if ((d <= SURR2_LAST) && (d >= SURR2_FIRST)) {
++i;
outputPtr = _decodeAndWriteSurrogate(c, d, outBuf, outputPtr);
continue;
}
outputPtr = _invalidSurrogateEnd(c, d, outBuf, outputPtr);
continue;
}
// Nah, something wrong
outputPtr = _invalidSurrogateStart(c, outBuf, outputPtr);
}
int codedLen = outputPtr - _outputTail;
_outputTail = outputPtr;
return codedLen;
}
private final int _shortUTF8Encode(String str, int i, int end)
throws IOException
{
// First: let's see if it's all ASCII: that's rather fast
int ptr = _outputTail;
final byte[] outBuf = _outputBuffer;
do {
int c = str.charAt(i);
if (c > 0x7F) {
return _shortUTF8Encode2(str, i, end, ptr);
}
outBuf[ptr++] = (byte) c;
} while (++i < end);
int codedLen = ptr - _outputTail;
_outputTail = ptr;
return codedLen;
}
private final int _shortUTF8Encode2(String str, int i, int end, int outputPtr)
throws IOException
{
final byte[] outBuf = _outputBuffer;
while (i < end) {
int c = str.charAt(i++);
if (c <= 0x7F) {
outBuf[outputPtr++] = (byte) c;
continue;
}
// Nope, multi-byte:
if (c < 0x800) { // 2-byte
outBuf[outputPtr++] = (byte) (0xc0 | (c >> 6));
outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// 3 or 4 bytes (surrogate)
// Surrogates?
if (c < SURR1_FIRST || c > SURR2_LAST) { // nope, regular 3-byte character
outBuf[outputPtr++] = (byte) (0xe0 | (c >> 12));
outBuf[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// Yup, looks like a surrogate pair... but is it?
if ((c <= SURR1_LAST) && (i < end)) { // must be from first range and have another char
final int d = str.charAt(i);
if ((d <= SURR2_LAST) && (d >= SURR2_FIRST)) {
++i;
outputPtr = _decodeAndWriteSurrogate(c, d, outBuf, outputPtr);
continue;
}
outputPtr = _invalidSurrogateEnd(c, d, outBuf, outputPtr);
continue;
}
// Nah, something wrong
outputPtr = _invalidSurrogateStart(c, outBuf, outputPtr);
}
int codedLen = outputPtr - _outputTail;
_outputTail = outputPtr;
return codedLen;
}
private void _mediumUTF8Encode(char[] str, int inputPtr, int inputEnd) throws IOException
{
final int bufferEnd = _outputEnd - 4;
output_loop:
while (inputPtr < inputEnd) {
// First, let's ensure we can output at least 4 bytes
// (longest UTF-8 encoded codepoint):
if (_outputTail >= bufferEnd) {
_flushBuffer();
}
int c = str[inputPtr++];
// And then see if we have an ASCII char:
if (c <= 0x7F) { // If so, can do a tight inner loop:
_outputBuffer[_outputTail++] = (byte)c;
// Let's calc how many ASCII chars we can copy at most:
int maxInCount = (inputEnd - inputPtr);
int maxOutCount = (bufferEnd - _outputTail);
if (maxInCount > maxOutCount) {
maxInCount = maxOutCount;
}
maxInCount += inputPtr;
ascii_loop:
while (true) {
if (inputPtr >= maxInCount) { // done with max. ascii seq
continue output_loop;
}
c = str[inputPtr++];
if (c > 0x7F) {
break ascii_loop;
}
_outputBuffer[_outputTail++] = (byte) c;
}
}
// Nope, multi-byte:
if (c < 0x800) { // 2-byte
_outputBuffer[_outputTail++] = (byte) (0xc0 | (c >> 6));
_outputBuffer[_outputTail++] = (byte) (0x80 | (c & 0x3f));
} else { // 3 or 4 bytes
// Surrogates?
if (c < SURR1_FIRST || c > SURR2_LAST) {
_outputBuffer[_outputTail++] = (byte) (0xe0 | (c >> 12));
_outputBuffer[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
_outputBuffer[_outputTail++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// Yup, looks like a surrogate pair... but is it?
if ((c <= SURR1_LAST) && (inputPtr < inputEnd)) { // must be from first range and have another char
final int d = str[inputPtr];
if ((d <= SURR2_LAST) && (d >= SURR2_FIRST)) {
++inputPtr;
_outputTail = _decodeAndWriteSurrogate(c, d, _outputBuffer, _outputTail);
continue;
}
_outputTail = _invalidSurrogateEnd(c, d, _outputBuffer, _outputTail);
continue;
}
// Nah, something wrong
_outputTail = _invalidSurrogateStart(c, _outputBuffer, _outputTail);
}
}
}
private void _mediumUTF8Encode(String str, int inputPtr, int inputEnd) throws IOException
{
final int bufferEnd = _outputEnd - 4;
output_loop:
while (inputPtr < inputEnd) {
// First, let's ensure we can output at least 4 bytes
// (longest UTF-8 encoded codepoint):
if (_outputTail >= bufferEnd) {
_flushBuffer();
}
int c = str.charAt(inputPtr++);
// And then see if we have an ASCII char:
if (c <= 0x7F) { // If so, can do a tight inner loop:
_outputBuffer[_outputTail++] = (byte)c;
// Let's calc how many ASCII chars we can copy at most:
int maxInCount = (inputEnd - inputPtr);
int maxOutCount = (bufferEnd - _outputTail);
if (maxInCount > maxOutCount) {
maxInCount = maxOutCount;
}
maxInCount += inputPtr;
ascii_loop:
while (true) {
if (inputPtr >= maxInCount) { // done with max. ascii seq
continue output_loop;
}
c = str.charAt(inputPtr++);
if (c > 0x7F) {
break ascii_loop;
}
_outputBuffer[_outputTail++] = (byte) c;
}
}
// Nope, multi-byte:
if (c < 0x800) { // 2-byte
_outputBuffer[_outputTail++] = (byte) (0xc0 | (c >> 6));
_outputBuffer[_outputTail++] = (byte) (0x80 | (c & 0x3f));
} else { // 3 or 4 bytes
// Surrogates?
if (c < SURR1_FIRST || c > SURR2_LAST) {
_outputBuffer[_outputTail++] = (byte) (0xe0 | (c >> 12));
_outputBuffer[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
_outputBuffer[_outputTail++] = (byte) (0x80 | (c & 0x3f));
continue;
}
// Yup, looks like a surrogate pair... but is it?
if ((c <= SURR1_LAST) && (inputPtr < inputEnd)) { // must be from first range and have another char
final int d = str.charAt(inputPtr);
if ((d <= SURR2_LAST) && (d >= SURR2_FIRST)) {
++inputPtr;
_outputTail = _decodeAndWriteSurrogate(c, d, _outputBuffer, _outputTail);
continue;
}
_outputTail = _invalidSurrogateEnd(c, d, _outputBuffer, _outputTail);
continue;
}
// Nah, something wrong
_outputTail = _invalidSurrogateStart(c, _outputBuffer, _outputTail);
}
}
}
/*
/**********************************************************************
/* Internal methods, surrogate pair handling
/**********************************************************************
*/
private int _invalidSurrogateStart(int code, byte[] outBuf, int outputPtr)
throws IOException
{
if (isEnabled(Feature.LENIENT_UTF_ENCODING)) {
return _appendReplacementChar(outBuf, outputPtr);
}
// Will be called in two distinct cases: either first character is
// invalid (code range of second part), or first character is valid
// but there is no second part to encode
if (code <= SURR1_LAST) {
// Unmatched first part (closing without second part?)
_reportError(String.format(
"Unmatched surrogate pair, starts with valid high surrogate (0x%04X) but ends without low surrogate",
code));
}
_reportError(String.format(
"Invalid surrogate pair, starts with invalid high surrogate (0x%04X), not in valid range [0xD800, 0xDBFF]",
code));
return 0; // never gets here
}
private int _invalidSurrogateEnd(int surr1, int surr2,
byte[] outBuf, int outputPtr)
throws IOException
{
if (isEnabled(Feature.LENIENT_UTF_ENCODING)) {
return _appendReplacementChar(outBuf, outputPtr);
}
_reportError(String.format(
"Invalid surrogate pair, starts with valid high surrogate (0x%04X)"
+" but ends with invalid low surrogate (0x%04X), not in valid range [0xDC00, 0xDFFF]",
surr1, surr2));
return 0; // never gets here
}
private int _appendReplacementChar(byte[] outBuf, int outputPtr) {
outBuf[outputPtr++] = (byte) (0xe0 | (REPLACEMENT_CHAR >> 12));
outBuf[outputPtr++] = (byte) (0x80 | ((REPLACEMENT_CHAR >> 6) & 0x3f));
outBuf[outputPtr++] = (byte) (0x80 | (REPLACEMENT_CHAR & 0x3f));
return outputPtr;
}
private int _decodeAndWriteSurrogate(int surr1, int surr2,
byte[] outBuf, int outputPtr)
{
final int c = 0x10000 + ((surr1 - SURR1_FIRST) << 10)
+ (surr2 - SURR2_FIRST);
outBuf[outputPtr++] = (byte) (0xf0 | (c >> 18));
outBuf[outputPtr++] = (byte) (0x80 | ((c >> 12) & 0x3f));
outBuf[outputPtr++] = (byte) (0x80 | ((c >> 6) & 0x3f));
outBuf[outputPtr++] = (byte) (0x80 | (c & 0x3f));
return outputPtr;
}
/*
/**********************************************************
/* Internal methods, writing bytes
/**********************************************************
*/
private final void _ensureRoomForOutput(int needed) throws IOException
{
if ((_outputTail + needed) >= _outputEnd) {
_flushBuffer();
}
}
private final void _writeByte(byte b) throws IOException
{
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b;
}
private final void _writeBytes(byte b1, byte b2) throws IOException
{
if ((_outputTail + 1) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b2;
}
private final void _writeBytes(byte b1, byte b2, byte b3) throws IOException
{
if ((_outputTail + 2) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b3;
}
private final void _writeBytes(byte b1, byte b2, byte b3, byte b4) throws IOException
{
if ((_outputTail + 3) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b3;
_outputBuffer[_outputTail++] = b4;
}
private final void _writeBytes(byte b1, byte b2, byte b3, byte b4, byte b5) throws IOException
{
if ((_outputTail + 4) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b3;
_outputBuffer[_outputTail++] = b4;
_outputBuffer[_outputTail++] = b5;
}
private final void _writeBytes(byte b1, byte b2, byte b3, byte b4, byte b5, byte b6) throws IOException
{
if ((_outputTail + 5) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b3;
_outputBuffer[_outputTail++] = b4;
_outputBuffer[_outputTail++] = b5;
_outputBuffer[_outputTail++] = b6;
}
private final void _writeBytes(byte[] data, int offset, int len) throws IOException
{
if (len == 0) {
return;
}
if ((_outputTail + len) >= _outputEnd) {
_writeBytesLong(data, offset, len);
return;
}
// common case, non-empty, fits in just fine:
System.arraycopy(data, offset, _outputBuffer, _outputTail, len);
_outputTail += len;
}
private final int _writeBytes(InputStream in, int bytesLeft) throws IOException
{
while (bytesLeft > 0) {
int room = _outputEnd - _outputTail;
if (room <= 0) {
_flushBuffer();
room = _outputEnd - _outputTail;
}
if (room > bytesLeft) {
room = bytesLeft;
}
int count = in.read(_outputBuffer, _outputTail, room);
if (count < 0) {
break;
}
_outputTail += count;
bytesLeft -= count;
}
return bytesLeft;
}
private final void _writeBytesLong(byte[] data, int offset, int len) throws IOException
{
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
while (true) {
int currLen = Math.min(len, (_outputEnd - _outputTail));
System.arraycopy(data, offset, _outputBuffer, _outputTail, currLen);
_outputTail += currLen;
if ((len -= currLen) == 0) {
break;
}
offset += currLen;
_flushBuffer();
}
}
/**
* Helper method for writing a 32-bit positive (really 31-bit then) value.
* Value is NOT zigzag encoded (since there is no sign bit to worry about)
*/
private void _writePositiveVInt(int i) throws IOException
{
// At most 5 bytes (4 * 7 + 6 bits == 34 bits)
_ensureRoomForOutput(5);
byte b0 = (byte) (0x80 + (i & 0x3F));
i >>= 6;
if (i <= 0x7F) { // 6 or 13 bits is enough (== 2 or 3 byte total encoding)
if (i > 0) {
_outputBuffer[_outputTail++] = (byte) i;
}
_outputBuffer[_outputTail++] = b0;
return;
}
byte b1 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b0;
} else {
byte b2 = (byte) (i & 0x7F);
i >>= 7;
if (i <= 0x7F) {
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b0;
} else {
byte b3 = (byte) (i & 0x7F);
_outputBuffer[_outputTail++] = (byte) (i >> 7);
_outputBuffer[_outputTail++] = b3;
_outputBuffer[_outputTail++] = b2;
_outputBuffer[_outputTail++] = b1;
_outputBuffer[_outputTail++] = b0;
}
}
}
/**
* Helper method for writing 32-bit signed value, using
* "zig zag encoding" (see protocol buffers for explanation -- basically,
* sign bit is moved as LSB, rest of value shifted left by one)
* coupled with basic variable length encoding
*/
private void _writeSignedVInt(int input) throws IOException
{
_writePositiveVInt(SmileUtil.zigzagEncode(input));
}
protected void _write7BitBinaryWithLength(byte[] data, int offset, int len) throws IOException
{
_writePositiveVInt(len);
// first, let's handle full 7-byte chunks
while (len >= 7) {
if ((_outputTail + 8) >= _outputEnd) {
_flushBuffer();
}
int i = data[offset++]; // 1st byte
_outputBuffer[_outputTail++] = (byte) ((i >> 1) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 2nd
_outputBuffer[_outputTail++] = (byte) ((i >> 2) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 3rd
_outputBuffer[_outputTail++] = (byte) ((i >> 3) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 4th
_outputBuffer[_outputTail++] = (byte) ((i >> 4) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 5th
_outputBuffer[_outputTail++] = (byte) ((i >> 5) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 6th
_outputBuffer[_outputTail++] = (byte) ((i >> 6) & 0x7F);
i = (i << 8) | (data[offset++] & 0xFF); // 7th
_outputBuffer[_outputTail++] = (byte) ((i >> 7) & 0x7F);
_outputBuffer[_outputTail++] = (byte) (i & 0x7F);
len -= 7;
}
// and then partial piece, if any
if (len > 0) {
// up to 6 bytes to output, resulting in at most 7 bytes (which can encode 49 bits)
if ((_outputTail + 7) >= _outputEnd) {
_flushBuffer();
}
int i = data[offset++];
_outputBuffer[_outputTail++] = (byte) ((i >> 1) & 0x7F);
if (len > 1) {
i = ((i & 0x01) << 8) | (data[offset++] & 0xFF); // 2nd
_outputBuffer[_outputTail++] = (byte) ((i >> 2) & 0x7F);
if (len > 2) {
i = ((i & 0x03) << 8) | (data[offset++] & 0xFF); // 3rd
_outputBuffer[_outputTail++] = (byte) ((i >> 3) & 0x7F);
if (len > 3) {
i = ((i & 0x07) << 8) | (data[offset++] & 0xFF); // 4th
_outputBuffer[_outputTail++] = (byte) ((i >> 4) & 0x7F);
if (len > 4) {
i = ((i & 0x0F) << 8) | (data[offset++] & 0xFF); // 5th
_outputBuffer[_outputTail++] = (byte) ((i >> 5) & 0x7F);
if (len > 5) {
i = ((i & 0x1F) << 8) | (data[offset++] & 0xFF); // 6th
_outputBuffer[_outputTail++] = (byte) ((i >> 6) & 0x7F);
_outputBuffer[_outputTail++] = (byte) (i & 0x3F); // last 6 bits
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x1F); // last 5 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x0F); // last 4 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x07); // last 3 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x03); // last 2 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x01); // last bit
}
}
}
protected int _write7BitBinaryWithLength(InputStream in, int bytesLeft, byte[] buffer)
throws IOException
{
_writePositiveVInt(bytesLeft);
int inputPtr = 0;
int inputEnd = 0;
int lastFullOffset = -7;
// first, let's handle full 7-byte chunks
while (bytesLeft >= 7) {
if (inputPtr > lastFullOffset) {
inputEnd = _readMore(in, buffer, inputPtr, inputEnd, bytesLeft);
inputPtr = 0;
if (inputEnd < 7) { // required to try to read to have at least 7 bytes
bytesLeft -= inputEnd; // just to give accurate error messages wrt how much was gotten
break;
}
lastFullOffset = inputEnd-7;
}
if ((_outputTail + 8) >= _outputEnd) {
_flushBuffer();
}
int i = buffer[inputPtr++]; // 1st byte
_outputBuffer[_outputTail++] = (byte) ((i >> 1) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 2nd
_outputBuffer[_outputTail++] = (byte) ((i >> 2) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 3rd
_outputBuffer[_outputTail++] = (byte) ((i >> 3) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 4th
_outputBuffer[_outputTail++] = (byte) ((i >> 4) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 5th
_outputBuffer[_outputTail++] = (byte) ((i >> 5) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 6th
_outputBuffer[_outputTail++] = (byte) ((i >> 6) & 0x7F);
i = (i << 8) | (buffer[inputPtr++] & 0xFF); // 7th
_outputBuffer[_outputTail++] = (byte) ((i >> 7) & 0x7F);
_outputBuffer[_outputTail++] = (byte) (i & 0x7F);
bytesLeft -= 7;
}
// and then partial piece, if any
if (bytesLeft > 0) {
// up to 6 bytes to output, resulting in at most 7 bytes (which can encode 49 bits)
if ((_outputTail + 7) >= _outputEnd) {
_flushBuffer();
}
inputEnd = _readMore(in, buffer, inputPtr, inputEnd, bytesLeft);
inputPtr = 0;
if (inputEnd > 0) { // yes, but do we have room for output?
bytesLeft -= inputEnd;
int i = buffer[inputPtr++];
_outputBuffer[_outputTail++] = (byte) ((i >> 1) & 0x7F);
if (inputEnd > 1) {
i = ((i & 0x01) << 8) | (buffer[inputPtr++] & 0xFF); // 2nd
_outputBuffer[_outputTail++] = (byte) ((i >> 2) & 0x7F);
if (inputEnd > 2) {
i = ((i & 0x03) << 8) | (buffer[inputPtr++] & 0xFF); // 3rd
_outputBuffer[_outputTail++] = (byte) ((i >> 3) & 0x7F);
if (inputEnd > 3) {
i = ((i & 0x07) << 8) | (buffer[inputPtr++] & 0xFF); // 4th
_outputBuffer[_outputTail++] = (byte) ((i >> 4) & 0x7F);
if (inputEnd > 4) {
i = ((i & 0x0F) << 8) | (buffer[inputPtr++] & 0xFF); // 5th
_outputBuffer[_outputTail++] = (byte) ((i >> 5) & 0x7F);
if (inputEnd > 5) {
i = ((i & 0x1F) << 8) | (buffer[inputPtr++] & 0xFF); // 6th
_outputBuffer[_outputTail++] = (byte) ((i >> 6) & 0x7F);
_outputBuffer[_outputTail++] = (byte) (i & 0x3F); // last 6 bits
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x1F); // last 5 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x0F); // last 4 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x07); // last 3 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x03); // last 2 bits
}
} else {
_outputBuffer[_outputTail++] = (byte) (i & 0x01); // last bit
}
}
}
return bytesLeft;
}
private int _readMore(InputStream in,
byte[] readBuffer, int inputPtr, int inputEnd,
int maxRead) throws IOException
{
// anything to shift to front?
int i = 0;
while (inputPtr < inputEnd) {
readBuffer[i++] = readBuffer[inputPtr++];
}
inputPtr = 0;
inputEnd = i;
maxRead = Math.min(maxRead, readBuffer.length);
do {
/* 26-Feb-2013, tatu: Similar to jackson-core issue #55, need to ensure
* we have something to read.
*/
int length = maxRead - inputEnd;
if (length == 0) {
break;
}
int count = in.read(readBuffer, inputEnd, length);
if (count < 0) {
return inputEnd;
}
inputEnd += count;
} while (inputEnd < 7);
return inputEnd;
}
/*
/**********************************************************
/* Internal methods, buffer handling
/**********************************************************
*/
@Override
protected void _releaseBuffers()
{
byte[] buf = _outputBuffer;
if (buf != null && _bufferRecyclable) {
_outputBuffer = null;
_ioContext.releaseWriteEncodingBuffer(buf);
}
}
protected final void _flushBuffer() throws IOException
{
if (_outputTail > 0) {
_bytesWritten += _outputTail;
_out.write(_outputBuffer, 0, _outputTail);
_outputTail = 0;
}
}
/*
/**********************************************************
/* Internal methods, handling shared string "maps"
/**********************************************************
*/
private final int _findSeenName(String name)
{
int hash = name.hashCode();
SharedStringNode head = _seenNames[hash & (_seenNames.length-1)];
if (head == null) {
return -1;
}
SharedStringNode node = head;
// first, identity match; assuming most of the time we get intern()ed String
// And do unrolled initial check; 90+% likelihood head node has all info we need:
if (node.value == name) {
return node.index;
}
while ((node = node.next) != null) {
if (node.value == name) {
return node.index;
}
}
// If not, equality check; we already know head is not null
node = head;
do {
String value = node.value;
if (value.hashCode() == hash && value.equals(name)) {
return node.index;
}
node = node.next;
} while (node != null);
return -1;
}
private final void _addSeenName(String name)
{
// first: do we need to expand?
if (_seenNameCount == _seenNames.length) {
if (_seenNameCount == MAX_SHARED_NAMES) { // we are too full, restart from empty
Arrays.fill(_seenNames, null);
_seenNameCount = 0;
} else { // we always start with modest default size (like 64), so expand to full
SharedStringNode[] old = _seenNames;
_seenNames = new SharedStringNode[MAX_SHARED_NAMES];
final int mask = MAX_SHARED_NAMES-1;
for (SharedStringNode node : old) {
while (node != null) {
int ix = node.value.hashCode() & mask;
SharedStringNode next = node.next;
node.next = _seenNames[ix];
_seenNames[ix] = node;
node = next;
}
}
}
}
// other than that, just slap it there
int ref = _seenNameCount;
if (_validBackRef(ref)) {
int ix = name.hashCode() & (_seenNames.length-1);
_seenNames[ix] = new SharedStringNode(name, ref, _seenNames[ix]);
}
_seenNameCount = ref+1;
}
private final int _findSeenStringValue(String text)
{
int hash = text.hashCode();
SharedStringNode head = _seenStringValues[hash & (_seenStringValues.length-1)];
if (head != null) {
SharedStringNode node = head;
// first, identity match; assuming most of the time we get intern()ed String
do {
if (node.value == text) {
return node.index;
}
node = node.next;
} while (node != null);
// and then comparison, if no match yet
node = head;
do {
String value = node.value;
if (value.hashCode() == hash && value.equals(text)) {
return node.index;
}
node = node.next;
} while (node != null);
}
return -1;
}
private final void _addSeenStringValue(String text)
{
// first: do we need to expand?
if (_seenStringValueCount == _seenStringValues.length) {
if (_seenStringValueCount == MAX_SHARED_STRING_VALUES) { // we are too full, restart from empty
Arrays.fill(_seenStringValues, null);
_seenStringValueCount = 0;
} else { // we always start with modest default size (like 64), so expand to full
SharedStringNode[] old = _seenStringValues;
_seenStringValues = new SharedStringNode[MAX_SHARED_STRING_VALUES];
final int mask = MAX_SHARED_STRING_VALUES-1;
for (SharedStringNode node : old) {
while (node != null) {
int ix = node.value.hashCode() & mask;
SharedStringNode next = node.next;
node.next = _seenStringValues[ix];
_seenStringValues[ix] = node;
node = next;
}
}
}
}
// other than that, just slap it there
/* [Issue#18]: Except need to avoid producing bytes 0xFE and 0xFF in content;
* so skip additions of those; this may produce duplicate values (and lower
* efficiency), but it must be done to since these bytes must be avoided by
* encoder, as per specification (except for native byte content, or as explicit
* end markers). Avoiding nulls is sort of
*/
int ref = _seenStringValueCount;
if (_validBackRef(ref)) {
int ix = text.hashCode() & (_seenStringValues.length-1);
_seenStringValues[ix] = new SharedStringNode(text, ref, _seenStringValues[ix]);
}
_seenStringValueCount = ref+1;
}
/**
* Helper method used to ensure that we do not use back-reference values
* that would produce illegal byte sequences (ones with byte 0xFE or 0xFF).
* Note that we do not try to avoid null byte (0x00) by default, although
* it would be technically possible as well.
*/
private final static boolean _validBackRef(int index) {
return (index & 0xFF) < 0xFE;
}
/*
/**********************************************************
/* Internal methods, error reporting
/**********************************************************
*/
/**
* Method for accessing offset of the next byte within the whole output
* stream that this generator has produced.
*/
protected long outputOffset() {
return _bytesWritten + _outputTail;
}
protected UnsupportedOperationException _notSupported() {
return new UnsupportedOperationException();
}
/*
/**********************************************************
/* Internal methods, misc other
/**********************************************************
*/
/**
* We need access to some reader-side constraints for safety-check within
* number decoding for {@linl #writeNumber(String)}: for now we need to
* rely on global defaults; should be ok for basic safeguarding.
*
* @since 2.17
*/
protected StreamReadConstraints _streamReadConstraints() {
return StreamReadConstraints.defaults();
}
}