blob: 3575d0103d8df84f3372bede0bc0ae1a758175b2 [file] [log] [blame]
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001package com.fasterxml.jackson.core.json;
2
3import java.io.*;
4import java.math.BigDecimal;
5import java.math.BigInteger;
6
7import com.fasterxml.jackson.core.*;
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -08008import com.fasterxml.jackson.core.io.*;
Tatu Salorantaf15531c2011-12-22 23:00:40 -08009
10public class UTF8JsonGenerator
Tatu Salorantae6dfc692012-09-28 15:34:05 -070011 extends JsonGeneratorImpl
Tatu Salorantaf15531c2011-12-22 23:00:40 -080012{
13 private final static byte BYTE_u = (byte) 'u';
14
15 private final static byte BYTE_0 = (byte) '0';
16
17 private final static byte BYTE_LBRACKET = (byte) '[';
18 private final static byte BYTE_RBRACKET = (byte) ']';
19 private final static byte BYTE_LCURLY = (byte) '{';
20 private final static byte BYTE_RCURLY = (byte) '}';
21
22 private final static byte BYTE_BACKSLASH = (byte) '\\';
Tatu Salorantaf15531c2011-12-22 23:00:40 -080023 private final static byte BYTE_COMMA = (byte) ',';
24 private final static byte BYTE_COLON = (byte) ':';
25 private final static byte BYTE_QUOTE = (byte) '"';
26
27 protected final static int SURR1_FIRST = 0xD800;
28 protected final static int SURR1_LAST = 0xDBFF;
29 protected final static int SURR2_FIRST = 0xDC00;
30 protected final static int SURR2_LAST = 0xDFFF;
31
32 // intermediate copies only made up to certain length...
33 private final static int MAX_BYTES_TO_BUFFER = 512;
34
35 final static byte[] HEX_CHARS = CharTypes.copyHexBytes();
36
37 private final static byte[] NULL_BYTES = { 'n', 'u', 'l', 'l' };
38 private final static byte[] TRUE_BYTES = { 't', 'r', 'u', 'e' };
39 private final static byte[] FALSE_BYTES = { 'f', 'a', 'l', 's', 'e' };
40
Tatu Salorantaf15531c2011-12-22 23:00:40 -080041 /*
42 /**********************************************************
43 /* Output buffering
44 /**********************************************************
45 */
Tatu Salorantae6dfc692012-09-28 15:34:05 -070046
47 /**
48 * Underlying output stream used for writing JSON content.
49 */
50 final protected OutputStream _outputStream;
Tatu Salorantaf15531c2011-12-22 23:00:40 -080051
52 /**
53 * Intermediate buffer in which contents are buffered before
54 * being written using {@link #_outputStream}.
55 */
56 protected byte[] _outputBuffer;
57
58 /**
59 * Pointer to the position right beyond the last character to output
60 * (end marker; may be past the buffer)
61 */
62 protected int _outputTail = 0;
63
64 /**
65 * End marker of the output buffer; one past the last valid position
66 * within the buffer.
67 */
68 protected final int _outputEnd;
69
70 /**
71 * Maximum number of <code>char</code>s that we know will always fit
72 * in the output buffer after escaping
73 */
74 protected final int _outputMaxContiguous;
75
76 /**
77 * Intermediate buffer in which characters of a String are copied
78 * before being encoded.
79 */
80 protected char[] _charBuffer;
81
82 /**
83 * Length of <code>_charBuffer</code>
84 */
85 protected final int _charBufferLength;
86
87 /**
88 * 6 character temporary buffer allocated if needed, for constructing
89 * escape sequences
90 */
91 protected byte[] _entityBuffer;
92
93 /**
94 * Flag that indicates whether the output buffer is recycable (and
95 * needs to be returned to recycler once we are done) or not.
96 */
97 protected boolean _bufferRecyclable;
98
99 /*
100 /**********************************************************
101 /* Life-cycle
102 /**********************************************************
103 */
104
105 public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
106 OutputStream out)
107 {
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700108 super(ctxt, features, codec);
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800109 _outputStream = out;
110 _bufferRecyclable = true;
111 _outputBuffer = ctxt.allocWriteEncodingBuffer();
112 _outputEnd = _outputBuffer.length;
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700113
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800114 /* To be exact, each char can take up to 6 bytes when escaped (Unicode
115 * escape with backslash, 'u' and 4 hex digits); but to avoid fluctuation,
116 * we will actually round down to only do up to 1/8 number of chars
117 */
118 _outputMaxContiguous = _outputEnd >> 3;
119 _charBuffer = ctxt.allocConcatBuffer();
120 _charBufferLength = _charBuffer.length;
121
122 // By default we use this feature to determine additional quoting
123 if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
124 setHighestNonEscapedChar(127);
125 }
126 }
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700127
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800128 public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec,
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700129 OutputStream out,
130 byte[] outputBuffer, int outputOffset, boolean bufferRecyclable)
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800131 {
132
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700133 super(ctxt, features, codec);
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800134 _outputStream = out;
135 _bufferRecyclable = bufferRecyclable;
136 _outputTail = outputOffset;
137 _outputBuffer = outputBuffer;
138 _outputEnd = _outputBuffer.length;
139 // up to 6 bytes per char (see above), rounded up to 1/8
140 _outputMaxContiguous = _outputEnd >> 3;
141 _charBuffer = ctxt.allocConcatBuffer();
142 _charBufferLength = _charBuffer.length;
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800143 }
144
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800145 /*
146 /**********************************************************
147 /* Overridden configuration methods
148 /**********************************************************
149 */
150
151 @Override
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800152 public Object getOutputTarget() {
153 return _outputStream;
154 }
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700155
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800156 /*
157 /**********************************************************
158 /* Overridden methods
159 /**********************************************************
160 */
161
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800162 @Override
163 public final void writeFieldName(String name) throws IOException, JsonGenerationException
164 {
165 int status = _writeContext.writeFieldName(name);
166 if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
167 _reportError("Can not write a field name, expecting a value");
168 }
169 if (_cfgPrettyPrinter != null) {
170 _writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
171 return;
172 }
173 if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) { // need comma
174 if (_outputTail >= _outputEnd) {
175 _flushBuffer();
176 }
177 _outputBuffer[_outputTail++] = BYTE_COMMA;
178 }
179 _writeFieldName(name);
180 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800181
182 @Override
183 public final void writeFieldName(SerializableString name)
184 throws IOException, JsonGenerationException
185 {
186 // Object is a value, need to verify it's allowed
187 int status = _writeContext.writeFieldName(name.getValue());
188 if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
189 _reportError("Can not write a field name, expecting a value");
190 }
191 if (_cfgPrettyPrinter != null) {
192 _writePPFieldName(name, (status == JsonWriteContext.STATUS_OK_AFTER_COMMA));
193 return;
194 }
195 if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
196 if (_outputTail >= _outputEnd) {
197 _flushBuffer();
198 }
199 _outputBuffer[_outputTail++] = BYTE_COMMA;
200 }
201 _writeFieldName(name);
202 }
203
204 /*
205 /**********************************************************
206 /* Output method implementations, structural
207 /**********************************************************
208 */
209
210 @Override
211 public final void writeStartArray() throws IOException, JsonGenerationException
212 {
213 _verifyValueWrite("start an array");
214 _writeContext = _writeContext.createChildArrayContext();
215 if (_cfgPrettyPrinter != null) {
216 _cfgPrettyPrinter.writeStartArray(this);
217 } else {
218 if (_outputTail >= _outputEnd) {
219 _flushBuffer();
220 }
221 _outputBuffer[_outputTail++] = BYTE_LBRACKET;
222 }
223 }
224
225 @Override
226 public final void writeEndArray() throws IOException, JsonGenerationException
227 {
228 if (!_writeContext.inArray()) {
229 _reportError("Current context not an ARRAY but "+_writeContext.getTypeDesc());
230 }
231 if (_cfgPrettyPrinter != null) {
232 _cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
233 } else {
234 if (_outputTail >= _outputEnd) {
235 _flushBuffer();
236 }
237 _outputBuffer[_outputTail++] = BYTE_RBRACKET;
238 }
239 _writeContext = _writeContext.getParent();
240 }
241
242 @Override
243 public final void writeStartObject() throws IOException, JsonGenerationException
244 {
245 _verifyValueWrite("start an object");
246 _writeContext = _writeContext.createChildObjectContext();
247 if (_cfgPrettyPrinter != null) {
248 _cfgPrettyPrinter.writeStartObject(this);
249 } else {
250 if (_outputTail >= _outputEnd) {
251 _flushBuffer();
252 }
253 _outputBuffer[_outputTail++] = BYTE_LCURLY;
254 }
255 }
256
257 @Override
258 public final void writeEndObject() throws IOException, JsonGenerationException
259 {
260 if (!_writeContext.inObject()) {
261 _reportError("Current context not an object but "+_writeContext.getTypeDesc());
262 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800263 if (_cfgPrettyPrinter != null) {
264 _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
265 } else {
266 if (_outputTail >= _outputEnd) {
267 _flushBuffer();
268 }
269 _outputBuffer[_outputTail++] = BYTE_RCURLY;
270 }
Tatu Saloranta54377382012-06-11 23:07:02 -0700271 _writeContext = _writeContext.getParent();
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800272 }
273
274 protected final void _writeFieldName(String name)
275 throws IOException, JsonGenerationException
276 {
277 /* To support [JACKSON-46], we'll do this:
278 * (Question: should quoting of spaces (etc) still be enabled?)
279 */
280 if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
281 _writeStringSegments(name);
282 return;
283 }
284 if (_outputTail >= _outputEnd) {
285 _flushBuffer();
286 }
287 _outputBuffer[_outputTail++] = BYTE_QUOTE;
288 // The beef:
289 final int len = name.length();
290 if (len <= _charBufferLength) { // yes, fits right in
291 name.getChars(0, len, _charBuffer, 0);
292 // But as one segment, or multiple?
293 if (len <= _outputMaxContiguous) {
294 if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
295 _flushBuffer();
296 }
297 _writeStringSegment(_charBuffer, 0, len);
298 } else {
299 _writeStringSegments(_charBuffer, 0, len);
300 }
301 } else {
302 _writeStringSegments(name);
303 }
304
305 // and closing quotes; need room for one more char:
306 if (_outputTail >= _outputEnd) {
307 _flushBuffer();
308 }
309 _outputBuffer[_outputTail++] = BYTE_QUOTE;
310 }
311
312 protected final void _writeFieldName(SerializableString name)
313 throws IOException, JsonGenerationException
314 {
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800315 if (!isEnabled(Feature.QUOTE_FIELD_NAMES)) {
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800316 int len = name.appendQuotedUTF8(_outputBuffer, _outputTail); // different quoting (escaping)
317 if (len < 0) {
318 _writeBytes(name.asQuotedUTF8());
319 } else {
320 _outputTail += len;
321 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800322 return;
323 }
324 if (_outputTail >= _outputEnd) {
325 _flushBuffer();
326 }
327 _outputBuffer[_outputTail++] = BYTE_QUOTE;
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800328 int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
329 if (len < 0) { // couldn't append, bit longer processing
330 _writeBytes(name.asQuotedUTF8());
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800331 } else {
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800332 _outputTail += len;
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800333 }
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800334 if (_outputTail >= _outputEnd) {
335 _flushBuffer();
336 }
337 _outputBuffer[_outputTail++] = BYTE_QUOTE;
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800338 }
339
340 /**
341 * Specialized version of <code>_writeFieldName</code>, off-lined
342 * to keep the "fast path" as simple (and hopefully fast) as possible.
343 */
344 protected final void _writePPFieldName(String name, boolean commaBefore)
345 throws IOException, JsonGenerationException
346 {
347 if (commaBefore) {
348 _cfgPrettyPrinter.writeObjectEntrySeparator(this);
349 } else {
350 _cfgPrettyPrinter.beforeObjectEntries(this);
351 }
352
353 if (isEnabled(Feature.QUOTE_FIELD_NAMES)) { // standard
354 if (_outputTail >= _outputEnd) {
355 _flushBuffer();
356 }
357 _outputBuffer[_outputTail++] = BYTE_QUOTE;
358 final int len = name.length();
359 if (len <= _charBufferLength) { // yes, fits right in
360 name.getChars(0, len, _charBuffer, 0);
361 // But as one segment, or multiple?
362 if (len <= _outputMaxContiguous) {
363 if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
364 _flushBuffer();
365 }
366 _writeStringSegment(_charBuffer, 0, len);
367 } else {
368 _writeStringSegments(_charBuffer, 0, len);
369 }
370 } else {
371 _writeStringSegments(name);
372 }
373 if (_outputTail >= _outputEnd) {
374 _flushBuffer();
375 }
376 _outputBuffer[_outputTail++] = BYTE_QUOTE;
377 } else { // non-standard, omit quotes
378 _writeStringSegments(name);
379 }
380 }
381
382 protected final void _writePPFieldName(SerializableString name, boolean commaBefore)
383 throws IOException, JsonGenerationException
384 {
385 if (commaBefore) {
386 _cfgPrettyPrinter.writeObjectEntrySeparator(this);
387 } else {
388 _cfgPrettyPrinter.beforeObjectEntries(this);
389 }
390
391 boolean addQuotes = isEnabled(Feature.QUOTE_FIELD_NAMES); // standard
392 if (addQuotes) {
393 if (_outputTail >= _outputEnd) {
394 _flushBuffer();
395 }
396 _outputBuffer[_outputTail++] = BYTE_QUOTE;
397 }
398 _writeBytes(name.asQuotedUTF8());
399 if (addQuotes) {
400 if (_outputTail >= _outputEnd) {
401 _flushBuffer();
402 }
403 _outputBuffer[_outputTail++] = BYTE_QUOTE;
404 }
405 }
406
407 /*
408 /**********************************************************
409 /* Output method implementations, textual
410 /**********************************************************
411 */
412
413 @Override
414 public void writeString(String text)
415 throws IOException, JsonGenerationException
416 {
417 _verifyValueWrite("write text value");
418 if (text == null) {
419 _writeNull();
420 return;
421 }
422 // First: can we make a local copy of chars that make up text?
423 final int len = text.length();
424 if (len > _charBufferLength) { // nope: off-line handling
425 _writeLongString(text);
426 return;
427 }
428 // yes: good.
429 text.getChars(0, len, _charBuffer, 0);
430 // Output: if we can't guarantee it fits in output buffer, off-line as well:
431 if (len > _outputMaxContiguous) {
432 _writeLongString(_charBuffer, 0, len);
433 return;
434 }
435 if ((_outputTail + len) >= _outputEnd) {
436 _flushBuffer();
437 }
438 _outputBuffer[_outputTail++] = BYTE_QUOTE;
439 _writeStringSegment(_charBuffer, 0, len); // we checked space already above
440 /* [JACKSON-462] But that method may have had to expand multi-byte Unicode
441 * chars, so we must check again
442 */
443 if (_outputTail >= _outputEnd) {
444 _flushBuffer();
445 }
446 _outputBuffer[_outputTail++] = BYTE_QUOTE;
447 }
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800448
Francis Galiegue6c842ae2012-09-29 11:24:17 +0200449 private void _writeLongString(String text)
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800450 throws IOException, JsonGenerationException
451 {
452 if (_outputTail >= _outputEnd) {
453 _flushBuffer();
454 }
455 _outputBuffer[_outputTail++] = BYTE_QUOTE;
456 _writeStringSegments(text);
457 if (_outputTail >= _outputEnd) {
458 _flushBuffer();
459 }
460 _outputBuffer[_outputTail++] = BYTE_QUOTE;
461 }
462
Francis Galiegue6c842ae2012-09-29 11:24:17 +0200463 private void _writeLongString(char[] text, int offset, int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800464 throws IOException, JsonGenerationException
465 {
466 if (_outputTail >= _outputEnd) {
467 _flushBuffer();
468 }
469 _outputBuffer[_outputTail++] = BYTE_QUOTE;
470 _writeStringSegments(_charBuffer, 0, len);
471 if (_outputTail >= _outputEnd) {
472 _flushBuffer();
473 }
474 _outputBuffer[_outputTail++] = BYTE_QUOTE;
475 }
476
477 @Override
478 public void writeString(char[] text, int offset, int len)
479 throws IOException, JsonGenerationException
480 {
481 _verifyValueWrite("write text value");
482 if (_outputTail >= _outputEnd) {
483 _flushBuffer();
484 }
485 _outputBuffer[_outputTail++] = BYTE_QUOTE;
486 // One or multiple segments?
487 if (len <= _outputMaxContiguous) {
488 if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
489 _flushBuffer();
490 }
491 _writeStringSegment(text, offset, len);
492 } else {
493 _writeStringSegments(text, offset, len);
494 }
495 // And finally, closing quotes
496 if (_outputTail >= _outputEnd) {
497 _flushBuffer();
498 }
499 _outputBuffer[_outputTail++] = BYTE_QUOTE;
500 }
501
502 @Override
503 public final void writeString(SerializableString text)
504 throws IOException, JsonGenerationException
505 {
506 _verifyValueWrite("write text value");
507 if (_outputTail >= _outputEnd) {
508 _flushBuffer();
509 }
510 _outputBuffer[_outputTail++] = BYTE_QUOTE;
Tatu Saloranta9b59c3b2012-01-17 22:28:20 -0800511 int len = text.appendQuotedUTF8(_outputBuffer, _outputTail);
512 if (len < 0) {
513 _writeBytes(text.asQuotedUTF8());
514 } else {
515 _outputTail += len;
516 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800517 if (_outputTail >= _outputEnd) {
518 _flushBuffer();
519 }
520 _outputBuffer[_outputTail++] = BYTE_QUOTE;
521 }
522
523 @Override
524 public void writeRawUTF8String(byte[] text, int offset, int length)
525 throws IOException, JsonGenerationException
526 {
527 _verifyValueWrite("write text value");
528 if (_outputTail >= _outputEnd) {
529 _flushBuffer();
530 }
531 _outputBuffer[_outputTail++] = BYTE_QUOTE;
532 _writeBytes(text, offset, length);
533 if (_outputTail >= _outputEnd) {
534 _flushBuffer();
535 }
536 _outputBuffer[_outputTail++] = BYTE_QUOTE;
537 }
538
539 @Override
540 public void writeUTF8String(byte[] text, int offset, int len)
541 throws IOException, JsonGenerationException
542 {
543 _verifyValueWrite("write text value");
544 if (_outputTail >= _outputEnd) {
545 _flushBuffer();
546 }
547 _outputBuffer[_outputTail++] = BYTE_QUOTE;
548 // One or multiple segments?
549 if (len <= _outputMaxContiguous) {
550 _writeUTF8Segment(text, offset, len);
551 } else {
552 _writeUTF8Segments(text, offset, len);
553 }
554 if (_outputTail >= _outputEnd) {
555 _flushBuffer();
556 }
557 _outputBuffer[_outputTail++] = BYTE_QUOTE;
558 }
559
560 /*
561 /**********************************************************
562 /* Output method implementations, unprocessed ("raw")
563 /**********************************************************
564 */
565
566 @Override
567 public void writeRaw(String text)
568 throws IOException, JsonGenerationException
569 {
570 int start = 0;
571 int len = text.length();
572 while (len > 0) {
573 char[] buf = _charBuffer;
574 final int blen = buf.length;
575 final int len2 = (len < blen) ? len : blen;
576 text.getChars(start, start+len2, buf, 0);
577 writeRaw(buf, 0, len2);
578 start += len2;
579 len -= len2;
580 }
581 }
582
583 @Override
584 public void writeRaw(String text, int offset, int len)
585 throws IOException, JsonGenerationException
586 {
587 while (len > 0) {
588 char[] buf = _charBuffer;
589 final int blen = buf.length;
590 final int len2 = (len < blen) ? len : blen;
591 text.getChars(offset, offset+len2, buf, 0);
592 writeRaw(buf, 0, len2);
593 offset += len2;
594 len -= len2;
595 }
596 }
597
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700598 @Override
599 public void writeRaw(SerializableString text) throws IOException, JsonGenerationException
600 {
601 byte[] raw = text.asUnquotedUTF8();
602 if (raw.length > 0) {
603 _writeBytes(raw);
604 }
605 }
606
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800607 // @TODO: rewrite for speed...
608 @Override
609 public final void writeRaw(char[] cbuf, int offset, int len)
610 throws IOException, JsonGenerationException
611 {
612 // First: if we have 3 x charCount spaces, we know it'll fit just fine
613 {
614 int len3 = len+len+len;
615 if ((_outputTail + len3) > _outputEnd) {
616 // maybe we could flush?
617 if (_outputEnd < len3) { // wouldn't be enough...
618 _writeSegmentedRaw(cbuf, offset, len);
619 return;
620 }
621 // yes, flushing brings enough space
622 _flushBuffer();
623 }
624 }
625 len += offset; // now marks the end
626
627 // Note: here we know there is enough room, hence no output boundary checks
628 main_loop:
629 while (offset < len) {
630 inner_loop:
631 while (true) {
632 int ch = (int) cbuf[offset];
633 if (ch > 0x7F) {
634 break inner_loop;
635 }
636 _outputBuffer[_outputTail++] = (byte) ch;
637 if (++offset >= len) {
638 break main_loop;
639 }
640 }
641 char ch = cbuf[offset++];
642 if (ch < 0x800) { // 2-byte?
643 _outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6));
644 _outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
645 } else {
646 _outputRawMultiByteChar(ch, cbuf, offset, len);
647 }
648 }
649 }
650
651 @Override
652 public void writeRaw(char ch)
653 throws IOException, JsonGenerationException
654 {
655 if ((_outputTail + 3) >= _outputEnd) {
656 _flushBuffer();
657 }
658 final byte[] bbuf = _outputBuffer;
659 if (ch <= 0x7F) {
660 bbuf[_outputTail++] = (byte) ch;
661 } else if (ch < 0x800) { // 2-byte?
662 bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
663 bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
664 } else {
665 _outputRawMultiByteChar(ch, null, 0, 0);
666 }
667 }
668
669 /**
670 * Helper method called when it is possible that output of raw section
671 * to output may cross buffer boundary
672 */
Tatu Saloranta75273be2012-11-11 15:42:06 -0800673 private final void _writeSegmentedRaw(char[] cbuf, int offset, int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800674 throws IOException, JsonGenerationException
675 {
676 final int end = _outputEnd;
677 final byte[] bbuf = _outputBuffer;
678
679 main_loop:
680 while (offset < len) {
681 inner_loop:
682 while (true) {
683 int ch = (int) cbuf[offset];
684 if (ch >= 0x80) {
685 break inner_loop;
686 }
687 // !!! TODO: fast(er) writes (roll input, output checks in one)
688 if (_outputTail >= end) {
689 _flushBuffer();
690 }
691 bbuf[_outputTail++] = (byte) ch;
692 if (++offset >= len) {
693 break main_loop;
694 }
695 }
696 if ((_outputTail + 3) >= _outputEnd) {
697 _flushBuffer();
698 }
699 char ch = cbuf[offset++];
700 if (ch < 0x800) { // 2-byte?
701 bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
702 bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
703 } else {
704 _outputRawMultiByteChar(ch, cbuf, offset, len);
705 }
706 }
707 }
708
709 /*
710 /**********************************************************
711 /* Output method implementations, base64-encoded binary
712 /**********************************************************
713 */
714
715 @Override
Tatu Saloranta2e70a2a2012-06-04 22:21:45 -0700716 public void writeBinary(Base64Variant b64variant,
717 byte[] data, int offset, int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800718 throws IOException, JsonGenerationException
719 {
720 _verifyValueWrite("write binary value");
721 // Starting quotes
722 if (_outputTail >= _outputEnd) {
723 _flushBuffer();
724 }
725 _outputBuffer[_outputTail++] = BYTE_QUOTE;
726 _writeBinary(b64variant, data, offset, offset+len);
727 // and closing quotes
728 if (_outputTail >= _outputEnd) {
729 _flushBuffer();
730 }
731 _outputBuffer[_outputTail++] = BYTE_QUOTE;
732 }
Tatu Saloranta2e70a2a2012-06-04 22:21:45 -0700733
734 @Override
735 public int writeBinary(Base64Variant b64variant,
736 InputStream data, int dataLength)
737 throws IOException, JsonGenerationException
738 {
739 _verifyValueWrite("write binary value");
740 // Starting quotes
741 if (_outputTail >= _outputEnd) {
742 _flushBuffer();
743 }
744 _outputBuffer[_outputTail++] = BYTE_QUOTE;
745 byte[] encodingBuffer = _ioContext.allocBase64Buffer();
746 int bytes;
747 try {
Tatu Saloranta63ff5742012-06-08 21:30:30 -0700748 if (dataLength < 0) { // length unknown
749 bytes = _writeBinary(b64variant, data, encodingBuffer);
750 } else {
751 int missing = _writeBinary(b64variant, data, encodingBuffer, dataLength);
752 if (missing > 0) {
753 _reportError("Too few bytes available: missing "+missing+" bytes (out of "+dataLength+")");
754 }
755 bytes = dataLength;
756 }
Tatu Saloranta2e70a2a2012-06-04 22:21:45 -0700757 } finally {
758 _ioContext.releaseBase64Buffer(encodingBuffer);
759 }
760 // and closing quotes
761 if (_outputTail >= _outputEnd) {
762 _flushBuffer();
763 }
764 _outputBuffer[_outputTail++] = BYTE_QUOTE;
765 return bytes;
766 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800767
768 /*
769 /**********************************************************
770 /* Output method implementations, primitive
771 /**********************************************************
772 */
773
774 @Override
Martin Steiger7190d202013-04-13 12:55:32 +0200775 public void writeNumber(short s)
776 throws IOException, JsonGenerationException
777 {
778 _verifyValueWrite("write number");
779 // up to 5 digits and possible minus sign
780 if ((_outputTail + 6) >= _outputEnd) {
781 _flushBuffer();
782 }
783 if (_cfgNumbersAsStrings) {
784 _writeQuotedShort(s);
785 return;
786 }
787 _outputTail = NumberOutput.outputShort(s, _outputBuffer, _outputTail);
788 }
789
790 private void _writeQuotedShort(short s) throws IOException {
791 if ((_outputTail + 8) >= _outputEnd) {
792 _flushBuffer();
793 }
794 _outputBuffer[_outputTail++] = BYTE_QUOTE;
795 _outputTail = NumberOutput.outputShort(s, _outputBuffer, _outputTail);
796 _outputBuffer[_outputTail++] = BYTE_QUOTE;
797 }
798
799 @Override
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800800 public void writeNumber(int i)
801 throws IOException, JsonGenerationException
802 {
803 _verifyValueWrite("write number");
804 // up to 10 digits and possible minus sign
805 if ((_outputTail + 11) >= _outputEnd) {
806 _flushBuffer();
807 }
808 if (_cfgNumbersAsStrings) {
809 _writeQuotedInt(i);
810 return;
811 }
812 _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
813 }
814
Francis Galiegue6c842ae2012-09-29 11:24:17 +0200815 private void _writeQuotedInt(int i) throws IOException {
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800816 if ((_outputTail + 13) >= _outputEnd) {
817 _flushBuffer();
818 }
819 _outputBuffer[_outputTail++] = BYTE_QUOTE;
820 _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
821 _outputBuffer[_outputTail++] = BYTE_QUOTE;
822 }
823
824 @Override
825 public void writeNumber(long l)
826 throws IOException, JsonGenerationException
827 {
828 _verifyValueWrite("write number");
829 if (_cfgNumbersAsStrings) {
830 _writeQuotedLong(l);
831 return;
832 }
833 if ((_outputTail + 21) >= _outputEnd) {
834 // up to 20 digits, minus sign
835 _flushBuffer();
836 }
837 _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
838 }
839
Francis Galiegue6c842ae2012-09-29 11:24:17 +0200840 private void _writeQuotedLong(long l) throws IOException {
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800841 if ((_outputTail + 23) >= _outputEnd) {
842 _flushBuffer();
843 }
844 _outputBuffer[_outputTail++] = BYTE_QUOTE;
845 _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
846 _outputBuffer[_outputTail++] = BYTE_QUOTE;
847 }
848
849 @Override
850 public void writeNumber(BigInteger value)
851 throws IOException, JsonGenerationException
852 {
853 _verifyValueWrite("write number");
854 if (value == null) {
855 _writeNull();
856 } else if (_cfgNumbersAsStrings) {
857 _writeQuotedRaw(value);
858 } else {
859 writeRaw(value.toString());
860 }
861 }
862
863
864 @Override
865 public void writeNumber(double d)
866 throws IOException, JsonGenerationException
867 {
868 if (_cfgNumbersAsStrings ||
869 // [JACKSON-139]
870 (((Double.isNaN(d) || Double.isInfinite(d))
871 && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
872 writeString(String.valueOf(d));
873 return;
874 }
875 // What is the max length for doubles? 40 chars?
876 _verifyValueWrite("write number");
877 writeRaw(String.valueOf(d));
878 }
879
880 @Override
881 public void writeNumber(float f)
882 throws IOException, JsonGenerationException
883 {
884 if (_cfgNumbersAsStrings ||
885 // [JACKSON-139]
886 (((Float.isNaN(f) || Float.isInfinite(f))
887 && isEnabled(Feature.QUOTE_NON_NUMERIC_NUMBERS)))) {
888 writeString(String.valueOf(f));
889 return;
890 }
891 // What is the max length for floats?
892 _verifyValueWrite("write number");
893 writeRaw(String.valueOf(f));
894 }
895
896 @Override
897 public void writeNumber(BigDecimal value)
898 throws IOException, JsonGenerationException
899 {
900 // Don't really know max length for big decimal, no point checking
901 _verifyValueWrite("write number");
902 if (value == null) {
903 _writeNull();
904 } else if (_cfgNumbersAsStrings) {
905 _writeQuotedRaw(value);
906 } else {
907 writeRaw(value.toString());
908 }
909 }
910
911 @Override
912 public void writeNumber(String encodedValue)
913 throws IOException, JsonGenerationException
914 {
915 _verifyValueWrite("write number");
916 if (_cfgNumbersAsStrings) {
917 _writeQuotedRaw(encodedValue);
918 } else {
919 writeRaw(encodedValue);
920 }
921 }
922
Francis Galiegue6c842ae2012-09-29 11:24:17 +0200923 private void _writeQuotedRaw(Object value) throws IOException
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800924 {
925 if (_outputTail >= _outputEnd) {
926 _flushBuffer();
927 }
928 _outputBuffer[_outputTail++] = BYTE_QUOTE;
929 writeRaw(value.toString());
930 if (_outputTail >= _outputEnd) {
931 _flushBuffer();
932 }
933 _outputBuffer[_outputTail++] = BYTE_QUOTE;
934 }
935
936 @Override
937 public void writeBoolean(boolean state)
938 throws IOException, JsonGenerationException
939 {
940 _verifyValueWrite("write boolean value");
941 if ((_outputTail + 5) >= _outputEnd) {
942 _flushBuffer();
943 }
944 byte[] keyword = state ? TRUE_BYTES : FALSE_BYTES;
945 int len = keyword.length;
946 System.arraycopy(keyword, 0, _outputBuffer, _outputTail, len);
947 _outputTail += len;
948 }
949
950 @Override
951 public void writeNull()
952 throws IOException, JsonGenerationException
953 {
954 _verifyValueWrite("write null value");
955 _writeNull();
956 }
957
958 /*
959 /**********************************************************
960 /* Implementations for other methods
961 /**********************************************************
962 */
963
964 @Override
965 protected final void _verifyValueWrite(String typeMsg)
966 throws IOException, JsonGenerationException
967 {
968 int status = _writeContext.writeValue();
969 if (status == JsonWriteContext.STATUS_EXPECT_NAME) {
970 _reportError("Can not "+typeMsg+", expecting field name");
971 }
972 if (_cfgPrettyPrinter == null) {
973 byte b;
974 switch (status) {
975 case JsonWriteContext.STATUS_OK_AFTER_COMMA:
976 b = BYTE_COMMA;
977 break;
978 case JsonWriteContext.STATUS_OK_AFTER_COLON:
979 b = BYTE_COLON;
980 break;
Tatu Salorantae6dfc692012-09-28 15:34:05 -0700981 case JsonWriteContext.STATUS_OK_AFTER_SPACE: // root-value separator
982 if (_rootValueSeparator != null) {
983 byte[] raw = _rootValueSeparator.asUnquotedUTF8();
984 if (raw.length > 0) {
985 _writeBytes(raw);
986 }
987 }
988 return;
Tatu Salorantaf15531c2011-12-22 23:00:40 -0800989 case JsonWriteContext.STATUS_OK_AS_IS:
990 default:
991 return;
992 }
993 if (_outputTail >= _outputEnd) {
994 _flushBuffer();
995 }
996 _outputBuffer[_outputTail] = b;
997 ++_outputTail;
998 return;
999 }
1000 // Otherwise, pretty printer knows what to do...
1001 _verifyPrettyValueWrite(typeMsg, status);
1002 }
1003
1004 protected final void _verifyPrettyValueWrite(String typeMsg, int status)
1005 throws IOException, JsonGenerationException
1006 {
1007 // If we have a pretty printer, it knows what to do:
1008 switch (status) {
1009 case JsonWriteContext.STATUS_OK_AFTER_COMMA: // array
1010 _cfgPrettyPrinter.writeArrayValueSeparator(this);
1011 break;
1012 case JsonWriteContext.STATUS_OK_AFTER_COLON:
1013 _cfgPrettyPrinter.writeObjectFieldValueSeparator(this);
1014 break;
1015 case JsonWriteContext.STATUS_OK_AFTER_SPACE:
1016 _cfgPrettyPrinter.writeRootValueSeparator(this);
1017 break;
1018 case JsonWriteContext.STATUS_OK_AS_IS:
1019 // First entry, but of which context?
1020 if (_writeContext.inArray()) {
1021 _cfgPrettyPrinter.beforeArrayValues(this);
1022 } else if (_writeContext.inObject()) {
1023 _cfgPrettyPrinter.beforeObjectEntries(this);
1024 }
1025 break;
1026 default:
Tatu Saloranta23439272013-04-06 20:44:18 -07001027 _throwInternal();
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001028 break;
1029 }
1030 }
1031
1032 /*
1033 /**********************************************************
1034 /* Low-level output handling
1035 /**********************************************************
1036 */
1037
1038 @Override
1039 public final void flush()
1040 throws IOException
1041 {
1042 _flushBuffer();
1043 if (_outputStream != null) {
1044 if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1045 _outputStream.flush();
1046 }
1047 }
1048 }
1049
1050 @Override
1051 public void close()
1052 throws IOException
1053 {
1054 super.close();
1055
1056 /* 05-Dec-2008, tatu: To add [JACKSON-27], need to close open
1057 * scopes.
1058 */
1059 // First: let's see that we still have buffers...
1060 if (_outputBuffer != null
1061 && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
1062 while (true) {
1063 JsonStreamContext ctxt = getOutputContext();
1064 if (ctxt.inArray()) {
1065 writeEndArray();
1066 } else if (ctxt.inObject()) {
1067 writeEndObject();
1068 } else {
1069 break;
1070 }
1071 }
1072 }
1073 _flushBuffer();
1074
1075 /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
1076 * on the underlying Reader, unless we "own" it, or auto-closing
1077 * feature is enabled.
1078 * One downside: when using UTF8Writer, underlying buffer(s)
1079 * may not be properly recycled if we don't close the writer.
1080 */
1081 if (_outputStream != null) {
1082 if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
1083 _outputStream.close();
1084 } else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
1085 // If we can't close it, we should at least flush
1086 _outputStream.flush();
1087 }
1088 }
1089 // Internal buffer(s) generator has can now be released as well
1090 _releaseBuffers();
1091 }
1092
1093 @Override
1094 protected void _releaseBuffers()
1095 {
1096 byte[] buf = _outputBuffer;
1097 if (buf != null && _bufferRecyclable) {
1098 _outputBuffer = null;
1099 _ioContext.releaseWriteEncodingBuffer(buf);
1100 }
1101 char[] cbuf = _charBuffer;
1102 if (cbuf != null) {
1103 _charBuffer = null;
1104 _ioContext.releaseConcatBuffer(cbuf);
1105 }
1106 }
1107
1108 /*
1109 /**********************************************************
1110 /* Internal methods, low-level writing, raw bytes
1111 /**********************************************************
1112 */
1113
Tatu Saloranta75273be2012-11-11 15:42:06 -08001114 private final void _writeBytes(byte[] bytes) throws IOException
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001115 {
1116 final int len = bytes.length;
1117 if ((_outputTail + len) > _outputEnd) {
1118 _flushBuffer();
1119 // still not enough?
1120 if (len > MAX_BYTES_TO_BUFFER) {
1121 _outputStream.write(bytes, 0, len);
1122 return;
1123 }
1124 }
1125 System.arraycopy(bytes, 0, _outputBuffer, _outputTail, len);
1126 _outputTail += len;
1127 }
1128
Tatu Saloranta75273be2012-11-11 15:42:06 -08001129 private final void _writeBytes(byte[] bytes, int offset, int len) throws IOException
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001130 {
1131 if ((_outputTail + len) > _outputEnd) {
1132 _flushBuffer();
1133 // still not enough?
1134 if (len > MAX_BYTES_TO_BUFFER) {
1135 _outputStream.write(bytes, offset, len);
1136 return;
1137 }
1138 }
1139 System.arraycopy(bytes, offset, _outputBuffer, _outputTail, len);
1140 _outputTail += len;
1141 }
1142
1143 /*
1144 /**********************************************************
1145 /* Internal methods, mid-level writing, String segments
1146 /**********************************************************
1147 */
1148
1149 /**
1150 * Method called when String to write is long enough not to fit
1151 * completely in temporary copy buffer. If so, we will actually
1152 * copy it in small enough chunks so it can be directly fed
1153 * to single-segment writes (instead of maximum slices that
1154 * would fit in copy buffer)
1155 */
Tatu Saloranta75273be2012-11-11 15:42:06 -08001156 private final void _writeStringSegments(String text)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001157 throws IOException, JsonGenerationException
1158 {
1159 int left = text.length();
1160 int offset = 0;
1161 final char[] cbuf = _charBuffer;
1162
1163 while (left > 0) {
1164 int len = Math.min(_outputMaxContiguous, left);
1165 text.getChars(offset, offset+len, cbuf, 0);
1166 if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
1167 _flushBuffer();
1168 }
1169 _writeStringSegment(cbuf, 0, len);
1170 offset += len;
1171 left -= len;
1172 }
1173 }
1174
1175 /**
1176 * Method called when character sequence to write is long enough that
1177 * its maximum encoded and escaped form is not guaranteed to fit in
1178 * the output buffer. If so, we will need to choose smaller output
1179 * chunks to write at a time.
1180 */
Tatu Saloranta75273be2012-11-11 15:42:06 -08001181 private final void _writeStringSegments(char[] cbuf, int offset, int totalLen)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001182 throws IOException, JsonGenerationException
1183 {
1184 do {
1185 int len = Math.min(_outputMaxContiguous, totalLen);
1186 if ((_outputTail + len) > _outputEnd) { // caller must ensure enough space
1187 _flushBuffer();
1188 }
1189 _writeStringSegment(cbuf, offset, len);
1190 offset += len;
1191 totalLen -= len;
1192 } while (totalLen > 0);
1193 }
1194
1195 /*
1196 /**********************************************************
1197 /* Internal methods, low-level writing, text segments
1198 /**********************************************************
1199 */
1200
1201 /**
1202 * This method called when the string content is already in
1203 * a char buffer, and its maximum total encoded and escaped length
1204 * can not exceed size of the output buffer.
1205 * Caller must ensure that there is enough space in output buffer,
1206 * assuming case of all non-escaped ASCII characters, as well as
1207 * potentially enough space for other cases (but not necessarily flushed)
1208 */
Tatu Saloranta75273be2012-11-11 15:42:06 -08001209 private final void _writeStringSegment(char[] cbuf, int offset, int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001210 throws IOException, JsonGenerationException
1211 {
1212 // note: caller MUST ensure (via flushing) there's room for ASCII only
1213
1214 // Fast+tight loop for ASCII-only, no-escaping-needed output
1215 len += offset; // becomes end marker, then
1216
1217 int outputPtr = _outputTail;
1218 final byte[] outputBuffer = _outputBuffer;
1219 final int[] escCodes = _outputEscapes;
1220
1221 while (offset < len) {
1222 int ch = cbuf[offset];
1223 // note: here we know that (ch > 0x7F) will cover case of escaping non-ASCII too:
1224 if (ch > 0x7F || escCodes[ch] != 0) {
1225 break;
1226 }
1227 outputBuffer[outputPtr++] = (byte) ch;
1228 ++offset;
1229 }
1230 _outputTail = outputPtr;
1231 if (offset < len) {
1232 // [JACKSON-106]
1233 if (_characterEscapes != null) {
1234 _writeCustomStringSegment2(cbuf, offset, len);
1235 // [JACKSON-102]
1236 } else if (_maximumNonEscapedChar == 0) {
1237 _writeStringSegment2(cbuf, offset, len);
1238 } else {
1239 _writeStringSegmentASCII2(cbuf, offset, len);
1240 }
1241
1242 }
1243 }
1244
1245 /**
1246 * Secondary method called when content contains characters to escape,
1247 * and/or multi-byte UTF-8 characters.
1248 */
Tatu Saloranta75273be2012-11-11 15:42:06 -08001249 private final void _writeStringSegment2(final char[] cbuf, int offset, final int end)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001250 throws IOException, JsonGenerationException
1251 {
1252 // Ok: caller guarantees buffer can have room; but that may require flushing:
1253 if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
1254 _flushBuffer();
1255 }
1256
1257 int outputPtr = _outputTail;
1258
1259 final byte[] outputBuffer = _outputBuffer;
1260 final int[] escCodes = _outputEscapes;
1261
1262 while (offset < end) {
1263 int ch = cbuf[offset++];
1264 if (ch <= 0x7F) {
1265 if (escCodes[ch] == 0) {
1266 outputBuffer[outputPtr++] = (byte) ch;
1267 continue;
1268 }
1269 int escape = escCodes[ch];
1270 if (escape > 0) { // 2-char escape, fine
1271 outputBuffer[outputPtr++] = BYTE_BACKSLASH;
1272 outputBuffer[outputPtr++] = (byte) escape;
1273 } else {
1274 // ctrl-char, 6-byte escape...
1275 outputPtr = _writeGenericEscape(ch, outputPtr);
1276 }
1277 continue;
1278 }
1279 if (ch <= 0x7FF) { // fine, just needs 2 byte output
1280 outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
1281 outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
1282 } else {
1283 outputPtr = _outputMultiByteChar(ch, outputPtr);
1284 }
1285 }
1286 _outputTail = outputPtr;
1287 }
1288
1289 /*
1290 /**********************************************************
1291 /* Internal methods, low-level writing, text segment
1292 /* with additional escaping (ASCII or such)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001293 /**********************************************************
1294 */
1295
1296 /**
1297 * Same as <code>_writeStringSegment2(char[], ...)</code., but with
1298 * additional escaping for high-range code points
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001299 */
Tatu Saloranta75273be2012-11-11 15:42:06 -08001300 private final void _writeStringSegmentASCII2(final char[] cbuf, int offset, final int end)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001301 throws IOException, JsonGenerationException
1302 {
1303 // Ok: caller guarantees buffer can have room; but that may require flushing:
1304 if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
1305 _flushBuffer();
1306 }
1307
1308 int outputPtr = _outputTail;
1309
1310 final byte[] outputBuffer = _outputBuffer;
1311 final int[] escCodes = _outputEscapes;
1312 final int maxUnescaped = _maximumNonEscapedChar;
1313
1314 while (offset < end) {
1315 int ch = cbuf[offset++];
1316 if (ch <= 0x7F) {
1317 if (escCodes[ch] == 0) {
1318 outputBuffer[outputPtr++] = (byte) ch;
1319 continue;
1320 }
1321 int escape = escCodes[ch];
1322 if (escape > 0) { // 2-char escape, fine
1323 outputBuffer[outputPtr++] = BYTE_BACKSLASH;
1324 outputBuffer[outputPtr++] = (byte) escape;
1325 } else {
1326 // ctrl-char, 6-byte escape...
1327 outputPtr = _writeGenericEscape(ch, outputPtr);
1328 }
1329 continue;
1330 }
1331 if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
1332 outputPtr = _writeGenericEscape(ch, outputPtr);
1333 continue;
1334 }
1335 if (ch <= 0x7FF) { // fine, just needs 2 byte output
1336 outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
1337 outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
1338 } else {
1339 outputPtr = _outputMultiByteChar(ch, outputPtr);
1340 }
1341 }
1342 _outputTail = outputPtr;
1343 }
1344
1345 /*
1346 /**********************************************************
1347 /* Internal methods, low-level writing, text segment
1348 /* with fully custom escaping (and possibly escaping of non-ASCII
1349 /**********************************************************
1350 */
1351
1352 /**
1353 * Same as <code>_writeStringSegmentASCII2(char[], ...)</code., but with
1354 * additional checking for completely custom escapes
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001355 */
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001356 private void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001357 throws IOException, JsonGenerationException
1358 {
1359 // Ok: caller guarantees buffer can have room; but that may require flushing:
1360 if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
1361 _flushBuffer();
1362 }
1363 int outputPtr = _outputTail;
1364
1365 final byte[] outputBuffer = _outputBuffer;
1366 final int[] escCodes = _outputEscapes;
1367 // may or may not have this limit
1368 final int maxUnescaped = (_maximumNonEscapedChar <= 0) ? 0xFFFF : _maximumNonEscapedChar;
1369 final CharacterEscapes customEscapes = _characterEscapes; // non-null
1370
1371 while (offset < end) {
1372 int ch = cbuf[offset++];
1373 if (ch <= 0x7F) {
1374 if (escCodes[ch] == 0) {
1375 outputBuffer[outputPtr++] = (byte) ch;
1376 continue;
1377 }
1378 int escape = escCodes[ch];
1379 if (escape > 0) { // 2-char escape, fine
1380 outputBuffer[outputPtr++] = BYTE_BACKSLASH;
1381 outputBuffer[outputPtr++] = (byte) escape;
1382 } else if (escape == CharacterEscapes.ESCAPE_CUSTOM) {
1383 SerializableString esc = customEscapes.getEscapeSequence(ch);
1384 if (esc == null) {
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001385 _reportError("Invalid custom escape definitions; custom escape not found for character code 0x"
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001386 +Integer.toHexString(ch)+", although was supposed to have one");
1387 }
1388 outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
1389 } else {
1390 // ctrl-char, 6-byte escape...
1391 outputPtr = _writeGenericEscape(ch, outputPtr);
1392 }
1393 continue;
1394 }
1395 if (ch > maxUnescaped) { // [JACKSON-102] Allow forced escaping if non-ASCII (etc) chars:
1396 outputPtr = _writeGenericEscape(ch, outputPtr);
1397 continue;
1398 }
1399 SerializableString esc = customEscapes.getEscapeSequence(ch);
1400 if (esc != null) {
1401 outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end-offset);
1402 continue;
1403 }
1404 if (ch <= 0x7FF) { // fine, just needs 2 byte output
1405 outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
1406 outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
1407 } else {
1408 outputPtr = _outputMultiByteChar(ch, outputPtr);
1409 }
1410 }
1411 _outputTail = outputPtr;
1412 }
1413
1414 private int _writeCustomEscape(byte[] outputBuffer, int outputPtr, SerializableString esc, int remainingChars)
1415 throws IOException, JsonGenerationException
1416 {
1417 byte[] raw = esc.asUnquotedUTF8(); // must be escaped at this point, shouldn't double-quote
1418 int len = raw.length;
1419 if (len > 6) { // may violate constraints we have, do offline
1420 return _handleLongCustomEscape(outputBuffer, outputPtr, _outputEnd, raw, remainingChars);
1421 }
1422 // otherwise will fit without issues, so:
1423 System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
1424 return (outputPtr + len);
1425 }
1426
1427 private int _handleLongCustomEscape(byte[] outputBuffer, int outputPtr, int outputEnd, byte[] raw,
1428 int remainingChars)
1429 throws IOException, JsonGenerationException
1430 {
1431 int len = raw.length;
1432 if ((outputPtr + len) > outputEnd) {
1433 _outputTail = outputPtr;
1434 _flushBuffer();
1435 outputPtr = _outputTail;
1436 if (len > outputBuffer.length) { // very unlikely, but possible...
1437 _outputStream.write(raw, 0, len);
1438 return outputPtr;
1439 }
1440 System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
1441 outputPtr += len;
1442 }
1443 // but is the invariant still obeyed? If not, flush once more
1444 if ((outputPtr + 6 * remainingChars) > outputEnd) {
1445 _flushBuffer();
1446 return _outputTail;
1447 }
1448 return outputPtr;
1449 }
1450
1451 /*
1452 /**********************************************************
1453 /* Internal methods, low-level writing, "raw UTF-8" segments
1454 /**********************************************************
1455 */
1456
1457 /**
1458 * Method called when UTF-8 encoded (but NOT yet escaped!) content is not guaranteed
1459 * to fit in the output buffer after escaping; as such, we just need to
1460 * chunk writes.
1461 */
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001462 private void _writeUTF8Segments(byte[] utf8, int offset, int totalLen)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001463 throws IOException, JsonGenerationException
1464 {
1465 do {
1466 int len = Math.min(_outputMaxContiguous, totalLen);
1467 _writeUTF8Segment(utf8, offset, len);
1468 offset += len;
1469 totalLen -= len;
1470 } while (totalLen > 0);
1471 }
1472
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001473 private void _writeUTF8Segment(byte[] utf8, final int offset, final int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001474 throws IOException, JsonGenerationException
1475 {
1476 // fast loop to see if escaping is needed; don't copy, just look
1477 final int[] escCodes = _outputEscapes;
1478
1479 for (int ptr = offset, end = offset + len; ptr < end; ) {
1480 // 28-Feb-2011, tatu: escape codes just cover 7-bit range, so:
1481 int ch = utf8[ptr++];
1482 if ((ch >= 0) && escCodes[ch] != 0) {
1483 _writeUTF8Segment2(utf8, offset, len);
1484 return;
1485 }
1486 }
1487
1488 // yes, fine, just copy the sucker
1489 if ((_outputTail + len) > _outputEnd) { // enough room or need to flush?
1490 _flushBuffer(); // but yes once we flush (caller guarantees length restriction)
1491 }
1492 System.arraycopy(utf8, offset, _outputBuffer, _outputTail, len);
1493 _outputTail += len;
1494 }
1495
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001496 private void _writeUTF8Segment2(final byte[] utf8, int offset, int len)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001497 throws IOException, JsonGenerationException
1498 {
1499 int outputPtr = _outputTail;
1500
1501 // Ok: caller guarantees buffer can have room; but that may require flushing:
1502 if ((outputPtr + (len * 6)) > _outputEnd) {
1503 _flushBuffer();
1504 outputPtr = _outputTail;
1505 }
1506
1507 final byte[] outputBuffer = _outputBuffer;
1508 final int[] escCodes = _outputEscapes;
1509 len += offset; // so 'len' becomes 'end'
1510
1511 while (offset < len) {
1512 byte b = utf8[offset++];
1513 int ch = b;
1514 if (ch < 0 || escCodes[ch] == 0) {
1515 outputBuffer[outputPtr++] = b;
1516 continue;
1517 }
1518 int escape = escCodes[ch];
1519 if (escape > 0) { // 2-char escape, fine
1520 outputBuffer[outputPtr++] = BYTE_BACKSLASH;
1521 outputBuffer[outputPtr++] = (byte) escape;
1522 } else {
1523 // ctrl-char, 6-byte escape...
1524 outputPtr = _writeGenericEscape(ch, outputPtr);
1525 }
1526 }
1527 _outputTail = outputPtr;
1528 }
1529
1530 /*
1531 /**********************************************************
1532 /* Internal methods, low-level writing, base64 encoded
1533 /**********************************************************
1534 */
1535
Tatu Saloranta2e70a2a2012-06-04 22:21:45 -07001536 protected void _writeBinary(Base64Variant b64variant,
1537 byte[] input, int inputPtr, final int inputEnd)
1538 throws IOException, JsonGenerationException
1539 {
1540 // Encoding is by chunks of 3 input, 4 output chars, so:
1541 int safeInputEnd = inputEnd - 3;
1542 // Let's also reserve room for possible (and quoted) lf char each round
1543 int safeOutputEnd = _outputEnd - 6;
1544 int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1545
1546 // Ok, first we loop through all full triplets of data:
1547 while (inputPtr <= safeInputEnd) {
1548 if (_outputTail > safeOutputEnd) { // need to flush
1549 _flushBuffer();
1550 }
1551 // First, mash 3 bytes into lsb of 32-bit int
1552 int b24 = ((int) input[inputPtr++]) << 8;
1553 b24 |= ((int) input[inputPtr++]) & 0xFF;
1554 b24 = (b24 << 8) | (((int) input[inputPtr++]) & 0xFF);
1555 _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
1556 if (--chunksBeforeLF <= 0) {
1557 // note: must quote in JSON value
1558 _outputBuffer[_outputTail++] = '\\';
1559 _outputBuffer[_outputTail++] = 'n';
1560 chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1561 }
1562 }
1563
1564 // And then we may have 1 or 2 leftover bytes to encode
1565 int inputLeft = inputEnd - inputPtr; // 0, 1 or 2
1566 if (inputLeft > 0) { // yes, but do we have room for output?
1567 if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
1568 _flushBuffer();
1569 }
1570 int b24 = ((int) input[inputPtr++]) << 16;
1571 if (inputLeft == 2) {
1572 b24 |= (((int) input[inputPtr++]) & 0xFF) << 8;
1573 }
1574 _outputTail = b64variant.encodeBase64Partial(b24, inputLeft, _outputBuffer, _outputTail);
1575 }
1576 }
1577
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001578 // write-method called when length is definitely known
Tatu Saloranta2e70a2a2012-06-04 22:21:45 -07001579 protected int _writeBinary(Base64Variant b64variant,
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001580 InputStream data, byte[] readBuffer, int bytesLeft)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001581 throws IOException, JsonGenerationException
1582 {
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001583 int inputPtr = 0;
1584 int inputEnd = 0;
1585 int lastFullOffset = -3;
1586
Tatu Salorantad80a2d02012-06-11 20:37:35 -07001587 // Let's also reserve room for possible (and quoted) LF char each round
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001588 int safeOutputEnd = _outputEnd - 6;
1589 int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1590
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001591 while (bytesLeft > 2) { // main loop for full triplets
1592 if (inputPtr > lastFullOffset) {
1593 inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
1594 inputPtr = 0;
1595 if (inputEnd < 3) { // required to try to read to have at least 3 bytes
1596 break;
1597 }
1598 lastFullOffset = inputEnd-3;
1599 }
1600 if (_outputTail > safeOutputEnd) { // need to flush
1601 _flushBuffer();
1602 }
1603 int b24 = ((int) readBuffer[inputPtr++]) << 8;
1604 b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
1605 b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
1606 bytesLeft -= 3;
1607 _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
1608 if (--chunksBeforeLF <= 0) {
1609 _outputBuffer[_outputTail++] = '\\';
1610 _outputBuffer[_outputTail++] = 'n';
1611 chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1612 }
1613 }
1614
1615 // And then we may have 1 or 2 leftover bytes to encode
1616 if (bytesLeft > 0) {
1617 inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
1618 inputPtr = 0;
1619 if (inputEnd > 0) { // yes, but do we have room for output?
1620 if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
1621 _flushBuffer();
1622 }
1623 int b24 = ((int) readBuffer[inputPtr++]) << 16;
1624 int amount;
1625 if (inputPtr < inputEnd) {
1626 b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
1627 amount = 2;
1628 } else {
1629 amount = 1;
1630 }
1631 _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
1632 bytesLeft -= amount;
1633 }
1634 }
1635 return bytesLeft;
1636 }
1637
1638 // write method when length is unknown
1639 protected int _writeBinary(Base64Variant b64variant,
1640 InputStream data, byte[] readBuffer)
1641 throws IOException, JsonGenerationException
1642 {
1643 int inputPtr = 0;
1644 int inputEnd = 0;
1645 int lastFullOffset = -3;
1646 int bytesDone = 0;
1647
1648 // Let's also reserve room for possible (and quoted) LF char each round
1649 int safeOutputEnd = _outputEnd - 6;
1650 int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1651
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001652 // Ok, first we loop through all full triplets of data:
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001653 while (true) {
1654 if (inputPtr > lastFullOffset) { // need to load more
1655 inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, readBuffer.length);
1656 inputPtr = 0;
1657 if (inputEnd < 3) { // required to try to read to have at least 3 bytes
1658 break;
1659 }
1660 lastFullOffset = inputEnd-3;
1661 }
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001662 if (_outputTail > safeOutputEnd) { // need to flush
1663 _flushBuffer();
1664 }
1665 // First, mash 3 bytes into lsb of 32-bit int
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001666 int b24 = ((int) readBuffer[inputPtr++]) << 8;
1667 b24 |= ((int) readBuffer[inputPtr++]) & 0xFF;
1668 b24 = (b24 << 8) | (((int) readBuffer[inputPtr++]) & 0xFF);
1669 bytesDone += 3;
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001670 _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
1671 if (--chunksBeforeLF <= 0) {
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001672 _outputBuffer[_outputTail++] = '\\';
1673 _outputBuffer[_outputTail++] = 'n';
1674 chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
1675 }
1676 }
1677
1678 // And then we may have 1 or 2 leftover bytes to encode
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001679 if (inputPtr < inputEnd) { // yes, but do we have room for output?
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001680 if (_outputTail > safeOutputEnd) { // don't really need 6 bytes but...
1681 _flushBuffer();
1682 }
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001683 int b24 = ((int) readBuffer[inputPtr++]) << 16;
1684 int amount = 1;
1685 if (inputPtr < inputEnd) {
1686 b24 |= (((int) readBuffer[inputPtr]) & 0xFF) << 8;
1687 amount = 2;
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001688 }
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001689 bytesDone += amount;
1690 _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001691 }
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001692 return bytesDone;
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001693 }
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001694
1695 private int _readMore(InputStream in,
1696 byte[] readBuffer, int inputPtr, int inputEnd,
1697 int maxRead) throws IOException
1698 {
1699 // anything to shift to front?
1700 int i = 0;
1701 while (inputPtr < inputEnd) {
1702 readBuffer[i++] = readBuffer[inputPtr++];
1703 }
1704 inputPtr = 0;
1705 inputEnd = i;
1706 maxRead = Math.min(maxRead, readBuffer.length);
1707
1708 do {
Tatu Saloranta8c2747d2013-02-26 14:51:12 -08001709 int length = maxRead - inputEnd;
1710 if (length == 0) {
1711 break;
1712 }
1713 int count = in.read(readBuffer, inputEnd, length);
Tatu Saloranta63ff5742012-06-08 21:30:30 -07001714 if (count < 0) {
1715 return inputEnd;
1716 }
1717 inputEnd += count;
1718 } while (inputEnd < 3);
1719 return inputEnd;
1720 }
1721
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001722 /*
1723 /**********************************************************
1724 /* Internal methods, character escapes/encoding
1725 /**********************************************************
1726 */
1727
1728 /**
1729 * Method called to output a character that is beyond range of
1730 * 1- and 2-byte UTF-8 encodings, when outputting "raw"
1731 * text (meaning it is not to be escaped or quoted)
1732 */
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001733 private int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputLen)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001734 throws IOException
1735 {
1736 // Let's handle surrogates gracefully (as 4 byte output):
1737 if (ch >= SURR1_FIRST) {
1738 if (ch <= SURR2_LAST) { // yes, outside of BMP
1739 // Do we have second part?
1740 if (inputOffset >= inputLen) { // nope... have to note down
1741 _reportError("Split surrogate on writeRaw() input (last character)");
1742 }
1743 _outputSurrogates(ch, cbuf[inputOffset]);
1744 return (inputOffset+1);
1745 }
1746 }
1747 final byte[] bbuf = _outputBuffer;
1748 bbuf[_outputTail++] = (byte) (0xe0 | (ch >> 12));
1749 bbuf[_outputTail++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
1750 bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
1751 return inputOffset;
1752 }
1753
1754 protected final void _outputSurrogates(int surr1, int surr2)
1755 throws IOException
1756 {
1757 int c = _decodeSurrogate(surr1, surr2);
1758 if ((_outputTail + 4) > _outputEnd) {
1759 _flushBuffer();
1760 }
1761 final byte[] bbuf = _outputBuffer;
1762 bbuf[_outputTail++] = (byte) (0xf0 | (c >> 18));
1763 bbuf[_outputTail++] = (byte) (0x80 | ((c >> 12) & 0x3f));
1764 bbuf[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
1765 bbuf[_outputTail++] = (byte) (0x80 | (c & 0x3f));
1766 }
1767
1768 /**
1769 *
1770 * @param ch
1771 * @param outputPtr Position within output buffer to append multi-byte in
1772 *
1773 * @return New output position after appending
1774 *
1775 * @throws IOException
1776 */
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001777 private int _outputMultiByteChar(int ch, int outputPtr)
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001778 throws IOException
1779 {
1780 byte[] bbuf = _outputBuffer;
1781 if (ch >= SURR1_FIRST && ch <= SURR2_LAST) { // yes, outside of BMP; add an escape
1782 bbuf[outputPtr++] = BYTE_BACKSLASH;
1783 bbuf[outputPtr++] = BYTE_u;
1784
1785 bbuf[outputPtr++] = HEX_CHARS[(ch >> 12) & 0xF];
1786 bbuf[outputPtr++] = HEX_CHARS[(ch >> 8) & 0xF];
1787 bbuf[outputPtr++] = HEX_CHARS[(ch >> 4) & 0xF];
1788 bbuf[outputPtr++] = HEX_CHARS[ch & 0xF];
1789 } else {
1790 bbuf[outputPtr++] = (byte) (0xe0 | (ch >> 12));
1791 bbuf[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
1792 bbuf[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
1793 }
1794 return outputPtr;
1795 }
1796
1797 protected final int _decodeSurrogate(int surr1, int surr2) throws IOException
1798 {
1799 // First is known to be valid, but how about the other?
1800 if (surr2 < SURR2_FIRST || surr2 > SURR2_LAST) {
1801 String msg = "Incomplete surrogate pair: first char 0x"+Integer.toHexString(surr1)+", second 0x"+Integer.toHexString(surr2);
1802 _reportError(msg);
1803 }
1804 int c = 0x10000 + ((surr1 - SURR1_FIRST) << 10) + (surr2 - SURR2_FIRST);
1805 return c;
1806 }
1807
Francis Galiegue6c842ae2012-09-29 11:24:17 +02001808 private void _writeNull() throws IOException
Tatu Salorantaf15531c2011-12-22 23:00:40 -08001809 {
1810 if ((_outputTail + 4) >= _outputEnd) {
1811 _flushBuffer();
1812 }
1813 System.arraycopy(NULL_BYTES, 0, _outputBuffer, _outputTail, 4);
1814 _outputTail += 4;
1815 }
1816
1817 /**
1818 * Method called to write a generic Unicode escape for given character.
1819 *
1820 * @param charToEscape Character to escape using escape sequence (\\uXXXX)
1821 */
1822 private int _writeGenericEscape(int charToEscape, int outputPtr)
1823 throws IOException
1824 {
1825 final byte[] bbuf = _outputBuffer;
1826 bbuf[outputPtr++] = BYTE_BACKSLASH;
1827 bbuf[outputPtr++] = BYTE_u;
1828 if (charToEscape > 0xFF) {
1829 int hi = (charToEscape >> 8) & 0xFF;
1830 bbuf[outputPtr++] = HEX_CHARS[hi >> 4];
1831 bbuf[outputPtr++] = HEX_CHARS[hi & 0xF];
1832 charToEscape &= 0xFF;
1833 } else {
1834 bbuf[outputPtr++] = BYTE_0;
1835 bbuf[outputPtr++] = BYTE_0;
1836 }
1837 // We know it's a control char, so only the last 2 chars are non-0
1838 bbuf[outputPtr++] = HEX_CHARS[charToEscape >> 4];
1839 bbuf[outputPtr++] = HEX_CHARS[charToEscape & 0xF];
1840 return outputPtr;
1841 }
1842
1843 protected final void _flushBuffer() throws IOException
1844 {
1845 int len = _outputTail;
1846 if (len > 0) {
1847 _outputTail = 0;
1848 _outputStream.write(_outputBuffer, 0, len);
1849 }
1850 }
1851}