package com.fasterxml.jackson.core.json;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.*;
public class UTF8JsonGenerator
extends JsonGeneratorImpl
{
private final static byte BYTE_u = (byte) 'u';
private final static byte BYTE_0 = (byte) '0';
private final static byte BYTE_LBRACKET = (byte) '[';
private final static byte BYTE_RBRACKET = (byte) ']';
private final static byte BYTE_LCURLY = (byte) '{';
private final static byte BYTE_RCURLY = (byte) '}';
private final static byte BYTE_BACKSLASH = (byte) '\\';
private final static byte BYTE_COMMA = (byte) ',';
private final static byte BYTE_COLON = (byte) ':';
private final static byte BYTE_QUOTE = (byte) '"';
protected final static int SURR1_FIRST = 0xD800;
protected final static int SURR1_LAST = 0xDBFF;
protected final static int SURR2_FIRST = 0xDC00;
protected final static int SURR2_LAST = 0xDFFF;
// intermediate copies only made up to certain length...
private final static int MAX_BYTES_TO_BUFFER = 512;
final static byte[] HEX_CHARS = CharTypes.copyHexBytes();
private final static byte[] NULL_BYTES = { 'n', 'u', 'l', 'l' };
private final static byte[] TRUE_BYTES = { 't', 'r', 'u', 'e' };
private final static byte[] FALSE_BYTES = { 'f', 'a', 'l', 's', 'e' };
/*
/**********************************************************
/* Output buffering
/**********************************************************
*/
/**
* Underlying output stream used for writing JSON content.
*/
final protected OutputStream _outputStream;
/**
* Intermediate buffer in which contents are buffered before
* being written using {@link #_outputStream}.
*/
protected byte[] _outputBuffer;
/**
* Pointer to the position right beyond the last character to output
* (end marker; may be past the buffer)
*/
protected int _outputTail = 0;
/**
* End marker of the output buffer; one past the last valid position
* within the buffer.
*/
protected final int _outputEnd;
/**
* Maximum number of <code>char</code>s that we know will always fit
* in the output buffer after escaping
*/
protected final int _outputMaxContiguous;
/**
* Intermediate buffer in which characters of a String are copied
* before being encoded.
*/
protected char[] _charBuffer;
/**
* Length of <code>_charBuffer</code>
*/
protected final int _charBufferLength;
/**
* 6 character temporary buffer allocated if needed, for constructing
* escape sequences
*/
protected byte[] _entityBuffer;
/**
* 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;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
OutputStream out)
{
super(ctxt, features, codec);
_outputStream = out;
_bufferRecyclable = true;
_outputBuffer = ctxt.allocWriteEncodingBuffer();
_outputEnd = _outputBuffer.length;
/* To be exact, each char can take up to 6 bytes when escaped (Unicode
* escape with backslash, 'u' and 4 hex digits); but to avoid fluctuation,
* we will actually round down to only do up to 1/8 number of chars
*/
_outputMaxContiguous = _outputEnd >> 3;
_charBuffer = ctxt.allocConcatBuffer();
_charBufferLength = _charBuffer.length;
// By default we use this feature to determine additional quoting
if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
setHighestNonEscapedChar(127);
}
}
public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
OutputStream out,
byte[] outputBuffer, int outputOffset, boolean bufferRecyclable)
{
super(ctxt, features, codec);
_outputStream = out;
_bufferRecyclable = bufferRecyclable;
_outputTail = outputOffset;
_outputBuffer = outputBuffer;
_outputEnd = _outputBuffer.length;
// up to 6 bytes per char (see above), rounded up to 1/8
_outputMaxContiguous = _outputEnd >> 3;
_charBuffer = ctxt.allocConcatBuffer();
_charBufferLength = _charBuffer.length;
}
/*
/**********************************************************
/* Overridden configuration methods
/**********************************************************
*/
@Override
public Object getOutputTarget() {
return _outputStream;
}
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public final void writeFieldName(String name) throws IOException, JsonGenerationException
{
int status = _writeContext.writeFieldName(name);
if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
if (_cfgPrettyPrinter != null) {
_writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
return;
}
if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) { // need comma
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_COMMA;
}
_writeFieldName(name);
}
@Override
public final void writeFieldName(SerializableString name)
throws IOException, JsonGenerationException
{
// Object is a value, need to verify it's allowed
int status = _writeContext.writeFieldName(name.getValue());
if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
if (_cfgPrettyPrinter != null) {
_writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
return;
}
if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_COMMA;
}
_writeFieldName(name);
}
/*
/**********************************************************
/* Output method implementations, structural
/**********************************************************
*/
@Override
public final void writeStartArray() throws IOException, JsonGenerationException
{
_verifyValueWrite("start an array");
_writeContext = _writeContext.createChildArrayContext();
if (_cfgPrettyPrinter != null) {
_cfgPrettyPrinter.writeStartArray(this);
} else {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_LBRACKET;
}
}
@Override
public final void writeEndArray() throws IOException, JsonGenerationException
{
if (!_writeContext.inArray()) {
_reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc());
}
if (_cfgPrettyPrinter != null) {
_cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
} else {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_RBRACKET;
}
_writeContext = _writeContext.getParent();
}
@Override
public final void writeStartObject() throws IOException, JsonGenerationException
{
_verifyValueWrite("start an object");
_writeContext = _writeContext.createChildObjectContext();
if (_cfgPrettyPrinter != null) {
_cfgPrettyPrinter.writeStartObject(this);
} else {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_LCURLY;
}
}
@Override
public final void writeEndObject() throws IOException, JsonGenerationException
{
if (!_writeContext.inObject()) {
_reportError("Current context not an object but "+_writeContext.getTypeDesc());
}
if (_cfgPrettyPrinter != null) {
_cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
} else {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_RCURLY;
}
_writeContext = _writeContext.getParent();
}
protected final void _writeFieldName(String name)
throws IOException, JsonGenerationException
{
/* To support [JACKSON-46], we'll do this:
* (Question: should quoting of spaces (etc) still be enabled?)
*/
if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
_writeStringSegments(name);
return;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
// The beef:
final int len = name.length();
if (len <= _charBufferLength) { // yes, fits right in
name.getChars(0, len, _charBuffer, 0);
// But as one segment, or multiple?
if (len <= _outputMaxContiguous) {
if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
_flushBuffer();
}
_writeStringSegment(_charBuffer, 0, len);
} else {
_writeStringSegments(_charBuffer, 0, len);
}
} else {
_writeStringSegments(name);
}
// and closing quotes; need room for one more char:
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
protected final void _writeFieldName(SerializableString name)
throws IOException, JsonGenerationException
{
if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
int len = name.appendQuotedUTF8(_outputBuffer, _outputTail); // different quoting (escaping)
if (len < 0) {
_writeBytes(name.asQuotedUTF8());
} else {
_outputTail += len;
}
return;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
if (len < 0) { // couldn't append, bit longer processing
_writeBytes(name.asQuotedUTF8());
} else {
_outputTail += len;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
/**
* Specialized version of <code>_writeFieldName</code>, off-lined
* to keep the "fast path" as simple (and hopefully fast) as possible.
*/
protected final void _writePPFieldName(String name, boolean commaBefore)
throws IOException, JsonGenerationException
{
if (commaBefore) {
_cfgPrettyPrinter.writeObjectEntrySeparator(this);
} else {
_cfgPrettyPrinter.beforeObjectEntries(this);
}
if (isEnabled(Feature.QUOTE_FIELD_NAMES)) { // standard
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
final int len = name.length();
if (len <= _charBufferLength) { // yes, fits right in
name.getChars(0, len, _charBuffer, 0);
// But as one segment, or multiple?
if (len <= _outputMaxContiguous) {
if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
_flushBuffer();
}
_writeStringSegment(_charBuffer, 0, len);
} else {
_writeStringSegments(_charBuffer, 0, len);
}
} else {
_writeStringSegments(name);
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
} else { // non-standard, omit quotes
_writeStringSegments(name);
}
}
protected final void _writePPFieldName(SerializableString name, boolean commaBefore)
throws IOException, JsonGenerationException
{
if (commaBefore) {
_cfgPrettyPrinter.writeObjectEntrySeparator(this);
} else {
_cfgPrettyPrinter.beforeObjectEntries(this);
}
boolean addQuotes = isEnabled(Feature.QUOTE_FIELD_NAMES); // standard
if (addQuotes) {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
_writeBytes(name.asQuotedUTF8());
if (addQuotes) {
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
}
/*
/**********************************************************
/* Output method implementations, textual
/**********************************************************
*/
@Override
public void writeString(String text)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (text == null) {
_writeNull();
return;
}
// First: can we make a local copy of chars that make up text?
final int len = text.length();
if (len > _charBufferLength) { // nope: off-line handling
_writeLongString(text);
return;
}
// yes: good.
text.getChars(0, len, _charBuffer, 0);
// Output: if we can't guarantee it fits in output buffer, off-line as well:
if (len > _outputMaxContiguous) {
_writeLongString(_charBuffer, 0, len);
return;
}
if ((_outputTail + len) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_writeStringSegment(_charBuffer, 0, len); // we checked space already above
/* [JACKSON-462] But that method may have had to expand multi-byte Unicode
* chars, so we must check again
*/
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
private void _writeLongString(String text)
throws IOException, JsonGenerationException
{
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_writeStringSegments(text);
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
private void _writeLongString(char[] text, int offset, int len)
throws IOException, JsonGenerationException
{
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_writeStringSegments(_charBuffer, 0, len);
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeString(char[] text, int offset, int len)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
// One or multiple segments?
if (len <= _outputMaxContiguous) {
if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
_flushBuffer();
}
_writeStringSegment(text, offset, len);
} else {
_writeStringSegments(text, offset, len);
}
// And finally, closing quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public final void writeString(SerializableString text)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
int len = text.appendQuotedUTF8(_outputBuffer, _outputTail);
if (len < 0) {
_writeBytes(text.asQuotedUTF8());
} else {
_outputTail += len;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeRawUTF8String(byte[] text, int offset, int length)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_writeBytes(text, offset, length);
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeUTF8String(byte[] text, int offset, int len)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write text value");
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
// One or multiple segments?
if (len <= _outputMaxContiguous) {
_writeUTF8Segment(text, offset, len);
} else {
_writeUTF8Segments(text, offset, len);
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
/*
/**********************************************************
/* Output method implementations, unprocessed ("raw")
/**********************************************************
*/
@Override
public void writeRaw(String text)
throws IOException, JsonGenerationException
{
int start = 0;
int len = text.length();
while (len > 0) {
char[] buf = _charBuffer;
final int blen = buf.length;
final int len2 = (len < blen) ? len : blen;
text.getChars(start, start+len2, buf, 0);
writeRaw(buf, 0, len2);
start += len2;
len -= len2;
}
}
@Override
public void writeRaw(String text, int offset, int len)
throws IOException, JsonGenerationException
{
while (len > 0) {
char[] buf = _charBuffer;
final int blen = buf.length;
final int len2 = (len < blen) ? len : blen;
text.getChars(offset, offset+len2, buf, 0);
writeRaw(buf, 0, len2);
offset += len2;
len -= len2;
}
}
@Override
public void writeRaw(SerializableString text) throws IOException, JsonGenerationException
{
byte[] raw = text.asUnquotedUTF8();
if (raw.length > 0) {
_writeBytes(raw);
}
}
// @TODO: rewrite for speed...
@Override
public final void writeRaw(char[] cbuf, int offset, int len)
throws IOException, JsonGenerationException
{
// First: if we have 3 x charCount spaces, we know it'll fit just fine
{
int len3 = len+len+len;
if ((_outputTail + len3) > _outputEnd) {
// maybe we could flush?
if (_outputEnd < len3) { // wouldn't be enough...
_writeSegmentedRaw(cbuf, offset, len);
return;
}
// yes, flushing brings enough space
_flushBuffer();
}
}
len += offset; // now marks the end
// Note: here we know there is enough room, hence no output boundary checks
main_loop:
while (offset < len) {
inner_loop:
while (true) {
int ch = (int) cbuf[offset];
if (ch > 0x7F) {
break inner_loop;
}
_outputBuffer[_outputTail++] = (byte) ch;
if (++offset >= len) {
break main_loop;
}
}
char ch = cbuf[offset++];
if (ch < 0x800) { // 2-byte?
_outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6));
_outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
} else {
_outputRawMultiByteChar(ch, cbuf, offset, len);
}
}
}
@Override
public void writeRaw(char ch)
throws IOException, JsonGenerationException
{
if ((_outputTail + 3) >= _outputEnd) {
_flushBuffer();
}
final byte[] bbuf = _outputBuffer;
if (ch <= 0x7F) {
bbuf[_outputTail++] = (byte) ch;
} else if (ch < 0x800) { // 2-byte?
bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
} else {
_outputRawMultiByteChar(ch, null, 0, 0);
}
}
/**
* Helper method called when it is possible that output of raw section
* to output may cross buffer boundary
*/
private void _writeSegmentedRaw(char[] cbuf, int offset, int len)
throws IOException, JsonGenerationException
{
final int end = _outputEnd;
final byte[] bbuf = _outputBuffer;
main_loop:
while (offset < len) {
inner_loop:
while (true) {
int ch = (int) cbuf[offset];
if (ch >= 0x80) {
break inner_loop;
}
// !!! TODO: fast(er) writes (roll input, output checks in one)
if (_outputTail >= end) {
_flushBuffer();
}
bbuf[_outputTail++] = (byte) ch;
if (++offset >= len) {
break main_loop;
}
}
if ((_outputTail + 3) >= _outputEnd) {
_flushBuffer();
}
char ch = cbuf[offset++];
if (ch < 0x800) { // 2-byte?
bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
} else {
_outputRawMultiByteChar(ch, cbuf, offset, len);
}
}
}
/*
/**********************************************************
/* Output method implementations, base64-encoded binary
/**********************************************************
*/
@Override
public void writeBinary(Base64Variant b64variant,
byte[] data, int offset, int len)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write binary value");
// Starting quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_writeBinary(b64variant, data, offset, offset+len);
// and closing quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public int writeBinary(Base64Variant b64variant,
InputStream data, int dataLength)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write binary value");
// Starting quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
byte[] encodingBuffer = _ioContext.allocBase64Buffer();
int bytes;
try {
if (dataLength < 0) { // length unknown
bytes = _writeBinary(b64variant, data, encodingBuffer);
} else {
int missing = _writeBinary(b64variant, data, encodingBuffer, dataLength);
if (missing > 0) {
_reportError("Too few bytes available: missing "+missing+" bytes (out of "+dataLength+")");
}
bytes = dataLength;
}
} finally {
_ioContext.releaseBase64Buffer(encodingBuffer);
}
// and closing quotes
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
return bytes;
}
/*
/**********************************************************
/* Output method implementations, primitive
/**********************************************************
*/
@Override
public void writeNumber(int i)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write number");
// up to 10 digits and possible minus sign
if ((_outputTail + 11) >= _outputEnd) {
_flushBuffer();
}
if (_cfgNumbersAsStrings) {
_writeQuotedInt(i);
return;
}
_outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
}
private void _writeQuotedInt(int i) throws IOException {
if ((_outputTail + 13) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeNumber(long l)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write number");
if (_cfgNumbersAsStrings) {
_writeQuotedLong(l);
return;
}
if ((_outputTail + 21) >= _outputEnd) {
// up to 20 digits, minus sign
_flushBuffer();
}
_outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
}
private void _writeQuotedLong(long l) throws IOException {
if ((_outputTail + 23) >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
_outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeNumber(BigInteger value)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write number");
if (value == null) {
_writeNull();
} else if (_cfgNumbersAsStrings) {
_writeQuotedRaw(value);
} else {
writeRaw(value.toString());
}
}
@Override
public void writeNumber(double d)
throws IOException, JsonGenerationException
{
if (_cfgNumbersAsStrings ||
// [JACKSON-139]
(((Double.isNaN(d) || Double.isInfinite(d))
&& isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
writeString(String.valueOf(d));
return;
}
// What is the max length for doubles? 40 chars?
_verifyValueWrite("write number");
writeRaw(String.valueOf(d));
}
@Override
public void writeNumber(float f)
throws IOException, JsonGenerationException
{
if (_cfgNumbersAsStrings ||
// [JACKSON-139]
(((Float.isNaN(f) || Float.isInfinite(f))
&& isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
writeString(String.valueOf(f));
return;
}
// What is the max length for floats?
_verifyValueWrite("write number");
writeRaw(String.valueOf(f));
}
@Override
public void writeNumber(BigDecimal value)
throws IOException, JsonGenerationException
{
// Don't really know max length for big decimal, no point checking
_verifyValueWrite("write number");
if (value == null) {
_writeNull();
} else if (_cfgNumbersAsStrings) {
_writeQuotedRaw(value);
} else {
writeRaw(value.toString());
}
}
@Override
public void writeNumber(String encodedValue)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write number");
if (_cfgNumbersAsStrings) {
_writeQuotedRaw(encodedValue);
} else {
writeRaw(encodedValue);
}
}
private void _writeQuotedRaw(Object value) throws IOException
{
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
writeRaw(value.toString());
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail++] = BYTE_QUOTE;
}
@Override
public void writeBoolean(boolean state)
throws IOException, JsonGenerationException
{
_verifyValueWrite("write boolean value");
if ((_outputTail + 5) >= _outputEnd) {
_flushBuffer();
}
byte[] keyword = state ? TRUE_BYTES : FALSE_BYTES;
int len = keyword.length;
System.arraycopy(keyword, 0, _outputBuffer, _outputTail, len);
_outputTail += len;
}
@Override
public void writeNull()
throws IOException, JsonGenerationException
{
_verifyValueWrite("write null value");
_writeNull();
}
/*
/**********************************************************
/* Implementations for other methods
/**********************************************************
*/
@Override
protected final void _verifyValueWrite(String typeMsg)
throws IOException, JsonGenerationException
{
int status = _writeContext.writeValue();
if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
_reportError("Can not "+typeMsg+", expecting field name");
}
if (_cfgPrettyPrinter == null) {
byte b;
switch (status) {
case JsonWriteContext.STATUS_OK_AFTER_COMMA:
b = BYTE_COMMA;
break;
case JsonWriteContext.STATUS_OK_AFTER_COLON:
b = BYTE_COLON;
break;
case JsonWriteContext.STATUS_OK_AFTER_SPACE: // root-value separator
if (_rootValueSeparator != null) {
byte[] raw = _rootValueSeparator.asUnquotedUTF8();
if (raw.length > 0) {
_writeBytes(raw);
}
}
return;
case JsonWriteContext.STATUS_OK_AS_IS:
default:
return;
}
if (_outputTail >= _outputEnd) {
_flushBuffer();
}
_outputBuffer[_outputTail] = b;
++_outputTail;
return;
}
// Otherwise, pretty printer knows what to do...
_verifyPrettyValueWrite(typeMsg, status);
}
protected final void _verifyPrettyValueWrite(String typeMsg, int status)
throws IOException, JsonGenerationException
{
// If we have a pretty printer, it knows what to do:
switch (status) {
case JsonWriteContext.STATUS_OK_AFTER_COMMA: // array
_cfgPrettyPrinter.writeArrayValueSeparator(this);
break;
case JsonWriteContext.STATUS_OK_AFTER_COLON:
_cfgPrettyPrinter.writeObjectFieldValueSeparator(this);
break;
case JsonWriteContext.STATUS_OK_AFTER_SPACE:
_cfgPrettyPrinter.writeRootValueSeparator(this);
break;
case JsonWriteContext.STATUS_OK_AS_IS:
// First entry, but of which context?
if (_writeContext.inArray()) {
_cfgPrettyPrinter.beforeArrayValues(this);
} else if (_writeContext.inObject()) {
_cfgPrettyPrinter.beforeObjectEntries(this);
}
break;
default:
_cantHappen();
break;
}
}
/*
/**********************************************************
/* Low-level output handling
/**********************************************************
*/
@Override
public final void flush()
throws IOException
{
_flushBuffer();
if (_outputStream != null) {
if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
_outputStream.flush();
}
}
}
@Override
public void close()
throws IOException
{
super.close();
/* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
* scopes.
*/
// First: let's see that we still have buffers...
if (_outputBuffer != null
&& isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
while (true) {
JsonStreamContext ctxt = getOutputContext();
if (ctxt.inArray()) {
writeEndArray();
} else if (ctxt.inObject()) {
writeEndObject();
} else {
break;
}
}
}
_flushBuffer();
/* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
* on the underlying Reader, unless we "own" it, or auto-closing
* feature is enabled.
* One downside: when using UTF8Writer, underlying buffer(s)
* may not be properly recycled if we don't close the writer.
*/
if (_outputStream != null) {
if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
_outputStream.close();
} else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
// If we can't close it, we should at least flush
_outputStream.flush();
}
}
// Internal buffer(s) generator has can now be released as well
_releaseBuffers();
}
@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);
}
}
/*
/**********************************************************
/* Internal methods, low-level writing, raw bytes
/**********************************************************
*/
private void _writeBytes(byte[] bytes) throws IOException
{
final int len = bytes.length;
if ((_outputTail + len) > _outputEnd) {
_flushBuffer();
// still not enough?
if (len > MAX_BYTES_TO_BUFFER) {
_outputStream.write(bytes, 0, len);
return;
}
}
System.arraycopy(bytes, 0, _outputBuffer, _outputTail, len);
_outputTail += len;
}
private void _writeBytes(byte[] bytes, int offset, int len) throws IOException
{
if ((_outputTail + len) > _outputEnd) {
_flushBuffer();
// still not enough?
if (len > MAX_BYTES_TO_BUFFER) {
_outputStream.write(bytes, offset, len);
return;
}
}
System.arraycopy(bytes, offset, _outputBuffer, _outputTail, len);
_outputTail += len;
}
/*
/**********************************************************
/* Internal methods, mid-level writing, String segments
/**********************************************************
*/
/**
* Method called when String to write is long enough not to fit
* completely in temporary copy buffer. If so, we will actually
* copy it in small enough chunks so it can be directly fed
* to single-segment writes (instead of maximum slices that
* would fit in copy buffer)
*/
private void _writeStringSegments(String text)
throws IOException, JsonGenerationException
{
int left = text.length();
int offset = 0;
final char[] cbuf = _charBuffer;
while (left > 0) {
int len = Math.min(_outputMaxContiguous, left);
text.getChars(offset, offset+len, cbuf, 0);
if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
_flushBuffer();
}
_writeStringSegment(cbuf, 0, len);
offset += len;
left -= len;
}
}
/**
* Method called when character sequence to write is long enough that
* its maximum encoded and escaped form is not guaranteed to fit in
* the output buffer. If so, we will need to choose smaller output
* chunks to write at a time.
*/
private void _writeStringSegments(char[] cbuf, int offset, int totalLen)
throws IOException, JsonGenerationException
{
do {
int len = Math.min(_outputMaxContiguous, totalLen);
if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
_flushBuffer();
}
_writeStringSegment(cbuf, offset, len);
offset += len;
totalLen -= len;
} while (totalLen > 0);
}
/*
/**********************************************************
/* Internal methods, low-level writing, text segments
/**********************************************************
*/
/**
* This method called when the string content is already in
* a char buffer, and its maximum total encoded and escaped length
* can not exceed size of the output buffer.
* Caller must ensure that there is enough space in output buffer,
* assuming case of all non-escaped ASCII characters, as well as
* potentially enough space for other cases (but not necessarily flushed)
*/
private void _writeStringSegment(char[] cbuf, int offset, int len)
throws IOException, JsonGenerationException
{
// note: caller MUST ensure (via flushing) there's room for ASCII only
// Fast+tight loop for ASCII-only, no-escaping-needed output
len += offset; // becomes end marker, then
int outputPtr = _outputTail;
final byte[] outputBuffer = _outputBuffer;
final int[] escCodes = _outputEscapes;
while (offset < len) {
int ch = cbuf[offset];
// note: here we know that (ch > 0x7F) will cover case of escaping non-ASCII too:
if (ch > 0x7F || escCodes[ch] != 0) {
break;
}
outputBuffer[outputPtr++] = (byte) ch;
++offset;
}
_outputTail = outputPtr;
if (offset < len) {
// [JACKSON-106]
if (_characterEscapes != null) {
_writeCustomStringSegment2(cbuf, offset, len);
// [JACKSON-102]
} else if (_maximumNonEscapedChar == 0) {
_writeStringSegment2(cbuf, offset, len);
} else {
_writeStringSegmentASCII2(cbuf, offset, len);
}
}
}
/**
* Secondary method called when content contains characters to escape,
* and/or multi-byte UTF-8 characters.
*/
private void _writeStringSegment2(final char[] cbuf, int offset, final int end)
throws IOException, JsonGenerationException
{
// Ok: caller guarantees buffer can have room; but that may require flushing:
if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
_flushBuffer();
}
int outputPtr = _outputTail;
final byte[] outputBuffer = _outputBuffer;
final int[] escCodes = _outputEscapes;
while (offset < end) {
int ch = cbuf[offset++];
if (ch <= 0x7F) {
if (escCodes[ch] == 0) {
outputBuffer[outputPtr++] = (byte) ch;
continue;
}
int escape = escCodes[ch];
if (escape > 0) { // 2-char escape, fine
outputBuffer[outputPtr++] = BYTE_BACKSLASH;
outputBuffer[outputPtr++] = (byte) escape;
} else {
// ctrl-char, 6-byte escape...
outputPtr = _writeGenericEscape(ch, outputPtr);
}
continue;
}
if (ch <= 0x7FF) { // fine, just needs 2 byte output
outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
} else {
outputPtr = _outputMultiByteChar(ch, outputPtr);
}
}
_outputTail = outputPtr;
}
/*
/**********************************************************
/* Internal methods, low-level writing, text segment
/* with additional escaping (ASCII or such)
/**********************************************************
*/
/**
* Same as <code>_writeStringSegment2(char[], ...)</code., but with
* additional escaping for high-range code points
*/
private void _writeStringSegmentASCII2(final char[] cbuf, int offset, final int end)
throws IOException, JsonGenerationException
{
// Ok: caller guarantees buffer can have room; but that may require flushing:
if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
_flushBuffer();
}
int outputPtr = _outputTail;
final byte[] outputBuffer = _outputBuffer;
final int[] escCodes = _outputEscapes;
final int maxUnescaped = _maximumNonEscapedChar;
while (offset < end) {
int ch = cbuf[offset++];
if (ch <= 0x7F) {
if (escCodes[ch] == 0) {
outputBuffer[outputPtr++] = (byte) ch;
continue;
}
int escape = escCodes[ch];
if (escape > 0) { // 2-char escape, fine
outputBuffer[outputPtr++] = BYTE_BACKSLASH;
outputBuffer[outputPtr++] = (byte) escape;
} else {
// ctrl-char, 6-byte escape...
outputPtr = _writeGenericEscape(ch, outputPtr);
}
continue;
}
if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
outputPtr = _writeGenericEscape(ch, outputPtr);
continue;
}
if (ch <= 0x7FF) { // fine, just needs 2 byte output
outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
} else {
outputPtr = _outputMultiByteChar(ch, outputPtr);
}
}
_outputTail = outputPtr;
}
/*
/**********************************************************
/* Internal methods, low-level writing, text segment
/* with fully custom escaping (and possibly escaping of non-ASCII
/**********************************************************
*/
/**
* Same as <code>_writeStringSegmentASCII2(char[], ...)</code., but with
* additional checking for completely custom escapes
*/
private void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end)
throws IOException, JsonGenerationException
{
// Ok: caller guarantees buffer can have room; but that may require flushing:
if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
_flushBuffer();
}
int outputPtr = _outputTail;
final byte[] outputBuffer = _outputBuffer;
final int[] escCodes = _outputEscapes;
// may or may not have this limit
final int maxUnescaped = (_maximumNonEscapedChar <= 0) ? 0xFFFF : _maximumNonEscapedChar;
final CharacterEscapes customEscapes = _characterEscapes; // non-null
while (offset < end) {
int ch = cbuf[offset++];
if (ch <= 0x7F) {
if (escCodes[ch] == 0) {
outputBuffer[outputPtr++] = (byte) ch;
continue;
}
int escape = escCodes[ch];
if (escape > 0) { // 2-char escape, fine
outputBuffer[outputPtr++] = BYTE_BACKSLASH;
outputBuffer[outputPtr++] = (byte) escape;
} else if (escape == CharacterEscapes.ESCAPE_CUSTOM) {
SerializableString esc = customEscapes.getEscapeSequence(ch);
if (esc == null) {
_reportError("Invalid custom escape definitions; custom escape not found for character code 0x"
+Integer.toHexString(ch)+", although was supposed to have one");
}
outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
} else {
// ctrl-char, 6-byte escape...
outputPtr = _writeGenericEscape(ch, outputPtr);
}
continue;
}
if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
outputPtr = _writeGenericEscape(ch, outputPtr);
continue;
}
SerializableString esc = customEscapes.getEscapeSequence(ch);
if (esc != null) {
outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
continue;
}
if (ch <= 0x7FF) { // fine, just needs 2 byte output
outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
} else {
outputPtr = _outputMultiByteChar(ch, outputPtr);
}
}
_outputTail = outputPtr;
}
private int _writeCustomEscape(byte[] outputBuffer, int outputPtr, SerializableString esc, int remainingChars)
throws IOException, JsonGenerationException
{
byte[] raw = esc.asUnquotedUTF8(); // must be escaped at this point, shouldn't double-quote
int len = raw.length;
if (len > 6) { // may violate constraints we have, do offline
return _handleLongCustomEscape(outputBuffer, outputPtr, _outputEnd, raw, remainingChars);
}
// otherwise will fit without issues, so:
System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
return (outputPtr + len);
}
private int _handleLongCustomEscape(byte[] outputBuffer, int outputPtr, int outputEnd, byte[] raw,
int remainingChars)
throws IOException, JsonGenerationException
{
int len = raw.length;
if ((outputPtr + len) > outputEnd) {
_outputTail = outputPtr;
_flushBuffer();
outputPtr = _outputTail;
if (len > outputBuffer.length) { // very unlikely, but possible...
_outputStream.write(raw, 0, len);
return outputPtr;
}
System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
outputPtr += len;
}
// but is the invariant still obeyed? If not, flush once more
if ((outputPtr + 6 * remainingChars) > outputEnd) {
_flushBuffer();
return _outputTail;
}
return outputPtr;
}
/*
/**********************************************************
/* Internal methods, low-level writing, "raw UTF-8" segments
/**********************************************************
*/
/**
* Method called when UTF-8 encoded (but NOT yet escaped!) content is not guaranteed
* to fit in the output buffer after escaping; as such, we just need to
* chunk writes.
*/
private void _writeUTF8Segments(byte[] utf8, int offset, int totalLen)
throws IOException, JsonGenerationException
{
do {
int len = Math.min(_outputMaxContiguous, totalLen);
_writeUTF8Segment(utf8, offset, len);
offset += len;
totalLen -= len;
} while (totalLen > 0);
}
private void _writeUTF8Segment(byte[] utf8, final int offset, final int len)
throws IOException, JsonGenerationException
{
// fast loop to see if escaping is needed; don't copy, just look
final int[] escCodes = _outputEscapes;
for (int ptr = offset, end = offset + len; ptr < end; ) {
// 28-Feb-2011, tatu: escape codes just cover 7-bit range, so:
int ch = utf8[ptr++];
if ((ch >= 0) && escCodes[ch] != 0) {
_writeUTF8Segment2(utf8, offset, len);
return;
}
}
// yes, fine, just copy the sucker
if ((_outputTail + len) > _outputEnd) { // enough room or need to flush?
_flushBuffer(); // but yes once we flush (caller guarantees length restriction)
}
System.arraycopy(utf8, offset, _outputBuffer, _outputTail, len);
_outputTail += len;
}
private void _writeUTF8Segment2(final byte[] utf8, int offset, int len)
throws IOException, JsonGenerationException
{
int outputPtr = _outputTail;
// Ok: caller guarantees buffer can have room; but that may require flushing:
if ((outputPtr + (len * 6)) > _outputEnd) {
_flushBuffer();
outputPtr = _outputTail;
}
final byte[] outputBuffer = _outputBuffer;
final int[] escCodes = _outputEscapes;
len += offset; // so 'len' becomes 'end'
while (offset < len) {
byte b = utf8[offset++];
int ch = b;
if (ch < 0 || escCodes[ch] == 0) {
outputBuffer[outputPtr++] = b;
continue;
}
int escape = escCodes[ch];
if (escape > 0) { // 2-char escape, fine
outputBuffer[outputPtr++] = BYTE_BACKSLASH;
outputBuffer[outputPtr++] = (byte) escape;
} else {
// ctrl-char, 6-byte escape...
outputPtr = _writeGenericEscape(ch, outputPtr);
}
}
_outputTail = outputPtr;
}
/*
/**********************************************************
/* Internal methods, low-level writing, base64 encoded
/**********************************************************
*/
protected void _writeBinary(Base64Variant b64variant,
byte[] input, int inputPtr, final int inputEnd)
throws IOException, JsonGenerationException
{
// Encoding is by chunks of 3 input, 4 output chars, so:
int safeInputEnd = inputEnd - 3;
// Let's also reserve room for possible (and quoted) lf char each round
int safeOutputEnd = _outputEnd - 6;
int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
// Ok, first we loop through all full triplets of data:
while (inputPtr <= safeInputEnd) {
if (_outputTail > safeOutputEnd) { // need to flush
_flushBuffer();
}
// First, mash 3 bytes into lsb of 32-bit int
int b24 = ((int) input[inputPtr++]) << 8;
b24 |= ((int) input[inputPtr++]) & 0xFF;
b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
_outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
if (--chunksBeforeLF <= 0) {
// note: must quote in JSON value
_outputBuffer[_outputTail++] = '\\';
_outputBuffer[_outputTail++] = 'n';
chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
}
}
// And then we may have 1 or 2 leftover bytes to encode
int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
if (inputLeft > 0) { // yes, but do we have room for output?
if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
_flushBuffer();
}
int b24 = ((int) input[inputPtr++]) << 16;
if (inputLeft == 2) {
b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
}
_outputTail = b64variant.encodeBase64Partial(b24, inputLeft, _outputBuffer, _outputTail);
}
}
// write-method called when length is definitely known
protected int _writeBinary(Base64Variant b64variant,
InputStream data, byte[] readBuffer, int bytesLeft)
throws IOException, JsonGenerationException
{
int inputPtr = 0;
int inputEnd = 0;
int lastFullOffset = -3;
// Let's also reserve room for possible (and quoted) LF char each round
int safeOutputEnd = _outputEnd - 6;
int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
while (bytesLeft > 2) { // main loop for full triplets
if (inputPtr > lastFullOffset) {
inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
inputPtr = 0;
if (inputEnd < 3) { // required to try to read to have at least 3 bytes
break;
}
lastFullOffset = inputEnd-3;
}
if (_outputTail > safeOutputEnd) { // need to flush
_flushBuffer();
}
int b24 = ((int) readBuffer[inputPtr++]) << 8;
b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
bytesLeft -= 3;
_outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
if (--chunksBeforeLF <= 0) {
_outputBuffer[_outputTail++] = '\\';
_outputBuffer[_outputTail++] = 'n';
chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
}
}
// And then we may have 1 or 2 leftover bytes to encode
if (bytesLeft > 0) {
inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
inputPtr = 0;
if (inputEnd > 0) { // yes, but do we have room for output?
if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
_flushBuffer();
}
int b24 = ((int) readBuffer[inputPtr++]) << 16;
int amount;
if (inputPtr < inputEnd) {
b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
amount = 2;
} else {
amount = 1;
}
_outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
bytesLeft -= amount;
}
}
return bytesLeft;
}
// write method when length is unknown
protected int _writeBinary(Base64Variant b64variant,
InputStream data, byte[] readBuffer)
throws IOException, JsonGenerationException
{
int inputPtr = 0;
int inputEnd = 0;
int lastFullOffset = -3;
int bytesDone = 0;
// Let's also reserve room for possible (and quoted) LF char each round
int safeOutputEnd = _outputEnd - 6;
int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
// Ok, first we loop through all full triplets of data:
while (true) {
if (inputPtr > lastFullOffset) { // need to load more
inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, readBuffer.length);
inputPtr = 0;
if (inputEnd < 3) { // required to try to read to have at least 3 bytes
break;
}
lastFullOffset = inputEnd-3;
}
if (_outputTail > safeOutputEnd) { // need to flush
_flushBuffer();
}
// First, mash 3 bytes into lsb of 32-bit int
int b24 = ((int) readBuffer[inputPtr++]) << 8;
b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
bytesDone += 3;
_outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
if (--chunksBeforeLF <= 0) {
_outputBuffer[_outputTail++] = '\\';
_outputBuffer[_outputTail++] = 'n';
chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
}
}
// And then we may have 1 or 2 leftover bytes to encode
if (inputPtr < inputEnd) { // yes, but do we have room for output?
if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
_flushBuffer();
}
int b24 = ((int) readBuffer[inputPtr++]) << 16;
int amount = 1;
if (inputPtr < inputEnd) {
b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
amount = 2;
}
bytesDone += amount;
_outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
}
return bytesDone;
}
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 {
int count = in.read(readBuffer, inputEnd, maxRead - inputEnd);
if (count < 0) {
return inputEnd;
}
inputEnd += count;
} while (inputEnd < 3);
return inputEnd;
}
/*
/**********************************************************
/* Internal methods, character escapes/encoding
/**********************************************************
*/
/**
* Method called to output a character that is beyond range of
* 1- and 2-byte UTF-8 encodings, when outputting "raw"
* text (meaning it is not to be escaped or quoted)
*/
private int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputLen)
throws IOException
{
// Let's handle surrogates gracefully (as 4 byte output):
if (ch >= SURR1_FIRST) {
if (ch <= SURR2_LAST) { // yes, outside of BMP
// Do we have second part?
if (inputOffset >= inputLen) { // nope... have to note down
_reportError("Split surrogate on writeRaw() input (last character)");
}
_outputSurrogates(ch, cbuf[inputOffset]);
return (inputOffset+1);
}
}
final byte[] bbuf = _outputBuffer;
bbuf[_outputTail++] = (byte) (0xe0 | (ch >> 12));
bbuf[_outputTail++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
return inputOffset;
}
protected final void _outputSurrogates(int surr1, int surr2)
throws IOException
{
int c = _decodeSurrogate(surr1, surr2);
if ((_outputTail + 4) > _outputEnd) {
_flushBuffer();
}
final byte[] bbuf = _outputBuffer;
bbuf[_outputTail++] = (byte) (0xf0 | (c >> 18));
bbuf[_outputTail++] = (byte) (0x80 | ((c >> 12) & 0x3f));
bbuf[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
bbuf[_outputTail++] = (byte) (0x80 | (c & 0x3f));
}
/**
*
* @param ch
* @param outputPtr Position within output buffer to append multi-byte in
*
* @return New output position after appending
*
* @throws IOException
*/
private int _outputMultiByteChar(int ch, int outputPtr)
throws IOException
{
byte[] bbuf = _outputBuffer;
if (ch >= SURR1_FIRST && ch <= SURR2_LAST) { // yes, outside of BMP; add an escape
bbuf[outputPtr++] = BYTE_BACKSLASH;
bbuf[outputPtr++] = BYTE_u;
bbuf[outputPtr++] = HEX_CHARS[(ch >> 12) & 0xF];
bbuf[outputPtr++] = HEX_CHARS[(ch >> 8) & 0xF];
bbuf[outputPtr++] = HEX_CHARS[(ch >> 4) & 0xF];
bbuf[outputPtr++] = HEX_CHARS[ch & 0xF];
} else {
bbuf[outputPtr++] = (byte) (0xe0 | (ch >> 12));
bbuf[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
bbuf[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
}
return outputPtr;
}
protected final int _decodeSurrogate(int surr1, int surr2) throws IOException
{
// First is known to be valid, but how about the other?
if (surr2 < SURR2_FIRST || surr2 > SURR2_LAST) {
String msg = "Incomplete surrogate pair: first char 0x"+Integer.toHexString(surr1)+", second 0x"+Integer.toHexString(surr2);
_reportError(msg);
}
int c = 0x10000 + ((surr1 - SURR1_FIRST) << 10) + (surr2 - SURR2_FIRST);
return c;
}
private void _writeNull() throws IOException
{
if ((_outputTail + 4) >= _outputEnd) {
_flushBuffer();
}
System.arraycopy(NULL_BYTES, 0, _outputBuffer, _outputTail, 4);
_outputTail += 4;
}
/**
* Method called to write a generic Unicode escape for given character.
*
* @param charToEscape Character to escape using escape sequence (\\uXXXX)
*/
private int _writeGenericEscape(int charToEscape, int outputPtr)
throws IOException
{
final byte[] bbuf = _outputBuffer;
bbuf[outputPtr++] = BYTE_BACKSLASH;
bbuf[outputPtr++] = BYTE_u;
if (charToEscape > 0xFF) {
int hi = (charToEscape >> 8) & 0xFF;
bbuf[outputPtr++] = HEX_CHARS[hi >> 4];
bbuf[outputPtr++] = HEX_CHARS[hi & 0xF];
charToEscape &= 0xFF;
} else {
bbuf[outputPtr++] = BYTE_0;
bbuf[outputPtr++] = BYTE_0;
}
// We know it's a control char, so only the last 2 chars are non-0
bbuf[outputPtr++] = HEX_CHARS[charToEscape >> 4];
bbuf[outputPtr++] = HEX_CHARS[charToEscape & 0xF];
return outputPtr;
}
protected final void _flushBuffer() throws IOException
{
int len = _outputTail;
if (len > 0) {
_outputTail = 0;
_outputStream.write(_outputBuffer, 0, len);
}
}
}