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); } } }