CBORGenerator.java
package com.fasterxml.jackson.dataformat.cbor;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
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.DupDetector;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.*;
/**
* {@link JsonGenerator} implementation that writes CBOR encoded content.
*
* @author Tatu Saloranta
*/
public class CBORGenerator extends GeneratorBase
{
private final static int[] NO_INTS = new int[0];
/**
* Let's ensure that we have big enough output buffer because of safety
* margins we need for UTF-8 encoding.
*/
protected final static int BYTE_BUFFER_FOR_OUTPUT = 16000;
/**
* The replacement character to use to fix invalid Unicode sequences
* (mismatched surrogate pair).
*
* @since 2.12
*/
protected final static int REPLACEMENT_CHAR = 0xfffd;
/**
* Longest char chunk we will output is chosen so that it is guaranteed to
* fit in an empty buffer even if everything encoded in 3-byte sequences;
* but also fit two full chunks in case of single-byte (ascii) output.
*/
private final static int MAX_LONG_STRING_CHARS = (BYTE_BUFFER_FOR_OUTPUT / 4) - 4;
/**
* This is the worst case length (in bytes) of maximum chunk we ever write.
*/
private final static int MAX_LONG_STRING_BYTES = (MAX_LONG_STRING_CHARS * 3) + 3;
/**
* Enumeration that defines all togglable features for CBOR generator.
*/
public enum Feature implements FormatFeature {
/**
* Feature that determines whether generator should try to use smallest
* (size-wise) integer representation: if true, will use smallest
* representation that is enough to retain value; if false, will use
* length indicated by argument type (4-byte for <code>int</code>,
* 8-byte for <code>long</code> and so on).
*/
WRITE_MINIMAL_INTS(true),
/**
* Feature that determines whether CBOR "Self-Describe Tag" (value
* 55799, encoded as 3-byte sequence of <code>0xD9, 0xD9, 0xF7</code>)
* should be written at the beginning of document or not.
* <p>
* Default value is {@code false} meaning that type tag will not be
* written at the beginning of a new document.
*
* @since 2.5
*/
WRITE_TYPE_HEADER(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.12
*/
LENIENT_UTF_ENCODING(false),
/**
* Feature that determines if string references are generated based on the
* <a href="http://cbor.schmorp.de/stringref">stringref</a>) extension. This can save
* storage space, parsing time, and pool string memory when parsing. Readers of the output
* must also support the stringref extension to properly decode the data. Extra overhead may
* be added to generation time and memory usage to compute the shared binary and text
* strings.
* <p>
* Default value is {@code false} meaning that the stringref extension will not be used.
*
* @since 2.15
*/
STRINGREF(false),
/**
* Feature that determines whether generator should try to write doubles
* as floats: if {@code true}, will write a {@code double} as a 4-byte float if no
* precision loss will occur; if {@code false}, will always write a {@code double}
* as an 8-byte double.
* <p>
* Default value is {@code false} meaning that doubles will always be written as
* 8-byte values.
*
* @since 2.15
*/
WRITE_MINIMAL_DOUBLES(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 boolean enabledIn(int flags) {
return (flags & getMask()) != 0;
}
@Override
public int getMask() {
return _mask;
}
}
/**
* 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;
/**
* Special value that is use to keep tracks of arrays and maps opened with infinite length
*/
private final static int INDEFINITE_LENGTH = -2; // just to allow -1 as marker for "one too many"
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* @since 2.16
*/
protected final StreamWriteConstraints _streamWriteConstraints;
protected final OutputStream _out;
/**
* Bit flag composed of bits that indicate which
* {@link CBORGenerator.Feature}s are enabled.
*/
protected int _formatFeatures;
protected boolean _cfgMinimalInts;
// @since 2.15
protected boolean _cfgMinimalDoubles;
/*
/**********************************************************
/* Output state
/**********************************************************
*/
// @since 2.10 (named _cborContext before 2.13)
protected CBORWriteContext _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;
/**
* Intermediate buffer in which characters of a String are copied before
* being encoded.
*/
protected char[] _charBuffer;
protected final int _charBufferLength;
/**
* 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;
/*
/**********************************************************
/* Tracking of remaining elements to write
/**********************************************************
*/
protected int[] _elementCounts = NO_INTS;
protected int _elementCountsPtr;
/**
* Number of elements remaining in the current complex structure (if any),
* when writing defined-length Arrays, Objects; marker {code INDEFINITE_LENGTH}
* otherwise.
*/
protected int _currentRemainingElements = INDEFINITE_LENGTH;
/*
/**********************************************************
/* Shared String detection
/**********************************************************
*/
/**
* Flag that indicates whether the output buffer is recycable (and needs to
* be returned to recycler once we are done) or not.
*/
protected boolean _bufferRecyclable;
/**
* Table of previously referenced text and binary strings when the STRINGREF feature is used.
* @since 2.15
*/
protected HashMap<Object, Integer> _stringRefs;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public CBORGenerator(IOContext ioCtxt, int stdFeatures, int formatFeatures,
ObjectCodec codec, OutputStream out) {
super(stdFeatures, codec, ioCtxt, /* Write Context */ null);
DupDetector dups = JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(stdFeatures)
? DupDetector.rootDetector(this)
: null;
// NOTE: we passed `null` for default write context
_streamWriteContext = CBORWriteContext.createRootContext(dups);
_formatFeatures = formatFeatures;
_cfgMinimalInts = Feature.WRITE_MINIMAL_INTS.enabledIn(formatFeatures);
_cfgMinimalDoubles = Feature.WRITE_MINIMAL_DOUBLES.enabledIn(formatFeatures);
_streamWriteConstraints = ioCtxt.streamWriteConstraints();
_out = out;
_bufferRecyclable = true;
_stringRefs = Feature.STRINGREF.enabledIn(formatFeatures) ? new HashMap<>() : null;
_outputBuffer = ioCtxt.allocWriteEncodingBuffer(BYTE_BUFFER_FOR_OUTPUT);
_outputEnd = _outputBuffer.length;
_charBuffer = ioCtxt.allocConcatBuffer();
_charBufferLength = _charBuffer.length;
// let's just sanity check to prevent nasty odd errors
if (_outputEnd < MIN_BUFFER_LENGTH) {
throw new IllegalStateException("Internal encoding buffer length ("
+ _outputEnd + ") too short, must be at least "
+ MIN_BUFFER_LENGTH);
}
}
/**
* Alternative constructor that may be used to feed partially initialized content.
*
* @param outputBuffer
* Buffer to use for output before flushing to the underlying stream
* @param offset
* Offset pointing past already buffered content; that is, number
* of bytes of valid content to output, within buffer.
*/
public CBORGenerator(IOContext ioCtxt, int stdFeatures, int formatFeatures,
ObjectCodec codec, OutputStream out, byte[] outputBuffer,
int offset, boolean bufferRecyclable) {
super(stdFeatures, codec, ioCtxt, /* Write Context */ null);
DupDetector dups = JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(stdFeatures)
? DupDetector.rootDetector(this)
: null;
// NOTE: we passed `null` for default write context
_streamWriteContext = CBORWriteContext.createRootContext(dups);
_formatFeatures = formatFeatures;
_cfgMinimalInts = Feature.WRITE_MINIMAL_INTS.enabledIn(formatFeatures);
_cfgMinimalDoubles = Feature.WRITE_MINIMAL_DOUBLES.enabledIn(formatFeatures);
_streamWriteConstraints = ioCtxt.streamWriteConstraints();
_out = out;
_bufferRecyclable = bufferRecyclable;
_outputTail = offset;
_outputBuffer = outputBuffer;
_stringRefs = Feature.STRINGREF.enabledIn(formatFeatures) ? new HashMap<>() : null;
_outputEnd = _outputBuffer.length;
_charBuffer = ioCtxt.allocConcatBuffer();
_charBufferLength = _charBuffer.length;
// let's just sanity check to prevent nasty odd errors
if (_outputEnd < MIN_BUFFER_LENGTH) {
throw new IllegalStateException("Internal encoding buffer length ("
+ _outputEnd + ") too short, must be at least "
+ MIN_BUFFER_LENGTH);
}
}
/*
/**********************************************************
/* 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
/**********************************************************
*/
@Override
public StreamWriteConstraints streamWriteConstraints() {
return _streamWriteConstraints;
}
/**
* 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 overrideStdFeatures(int values, int mask) {
int oldState = _features;
int newState = (oldState & ~mask) | (values & mask);
if (oldState != newState) {
_features = newState;
}
return this;
}
@Override
public JsonGenerator overrideFormatFeatures(int values, int mask) {
int oldState = _formatFeatures;
int newState = (_formatFeatures & ~mask) | (values & mask);
if (oldState != newState) {
_formatFeatures = newState;
_cfgMinimalInts = Feature.WRITE_MINIMAL_INTS.enabledIn(newState);
_cfgMinimalDoubles = Feature.WRITE_MINIMAL_DOUBLES.enabledIn(newState);
}
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
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); }
/*
/**********************************************************
/* Extended API, configuration
/**********************************************************
*/
public CBORGenerator enable(Feature f) {
_formatFeatures |= f.getMask();
if (f == Feature.WRITE_MINIMAL_INTS) {
_cfgMinimalInts = true;
} else if (f == Feature.WRITE_MINIMAL_DOUBLES) {
_cfgMinimalDoubles = true;
}
return this;
}
public CBORGenerator disable(Feature f) {
_formatFeatures &= ~f.getMask();
if (f == Feature.WRITE_MINIMAL_INTS) {
_cfgMinimalInts = false;
} else if (f == Feature.WRITE_MINIMAL_DOUBLES) {
_cfgMinimalDoubles = false;
}
return this;
}
public final boolean isEnabled(Feature f) {
return (_formatFeatures & f.getMask()) != 0;
}
public CBORGenerator configure(Feature f, boolean state) {
if (state) {
enable(f);
} else {
disable(f);
}
return this;
}
/*
/**********************************************************
/* 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");
}
_writeString(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");
}
byte[] raw = name.asUnquotedUTF8();
final int len = raw.length;
if (len == 0) {
_writeByte(BYTE_EMPTY_STRING);
return;
} else if (_stringRefs != null) {
// Check for a string reference.
String str = name.getValue();
Integer index = _stringRefs.get(str);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
} else if (shouldReferenceString(_stringRefs.size(), len)) {
_stringRefs.put(str, _stringRefs.size());
}
}
_writeLengthMarker(PREFIX_TYPE_TEXT, len);
_writeBytes(raw, 0, len);
}
@Override // since 2.8
public final void writeFieldId(long id) throws IOException {
if (!_streamWriteContext.writeFieldId(id)) {
_reportError("Can not write a field id, expecting a value");
}
_writeLongNoCheck(id);
}
/*
/**********************************************************
/* Output method implementations, structural
/**********************************************************
*/
@Override
public final void writeStartArray() throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
_currentRemainingElements = INDEFINITE_LENGTH;
_writeByte(BYTE_ARRAY_INDEFINITE);
}
@Override // since 2.12
public void writeStartArray(Object forValue) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
_currentRemainingElements = INDEFINITE_LENGTH;
_writeByte(BYTE_ARRAY_INDEFINITE);
}
/*
* Unlike with JSON, this method is using slightly optimized version since
* CBOR has a variant that allows embedding length in array start marker.
*/
@Override // since 2.12
public void writeStartArray(Object forValue, int elementsToWrite) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
}
@Deprecated // since 2.12
@Override
public void writeStartArray(int elementsToWrite) throws IOException {
_verifyValueWrite("start an array");
_streamWriteContext = _streamWriteContext.createChildArrayContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_ARRAY, elementsToWrite);
}
@Override
public final void writeEndArray() throws IOException {
if (!_streamWriteContext.inArray()) {
_reportError("Current context not Array but "+_streamWriteContext.typeDesc());
}
closeComplexElement();
_streamWriteContext = _streamWriteContext.getParent();
}
@Override
public final void writeStartObject() throws IOException {
_verifyValueWrite("start an object");
_streamWriteContext = _streamWriteContext.createChildObjectContext(null);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
_currentRemainingElements = INDEFINITE_LENGTH;
_writeByte(BYTE_OBJECT_INDEFINITE);
}
@Override
// since 2.8
public final void writeStartObject(Object forValue) throws IOException {
_verifyValueWrite("start an object");
CBORWriteContext ctxt = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
_streamWriteContext = ctxt;
if (_elementCountsPtr > 0) {
_pushRemainingElements();
}
_currentRemainingElements = INDEFINITE_LENGTH;
_writeByte(BYTE_OBJECT_INDEFINITE);
}
public final void writeStartObject(int elementsToWrite) throws IOException {
writeStartObject(null, elementsToWrite);
}
@Override
public void writeStartObject(Object forValue, int elementsToWrite) throws IOException {
_verifyValueWrite("start an object");
_streamWriteContext = _streamWriteContext.createChildObjectContext(forValue);
streamWriteConstraints().validateNestingDepth(_streamWriteContext.getNestingDepth());
_pushRemainingElements();
_currentRemainingElements = elementsToWrite;
_writeLengthMarker(PREFIX_TYPE_OBJECT, elementsToWrite);
}
@Override
public final void writeEndObject() throws IOException {
if (!_streamWriteContext.inObject()) {
_reportError("Current context not Object but "+ _streamWriteContext.typeDesc());
}
closeComplexElement();
_streamWriteContext = _streamWriteContext.getParent();
}
@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");
_writeLengthMarker(PREFIX_TYPE_ARRAY, length);
if (_cfgMinimalInts) {
for (int i = offset, end = offset+length; i < end; ++i) {
final int value = array[i];
if (value < 0) {
_writeIntMinimal(PREFIX_TYPE_INT_NEG, -value - 1);
} else {
_writeIntMinimal(PREFIX_TYPE_INT_POS, value);
}
}
} else {
for (int i = offset, end = offset+length; i < end; ++i) {
final int value = array[i];
if (value < 0) {
_writeIntFull(PREFIX_TYPE_INT_NEG, -value - 1);
} else {
_writeIntFull(PREFIX_TYPE_INT_POS, value);
}
}
}
}
@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");
_writeLengthMarker(PREFIX_TYPE_ARRAY, length);
for (int i = offset, end = offset+length; i < end; ++i) {
_writeLongNoCheck(array[i]);
}
}
@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");
_writeLengthMarker(PREFIX_TYPE_ARRAY, length);
if (_cfgMinimalDoubles) {
for (int i = offset, end = offset+length; i < end; ++i) {
_writeDoubleMinimal(array[i]);
}
} else {
for (int i = offset, end = offset+length; i < end; ++i) {
_writeDoubleNoCheck(array[i]);
}
}
}
// @since 2.8.8
private final void _pushRemainingElements() {
if (_elementCounts.length == _elementCountsPtr) { // initially, as well as if full
_elementCounts = Arrays.copyOf(_elementCounts, _elementCounts.length+10);
}
_elementCounts[_elementCountsPtr++] = _currentRemainingElements;
}
private final void _writeIntMinimal(int markerBase, int i) throws IOException
{
_ensureRoomForOutput(5);
byte b0;
if (i >= 0) {
if (i < 24) {
_outputBuffer[_outputTail++] = (byte) (markerBase + i);
return;
}
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT8_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
return;
}
b0 = (byte) i;
i >>= 8;
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT16_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
return;
}
} else {
b0 = (byte) i;
i >>= 8;
}
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
}
private final void _writeIntFull(int markerBase, int i) throws IOException
{
// if ((_outputTail + needed) >= _outputEnd) { _flushBuffer(); }
_ensureRoomForOutput(5);
_outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
// Helper method that works like `writeNumber(long)` but DOES NOT
// check internal output state. It does, however, check need for minimization
private final void _writeLongNoCheck(long l) throws IOException
{
if (_cfgMinimalInts) {
if (l >= 0) {
// 31-Mar-2021, tatu: [dataformats-cbor#269] Incorrect boundary check,
// was off by one, resulting in truncation to 0
if (l < 0x100000000L) {
_writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l);
return;
}
} else if (l >= -0x100000000L) {
_writeIntMinimal(PREFIX_TYPE_INT_NEG, (int) (-l - 1));
return;
}
}
_ensureRoomForOutput(9);
if (l < 0L) {
l += 1;
l = -l;
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_NEG + SUFFIX_UINT64_ELEMENTS);
} else {
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS);
}
int i = (int) (l >> 32);
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
i = (int) l;
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
private final void _writeFloatNoCheck(float f) throws IOException {
_ensureRoomForOutput(5);
/*
* 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++] = BYTE_FLOAT32;
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
private final void _writeDoubleNoCheck(double d) throws IOException {
_ensureRoomForOutput(9);
// 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++] = BYTE_FLOAT64;
int i = (int) (l >> 32);
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
i = (int) l;
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
private final void _writeDoubleMinimal(double d) throws IOException {
float f = (float)d;
if (f == d) {
_writeFloatNoCheck(f);
} else {
_writeDoubleNoCheck(d);
}
}
/*
/***********************************************************
/* Output method implementations, textual
/***********************************************************
*/
@Override
public void writeString(String text) throws IOException {
if (text == null) {
writeNull();
return;
}
_verifyValueWrite("write String value");
_writeString(text);
}
@Override
public final void writeString(SerializableString sstr) throws IOException {
_verifyValueWrite("write String value");
byte[] raw = sstr.asUnquotedUTF8();
final int len = raw.length;
if (len == 0) {
_writeByte(BYTE_EMPTY_STRING);
return;
} else if (_stringRefs != null) {
// Check for a string reference.
String str = sstr.getValue();
Integer index = _stringRefs.get(str);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
} else if (shouldReferenceString(_stringRefs.size(), len)) {
_stringRefs.put(str, _stringRefs.size());
}
}
_writeLengthMarker(PREFIX_TYPE_TEXT, len);
_writeBytes(raw, 0, len);
}
@Override
public void writeString(char[] text, int offset, int len)
throws IOException {
_verifyValueWrite("write String value");
String str = null;
if (len == 0) {
_writeByte(BYTE_EMPTY_STRING);
return;
} else if (_stringRefs != null && len <= MAX_LONG_STRING_CHARS) {
// Check for a string reference.
str = new String(text, offset, len);
Integer index = _stringRefs.get(str);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
}
}
int actual = _writeString(text, offset, len);
if (str != null && shouldReferenceString(_stringRefs.size(), actual)) {
_stringRefs.put(str, _stringRefs.size());
}
}
@Override
public void writeRawUTF8String(byte[] raw, int offset, int len)
throws IOException
{
_verifyValueWrite("write String value");
if (len == 0) {
_writeByte(BYTE_EMPTY_STRING);
return;
} else if (_stringRefs != null) {
// Check for a string reference.
String str = new String(raw, offset, len, StandardCharsets.UTF_8);
Integer index = _stringRefs.get(str);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
} else if (shouldReferenceString(_stringRefs.size(), len)) {
_stringRefs.put(str, _stringRefs.size());
}
}
_writeLengthMarker(PREFIX_TYPE_TEXT, len);
_writeBytes(raw, offset, len);
}
@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");
ByteBuffer bytesRef = null;
if (_stringRefs != null) {
bytesRef = ByteBuffer.wrap(data, offset, len);
Integer index = _stringRefs.get(bytesRef);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
}
}
_writeLengthMarker(PREFIX_TYPE_BYTES, len);
_writeBytes(data, offset, len);
if (bytesRef != null && shouldReferenceString(_stringRefs.size(), len)) {
// Store a copy of the data to ensure that modifications don't corrupt the lookup table.
_stringRefs.put(ByteBuffer.wrap(Arrays.copyOfRange(data, offset, len)),
_stringRefs.size());
}
}
@Override
public int writeBinary(InputStream data, int dataLength) throws IOException {
/*
* 28-Mar-2014, tatu: Theoretically we could implement encoder that uses
* chunking to output binary content of unknown (a priori) length. But
* for no let's require knowledge of length, for simplicity: may be
* revisited in future.
*/
if (dataLength < 0) {
throw new UnsupportedOperationException(
"Must pass actual length for CBOR encoded data");
}
_verifyValueWrite("write Binary value");
int missing;
if (_stringRefs == null) {
_writeLengthMarker(PREFIX_TYPE_BYTES, dataLength);
missing = _writeBytes(data, dataLength);
} else {
// When computing string references must have the data available ahead of time.
byte[] bytes = new byte[dataLength];
missing = dataLength - data.read(bytes);
if (missing == 0) {
ByteBuffer bytesRef = ByteBuffer.wrap(bytes);
Integer index = _stringRefs.get(bytesRef);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
} else {
_writeLengthMarker(PREFIX_TYPE_BYTES, dataLength);
_writeBytes(bytes, 0, dataLength);
if (shouldReferenceString(_stringRefs.size(), dataLength)) {
_stringRefs.put(bytesRef, _stringRefs.size());
}
}
}
}
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(BYTE_TRUE);
} else {
_writeByte(BYTE_FALSE);
}
}
@Override
public void writeNull() throws IOException {
_verifyValueWrite("write null value");
_writeByte(BYTE_NULL);
}
@Override
public void writeNumber(int i) throws IOException {
_verifyValueWrite("write number");
int marker;
if (i < 0) {
i = -i - 1;
marker = PREFIX_TYPE_INT_NEG;
} else {
marker = PREFIX_TYPE_INT_POS;
}
_ensureRoomForOutput(5);
byte b0;
if (_cfgMinimalInts) {
if (i < 24) {
_outputBuffer[_outputTail++] = (byte) (marker + i);
return;
}
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (marker + SUFFIX_UINT8_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
return;
}
b0 = (byte) i;
i >>= 8;
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (marker + SUFFIX_UINT16_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
return;
}
} else {
b0 = (byte) i;
i >>= 8;
}
_outputBuffer[_outputTail++] = (byte) (marker + SUFFIX_UINT32_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
}
@Override
public void writeNumber(long l) throws IOException {
_verifyValueWrite("write number");
if (_cfgMinimalInts) { // maybe 32 bits is enough?
if (l >= 0) {
// 31-Mar-2021, tatu: [dataformats-cbor#269] Incorrect boundary check,
// was off by one, resulting in truncation to 0
if (l < 0x100000000L) {
_writeIntMinimal(PREFIX_TYPE_INT_POS, (int) l);
return;
}
} else if (l >= -0x100000000L) {
_writeIntMinimal(PREFIX_TYPE_INT_NEG, (int) (-l - 1));
return;
}
}
_ensureRoomForOutput(9);
if (l < 0L) {
l += 1;
l = -l;
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_NEG + SUFFIX_UINT64_ELEMENTS);
} else {
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS);
}
int i = (int) (l >> 32);
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
i = (int) l;
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
@Override
public void writeNumber(BigInteger v) throws IOException {
if (v == null) {
writeNull();
return;
}
_verifyValueWrite("write number");
_write(v);
}
// Main write method isolated so that it can be called directly
// in cases where that is needed (to encode BigDecimal)
protected void _write(BigInteger v) throws IOException {
/*
* Supported by using type tags, as per spec: major type for tag '6'; 5
* LSB either 2 for positive bignum or 3 for negative bignum. And then
* byte sequence that encode variable length integer.
*/
if (v.signum() < 0) {
_writeByte(BYTE_TAG_BIGNUM_NEG);
v = v.negate();
} else {
_writeByte(BYTE_TAG_BIGNUM_POS);
}
byte[] data = v.toByteArray();
final int len = data.length;
if (_stringRefs == null) {
_writeLengthMarker(PREFIX_TYPE_BYTES, len);
_writeBytes(data, 0, len);
} else {
ByteBuffer bytesRef = ByteBuffer.wrap(data);
Integer index = _stringRefs.get(bytesRef);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
} else {
_writeLengthMarker(PREFIX_TYPE_BYTES, len);
_writeBytes(data, 0, len);
if (shouldReferenceString(_stringRefs.size(), len)) {
_stringRefs.put(bytesRef, _stringRefs.size());
}
}
}
}
@Override
public void writeNumber(double d) throws IOException {
_verifyValueWrite("write number");
if (_cfgMinimalDoubles) {
_writeDoubleMinimal(d);
} else {
_writeDoubleNoCheck(d);
}
}
@Override
public void writeNumber(float f) throws IOException {
_verifyValueWrite("write number");
_writeFloatNoCheck(f);
}
@Override
public void writeNumber(BigDecimal dec) throws IOException {
if (dec == null) {
writeNull();
return;
}
_verifyValueWrite("write number");
/* Supported by using type tags, as per spec: major type for tag '6'; 5
* LSB 4. And then a two-element array; integer exponent, and int/bigint
* mantissa
*/
// 12-May-2016, tatu: Before 2.8, used "bigfloat", but that was
// incorrect...
_writeByte(BYTE_TAG_DECIMAL_FRACTION);
_writeByte(BYTE_ARRAY_2_ELEMENTS);
// 27-Nov-2019, tatu: As per [dataformats-binary#139] need to change sign here
int scale = dec.scale();
_writeIntValue(-scale);
// Hmmmh. Specification suggest use of regular integer for mantissa. But
// if it doesn't fit, use "bignum"
BigInteger unscaled = dec.unscaledValue();
int bitLength = unscaled.bitLength();
if (bitLength <= 31) {
_writeIntValue(unscaled.intValue());
} else if (bitLength <= 63) {
_writeLongValue(unscaled.longValue());
} else {
_write(unscaled);
}
}
@Override
public void writeNumber(String encodedValue) throws IOException,
JsonGenerationException, UnsupportedOperationException {
// just write as a String -- CBOR does not require schema, so
// databinding
// on receiving end should be able to coerce it appropriately
writeString(encodedValue);
}
/*
/**********************************************************
/* Implementations for other methods
/**********************************************************
*/
@Override
protected final void _verifyValueWrite(String typeMsg) throws IOException {
if (!_streamWriteContext.writeValue()) {
_reportError("Can not " + typeMsg + ", expecting field name/id");
}
// decrementElementsRemainingCount()
int count = _currentRemainingElements;
if (count != INDEFINITE_LENGTH) {
--count;
// 28-Jun-2016, tatu: _Should_ check overrun immediately (instead of waiting
// for end of Object/Array), but has 10% performance penalty for some reason,
// should figure out why and how to avoid
if (count < 0) {
_failSizedArrayOrObject();
return; // never gets here
}
_currentRemainingElements = count;
}
}
private void _failSizedArrayOrObject() throws IOException
{
_reportError(String.format("%s size mismatch: number of element encoded is not equal to reported array/map size.",
_streamWriteContext.typeDesc()));
}
/*
/**********************************************************
/* 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;
}
}
}
_flushBuffer();
if (_ioContext.isResourceManaged()
|| isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)) {
_out.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
_out.flush();
}
// Internal buffer(s) generator has can now be released as well
_releaseBuffers();
super.close();
}
}
/*
/**********************************************************
* Extended API, CBOR-specific encoded output
/**********************************************************
*/
/**
* Method for writing out an explicit CBOR Tag.
*
* @param tagId
* Positive integer (0 or higher)
*
* @since 2.5
*/
public void writeTag(int tagId) throws IOException {
if (tagId < 0) {
throw new IllegalArgumentException(
"Can not write negative tag ids (" + tagId + ")");
}
_writeLengthMarker(PREFIX_TYPE_TAG, tagId);
}
/*
/**********************************************************
/* Extended API, raw bytes (by-passing encoder)
/**********************************************************
*/
/**
* 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 {
_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);
}
/*
/**********************************************************
/* Internal methods: low-level text output
/**********************************************************
*/
private final static int MAX_SHORT_STRING_CHARS = 23;
// in case it's > 23 bytes
private final static int MAX_SHORT_STRING_BYTES = 23 * 3 + 2;
private final static int MAX_MEDIUM_STRING_CHARS = 255;
// in case it's > 255 bytes
private final static int MAX_MEDIUM_STRING_BYTES = 255 * 3 + 3;
protected final void _writeString(String name) throws IOException {
int len = name.length();
if (len == 0) {
_writeByte(BYTE_EMPTY_STRING);
return;
}
// Check if this is a previously referenced string. This will only be done for strings that
// have a definite length.
if (_stringRefs != null && len <= MAX_LONG_STRING_CHARS) {
Integer index = _stringRefs.get(name);
if (index != null) {
writeTag(TAG_ID_STRINGREF);
_writeIntMinimal(PREFIX_TYPE_INT_POS, index);
return;
}
}
// Actually, let's not bother with copy for shortest strings
if (len <= MAX_SHORT_STRING_CHARS) {
_ensureSpace(MAX_SHORT_STRING_BYTES); // can afford approximate length
int actual = _encode(_outputTail + 1, name, len);
// Store reference for later if valid to do so.
if (_stringRefs != null && shouldReferenceString(_stringRefs.size(), actual)) {
_stringRefs.put(name, _stringRefs.size());
}
final byte[] buf = _outputBuffer;
int ix = _outputTail;
if (actual <= MAX_SHORT_STRING_CHARS) { // fits in prefix byte
buf[ix++] = (byte) (PREFIX_TYPE_TEXT + actual);
_outputTail = ix + actual;
return;
}
// no, have to move. Blah.
System.arraycopy(buf, ix + 1, buf, ix + 2, actual);
buf[ix++] = BYTE_STRING_1BYTE_LEN;
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
return;
}
char[] cbuf = _charBuffer;
if (len > cbuf.length) {
_charBuffer = cbuf = new char[Math
.max(_charBuffer.length + 32, len)];
}
name.getChars(0, len, cbuf, 0);
int actual = _writeString(cbuf, 0, len);
// Store reference for later if valid to do so. Actual length will be negative if an
// indefinite length string was written.
if (actual >= 0 && _stringRefs != null &&
shouldReferenceString(_stringRefs.size(), actual)) {
_stringRefs.put(name, _stringRefs.size());
}
}
protected final void _ensureSpace(int needed) throws IOException {
if ((_outputTail + needed + 3) > _outputEnd) {
_flushBuffer();
}
}
protected final int _writeString(char[] text, int offset, int len)
throws IOException
{
if (len <= MAX_SHORT_STRING_CHARS) { // possibly short string (not necessarily)
_ensureSpace(MAX_SHORT_STRING_BYTES); // can afford approximate length
int actual = _encode(_outputTail + 1, text, offset, offset + len);
final byte[] buf = _outputBuffer;
int ix = _outputTail;
if (actual <= MAX_SHORT_STRING_CHARS) { // fits in prefix byte
buf[ix++] = (byte) (PREFIX_TYPE_TEXT + actual);
_outputTail = ix + actual;
return actual;
}
// no, have to move. Blah.
System.arraycopy(buf, ix + 1, buf, ix + 2, actual);
buf[ix++] = BYTE_STRING_1BYTE_LEN;
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
return actual;
}
if (len <= MAX_MEDIUM_STRING_CHARS) {
_ensureSpace(MAX_MEDIUM_STRING_BYTES); // short enough, can approximate
int actual = _encode(_outputTail + 2, text, offset, offset + len);
final byte[] buf = _outputBuffer;
int ix = _outputTail;
if (actual <= MAX_MEDIUM_STRING_CHARS) { // fits as expected
buf[ix++] = BYTE_STRING_1BYTE_LEN;
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
return actual;
}
// no, have to move. Blah.
System.arraycopy(buf, ix + 2, buf, ix + 3, actual);
buf[ix++] = BYTE_STRING_2BYTE_LEN;
buf[ix++] = (byte) (actual >> 8);
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
return actual;
}
if (len <= MAX_LONG_STRING_CHARS) { // no need to chunk yet
// otherwise, long but single chunk
_ensureSpace(MAX_LONG_STRING_BYTES); // calculate accurate length to
// avoid extra flushing
int ix = _outputTail;
int actual = _encode(ix + 3, text, offset, offset + len);
final byte[] buf = _outputBuffer;
buf[ix++] = BYTE_STRING_2BYTE_LEN;
buf[ix++] = (byte) (actual >> 8);
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
return actual;
}
_writeChunkedString(text, offset, len);
return -1;
}
protected final void _writeChunkedString(char[] text, int offset, int len)
throws IOException
{
// need to use a marker first
_writeByte(BYTE_STRING_INDEFINITE);
while (len > MAX_LONG_STRING_CHARS) {
_ensureSpace(MAX_LONG_STRING_BYTES); // marker and single-byte length?
int ix = _outputTail;
int amount = MAX_LONG_STRING_CHARS;
// 23-May-2016, tatu: Make sure NOT to try to split surrogates in half
int end = offset + amount;
char c = text[end-1];
if (c >= SURR1_FIRST && c <= SURR1_LAST) {
--end;
--amount;
}
int actual = _encode(_outputTail + 3, text, offset, end);
final byte[] buf = _outputBuffer;
buf[ix++] = BYTE_STRING_2BYTE_LEN;
buf[ix++] = (byte) (actual >> 8);
buf[ix++] = (byte) actual;
_outputTail = ix + actual;
offset += amount;
len -= amount;
}
// and for the last chunk, just use recursion
if (len > 0) {
_writeString(text, offset, len);
}
// plus end marker
_writeByte(BYTE_BREAK);
}
/*
/**********************************************************
/* 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 _encode(int outputPtr, char[] str, int i, int end) throws IOException
{
// First: let's see if it's all ASCII: that's rather fast
final byte[] outBuf = _outputBuffer;
final int outputStart = outputPtr;
do {
int c = str[i];
if (c > 0x7F) {
return _shortUTF8Encode2(str, i, end, outputPtr, outputStart);
}
outBuf[outputPtr++] = (byte) c;
} while (++i < end);
return outputPtr - outputStart;
}
/**
* 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, int outputStart) 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)
if (c < SURR1_FIRST || c > SURR2_LAST) { // 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);
}
return (outputPtr - outputStart);
}
private final int _encode(int outputPtr, String str, int len) throws IOException {
final byte[] outBuf = _outputBuffer;
final int outputStart = outputPtr;
for (int i = 0; i < len; ++i) {
int c = str.charAt(i);
if (c > 0x7F) {
return _encode2(i, outputPtr, str, len, outputStart);
}
outBuf[outputPtr++] = (byte) c;
}
return (outputPtr - outputStart);
}
private final int _encode2(int i, int outputPtr, String str, int len,
int outputStart) throws IOException
{
final byte[] outBuf = _outputBuffer;
// no; non-ASCII stuff, slower loop
while (i < len) {
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)
if (c < SURR1_FIRST || c > SURR2_LAST) { // 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 < len)) { // 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);
}
return (outputPtr - outputStart);
}
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 _writeIntValue(int i) throws IOException {
int marker;
if (i < 0) {
i = -i - 1;
marker = PREFIX_TYPE_INT_NEG;
} else {
marker = PREFIX_TYPE_INT_POS;
}
_writeLengthMarker(marker, i);
}
private final void _writeLongValue(long l) throws IOException {
_ensureRoomForOutput(9);
if (l < 0) {
l += 1;
l = -l;
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_NEG + SUFFIX_UINT64_ELEMENTS);
} else {
_outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS);
}
int i = (int) (l >> 32);
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
i = (int) l;
_outputBuffer[_outputTail++] = (byte) (i >> 24);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
}
private final void _writeLengthMarker(int majorType, int i)
throws IOException {
_ensureRoomForOutput(5);
if (i < 24) {
_outputBuffer[_outputTail++] = (byte) (majorType + i);
return;
}
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT8_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
return;
}
final byte b0 = (byte) i;
i >>= 8;
if (i <= 0xFF) {
_outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT16_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
return;
}
_outputBuffer[_outputTail++] = (byte) (majorType + SUFFIX_UINT32_ELEMENTS);
_outputBuffer[_outputTail++] = (byte) (i >> 16);
_outputBuffer[_outputTail++] = (byte) (i >> 8);
_outputBuffer[_outputTail++] = (byte) i;
_outputBuffer[_outputTail++] = b0;
}
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[] 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;
}
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();
}
}
/*
/**********************************************************
/* Internal methods, buffer handling
/**********************************************************
*/
@Override
protected void _releaseBuffers() {
byte[] buf = _outputBuffer;
if (buf != null && _bufferRecyclable) {
_outputBuffer = null;
_ioContext.releaseWriteEncodingBuffer(buf);
}
char[] cbuf = _charBuffer;
if (cbuf != null) {
_charBuffer = null;
_ioContext.releaseConcatBuffer(cbuf);
}
}
protected final void _flushBuffer() throws IOException {
if (_outputTail > 0) {
_bytesWritten += _outputTail;
_out.write(_outputBuffer, 0, _outputTail);
_outputTail = 0;
}
}
/*
/**********************************************************
/* Internal methods, size control for array and objects
/**********************************************************
*/
private final void closeComplexElement() throws IOException {
switch (_currentRemainingElements) {
case INDEFINITE_LENGTH:
_writeByte(BYTE_BREAK);
break;
case 0: // expected for sized ones
break;
default:
_reportError(String.format("%s size mismatch: expected %d more elements",
_streamWriteContext.typeDesc(), _currentRemainingElements));
}
_currentRemainingElements = (_elementCountsPtr == 0)
? INDEFINITE_LENGTH
: _elementCounts[--_elementCountsPtr];
}
/*
/**********************************************************
/* Internal methods, error reporting
/**********************************************************
*/
protected UnsupportedOperationException _notSupported() {
return new UnsupportedOperationException();
}
}