source: josm/trunk/src/org/glassfish/json/JsonGeneratorImpl.java@ 13686

Last change on this file since 13686 was 13231, checked in by Don-vip, 6 years ago

see #15682 - upgrade to JSR 374 (JSON Processing) API 1.1.2

File size: 23.3 KB
Line 
1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2012-2017 Oracle and/or its affiliates. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12 * or LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
14 *
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at LICENSE.txt.
17 *
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
22 *
23 * Modifications:
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
27 *
28 * Contributor(s):
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41package org.glassfish.json;
42
43import org.glassfish.json.api.BufferPool;
44
45import javax.json.*;
46import javax.json.stream.JsonGenerationException;
47import javax.json.stream.JsonGenerator;
48import java.io.*;
49import java.math.BigDecimal;
50import java.math.BigInteger;
51import java.nio.charset.Charset;
52import java.nio.charset.StandardCharsets;
53import java.util.ArrayDeque;
54import java.util.Deque;
55import java.util.Map;
56
57/**
58 * @author Jitendra Kotamraju
59 */
60class JsonGeneratorImpl implements JsonGenerator {
61
62 private static final char[] INT_MIN_VALUE_CHARS = "-2147483648".toCharArray();
63 private static final int[] INT_CHARS_SIZE_TABLE = { 9, 99, 999, 9999, 99999,
64 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE };
65
66 private static final char [] DIGIT_TENS = {
67 '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
68 '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
69 '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
70 '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
71 '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
72 '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
73 '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
74 '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
75 '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
76 '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
77 } ;
78
79 private static final char [] DIGIT_ONES = {
80 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
81 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
82 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
83 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
84 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
85 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
86 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
87 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
88 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
89 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
90 } ;
91
92 /**
93 * All possible chars for representing a number as a String
94 */
95 private static final char[] DIGITS = {
96 '0' , '1' , '2' , '3' , '4' , '5' ,
97 '6' , '7' , '8' , '9'
98 };
99
100 private static enum Scope {
101 IN_NONE,
102 IN_OBJECT,
103 IN_FIELD,
104 IN_ARRAY
105 }
106
107 private final BufferPool bufferPool;
108 private final Writer writer;
109 private Context currentContext = new Context(Scope.IN_NONE);
110 private final Deque<Context> stack = new ArrayDeque<>();
111
112 // Using own buffering mechanism as JDK's BufferedWriter uses synchronized
113 // methods. Also, flushBuffer() is useful when you don't want to actually
114 // flush the underlying output source
115 private final char buf[]; // capacity >= INT_MIN_VALUE_CHARS.length
116 private int len = 0;
117
118 JsonGeneratorImpl(Writer writer, BufferPool bufferPool) {
119 this.writer = writer;
120 this.bufferPool = bufferPool;
121 this.buf = bufferPool.take();
122 }
123
124 JsonGeneratorImpl(OutputStream out, BufferPool bufferPool) {
125 this(out, StandardCharsets.UTF_8, bufferPool);
126 }
127
128 JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
129 this(new OutputStreamWriter(out, encoding), bufferPool);
130 }
131
132 @Override
133 public void flush() {
134 flushBuffer();
135 try {
136 writer.flush();
137 } catch (IOException ioe) {
138 throw new JsonException(JsonMessages.GENERATOR_FLUSH_IO_ERR(), ioe);
139 }
140 }
141
142 @Override
143 public JsonGenerator writeStartObject() {
144 if (currentContext.scope == Scope.IN_OBJECT) {
145 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
146 }
147 if (currentContext.scope == Scope.IN_NONE && !currentContext.first) {
148 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_MULTIPLE_TEXT());
149 }
150 writeComma();
151 writeChar('{');
152 stack.push(currentContext);
153 currentContext = new Context(Scope.IN_OBJECT);
154 return this;
155 }
156
157 @Override
158 public JsonGenerator writeStartObject(String name) {
159 if (currentContext.scope != Scope.IN_OBJECT) {
160 throw new JsonGenerationException(
161 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
162 }
163 writeName(name);
164 writeChar('{');
165 stack.push(currentContext);
166 currentContext = new Context(Scope.IN_OBJECT);
167 return this;
168 }
169
170 private JsonGenerator writeName(String name) {
171 writeComma();
172 writeEscapedString(name);
173 writeColon();
174 return this;
175 }
176
177 @Override
178 public JsonGenerator write(String name, String fieldValue) {
179 if (currentContext.scope != Scope.IN_OBJECT) {
180 throw new JsonGenerationException(
181 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
182 }
183 writeName(name);
184 writeEscapedString(fieldValue);
185 return this;
186 }
187
188 @Override
189 public JsonGenerator write(String name, int value) {
190 if (currentContext.scope != Scope.IN_OBJECT) {
191 throw new JsonGenerationException(
192 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
193 }
194 writeName(name);
195 writeInt(value);
196 return this;
197 }
198
199 @Override
200 public JsonGenerator write(String name, long value) {
201 if (currentContext.scope != Scope.IN_OBJECT) {
202 throw new JsonGenerationException(
203 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
204 }
205 writeName(name);
206 writeString(String.valueOf(value));
207 return this;
208 }
209
210 @Override
211 public JsonGenerator write(String name, double value) {
212 if (currentContext.scope != Scope.IN_OBJECT) {
213 throw new JsonGenerationException(
214 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
215 }
216 if (Double.isInfinite(value) || Double.isNaN(value)) {
217 throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
218 }
219 writeName(name);
220 writeString(String.valueOf(value));
221 return this;
222 }
223
224 @Override
225 public JsonGenerator write(String name, BigInteger value) {
226 if (currentContext.scope != Scope.IN_OBJECT) {
227 throw new JsonGenerationException(
228 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
229 }
230 writeName(name);
231 writeString(String.valueOf(value));
232 return this;
233 }
234
235 @Override
236 public JsonGenerator write(String name, BigDecimal value) {
237 if (currentContext.scope != Scope.IN_OBJECT) {
238 throw new JsonGenerationException(
239 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
240 }
241 writeName(name);
242 writeString(String.valueOf(value));
243 return this;
244 }
245
246 @Override
247 public JsonGenerator write(String name, boolean value) {
248 if (currentContext.scope != Scope.IN_OBJECT) {
249 throw new JsonGenerationException(
250 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
251 }
252 writeName(name);
253 writeString(value? "true" : "false");
254 return this;
255 }
256
257 @Override
258 public JsonGenerator writeNull(String name) {
259 if (currentContext.scope != Scope.IN_OBJECT) {
260 throw new JsonGenerationException(
261 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
262 }
263 writeName(name);
264 writeString("null");
265 return this;
266 }
267
268 @Override
269 public JsonGenerator write(JsonValue value) {
270 checkContextForValue();
271
272 switch (value.getValueType()) {
273 case ARRAY:
274 JsonArray array = (JsonArray)value;
275 writeStartArray();
276 for(JsonValue child: array) {
277 write(child);
278 }
279 writeEnd();
280 break;
281 case OBJECT:
282 JsonObject object = (JsonObject)value;
283 writeStartObject();
284 for(Map.Entry<String, JsonValue> member: object.entrySet()) {
285 write(member.getKey(), member.getValue());
286 }
287 writeEnd();
288 break;
289 case STRING:
290 JsonString str = (JsonString)value;
291 write(str.getString());
292 break;
293 case NUMBER:
294 JsonNumber number = (JsonNumber)value;
295 writeValue(number.toString());
296 popFieldContext();
297 break;
298 case TRUE:
299 write(true);
300 break;
301 case FALSE:
302 write(false);
303 break;
304 case NULL:
305 writeNull();
306 break;
307 }
308
309 return this;
310 }
311
312 @Override
313 public JsonGenerator writeStartArray() {
314 if (currentContext.scope == Scope.IN_OBJECT) {
315 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
316 }
317 if (currentContext.scope == Scope.IN_NONE && !currentContext.first) {
318 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_MULTIPLE_TEXT());
319 }
320 writeComma();
321 writeChar('[');
322 stack.push(currentContext);
323 currentContext = new Context(Scope.IN_ARRAY);
324 return this;
325 }
326
327 @Override
328 public JsonGenerator writeStartArray(String name) {
329 if (currentContext.scope != Scope.IN_OBJECT) {
330 throw new JsonGenerationException(
331 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
332 }
333 writeName(name);
334 writeChar('[');
335 stack.push(currentContext);
336 currentContext = new Context(Scope.IN_ARRAY);
337 return this;
338 }
339
340 @Override
341 public JsonGenerator write(String name, JsonValue value) {
342 if (currentContext.scope != Scope.IN_OBJECT) {
343 throw new JsonGenerationException(
344 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
345 }
346 switch (value.getValueType()) {
347 case ARRAY:
348 JsonArray array = (JsonArray)value;
349 writeStartArray(name);
350 for(JsonValue child: array) {
351 write(child);
352 }
353 writeEnd();
354 break;
355 case OBJECT:
356 JsonObject object = (JsonObject)value;
357 writeStartObject(name);
358 for(Map.Entry<String, JsonValue> member: object.entrySet()) {
359 write(member.getKey(), member.getValue());
360 }
361 writeEnd();
362 break;
363 case STRING:
364 JsonString str = (JsonString)value;
365 write(name, str.getString());
366 break;
367 case NUMBER:
368 JsonNumber number = (JsonNumber)value;
369 writeValue(name, number.toString());
370 break;
371 case TRUE:
372 write(name, true);
373 break;
374 case FALSE:
375 write(name, false);
376 break;
377 case NULL:
378 writeNull(name);
379 break;
380 }
381 return this;
382 }
383
384 @Override
385 public JsonGenerator write(String value) {
386 checkContextForValue();
387 writeComma();
388 writeEscapedString(value);
389 popFieldContext();
390 return this;
391 }
392
393
394 @Override
395 public JsonGenerator write(int value) {
396 checkContextForValue();
397 writeComma();
398 writeInt(value);
399 popFieldContext();
400 return this;
401 }
402
403 @Override
404 public JsonGenerator write(long value) {
405 checkContextForValue();
406 writeValue(String.valueOf(value));
407 popFieldContext();
408 return this;
409 }
410
411 @Override
412 public JsonGenerator write(double value) {
413 checkContextForValue();
414 if (Double.isInfinite(value) || Double.isNaN(value)) {
415 throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
416 }
417 writeValue(String.valueOf(value));
418 popFieldContext();
419 return this;
420 }
421
422 @Override
423 public JsonGenerator write(BigInteger value) {
424 checkContextForValue();
425 writeValue(value.toString());
426 popFieldContext();
427 return this;
428 }
429
430 private void checkContextForValue() {
431 if ((!currentContext.first && currentContext.scope != Scope.IN_ARRAY && currentContext.scope != Scope.IN_FIELD)
432 || (currentContext.first && currentContext.scope == Scope.IN_OBJECT)) {
433 throw new JsonGenerationException(
434 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
435 }
436 }
437
438 @Override
439 public JsonGenerator write(BigDecimal value) {
440 checkContextForValue();
441 writeValue(value.toString());
442 popFieldContext();
443
444 return this;
445 }
446
447 private void popFieldContext() {
448 if (currentContext.scope == Scope.IN_FIELD) {
449 currentContext = stack.pop();
450 }
451 }
452
453 @Override
454 public JsonGenerator write(boolean value) {
455 checkContextForValue();
456 writeComma();
457 writeString(value ? "true" : "false");
458 popFieldContext();
459 return this;
460 }
461
462 @Override
463 public JsonGenerator writeNull() {
464 checkContextForValue();
465 writeComma();
466 writeString("null");
467 popFieldContext();
468 return this;
469 }
470
471 private void writeValue(String value) {
472 writeComma();
473 writeString(value);
474 }
475
476 private void writeValue(String name, String value) {
477 writeComma();
478 writeEscapedString(name);
479 writeColon();
480 writeString(value);
481 }
482
483 @Override
484 public JsonGenerator writeKey(String name) {
485 if (currentContext.scope != Scope.IN_OBJECT) {
486 throw new JsonGenerationException(
487 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
488 }
489 writeName(name);
490 stack.push(currentContext);
491 currentContext = new Context(Scope.IN_FIELD);
492 currentContext.first = false;
493 return this;
494 }
495
496 @Override
497 public JsonGenerator writeEnd() {
498 if (currentContext.scope == Scope.IN_NONE) {
499 throw new JsonGenerationException("writeEnd() cannot be called in no context");
500 }
501 writeChar(currentContext.scope == Scope.IN_ARRAY ? ']' : '}');
502 currentContext = stack.pop();
503 popFieldContext();
504 return this;
505 }
506
507 protected void writeComma() {
508 if (!currentContext.first && currentContext.scope != Scope.IN_FIELD) {
509 writeChar(',');
510 }
511 currentContext.first = false;
512 }
513
514 protected void writeColon() {
515 writeChar(':');
516 }
517
518 private static class Context {
519 boolean first = true;
520 final Scope scope;
521
522 Context(Scope scope) {
523 this.scope = scope;
524 }
525
526 }
527
528 @Override
529 public void close() {
530 if (currentContext.scope != Scope.IN_NONE || currentContext.first) {
531 throw new JsonGenerationException(JsonMessages.GENERATOR_INCOMPLETE_JSON());
532 }
533 flushBuffer();
534 try {
535 writer.close();
536 } catch (IOException ioe) {
537 throw new JsonException(JsonMessages.GENERATOR_CLOSE_IO_ERR(), ioe);
538 }
539 bufferPool.recycle(buf);
540 }
541
542 // begin, end-1 indexes represent characters that need not
543 // be escaped
544 //
545 // XXXssssssssssssXXXXXXXXXXXXXXXXXXXXXXrrrrrrrrrrrrrrXXXXXX
546 // ^ ^ ^ ^
547 // | | | |
548 // begin end begin end
549 void writeEscapedString(String string) {
550 writeChar('"');
551 int len = string.length();
552 for(int i = 0; i < len; i++) {
553 int begin = i, end = i;
554 char c = string.charAt(i);
555 // find all the characters that need not be escaped
556 // unescaped = %x20-21 | %x23-5B | %x5D-10FFFF
557 while(c >= 0x20 && c <= 0x10ffff && c != 0x22 && c != 0x5c) {
558 i++; end = i;
559 if (i < len) {
560 c = string.charAt(i);
561 } else {
562 break;
563 }
564 }
565 // Write characters without escaping
566 if (begin < end) {
567 writeString(string, begin, end);
568 if (i == len) {
569 break;
570 }
571 }
572
573 switch (c) {
574 case '"':
575 case '\\':
576 writeChar('\\'); writeChar(c);
577 break;
578 case '\b':
579 writeChar('\\'); writeChar('b');
580 break;
581 case '\f':
582 writeChar('\\'); writeChar('f');
583 break;
584 case '\n':
585 writeChar('\\'); writeChar('n');
586 break;
587 case '\r':
588 writeChar('\\'); writeChar('r');
589 break;
590 case '\t':
591 writeChar('\\'); writeChar('t');
592 break;
593 default:
594 String hex = "000" + Integer.toHexString(c);
595 writeString("\\u" + hex.substring(hex.length() - 4));
596 }
597 }
598 writeChar('"');
599 }
600
601 void writeString(String str, int begin, int end) {
602 while (begin < end) { // source begin and end indexes
603 int no = Math.min(buf.length - len, end - begin);
604 str.getChars(begin, begin + no, buf, len);
605 begin += no; // Increment source index
606 len += no; // Increment dest index
607 if (len >= buf.length) {
608 flushBuffer();
609 }
610 }
611 }
612
613 void writeString(String str) {
614 writeString(str, 0, str.length());
615 }
616
617 void writeChar(char c) {
618 if (len >= buf.length) {
619 flushBuffer();
620 }
621 buf[len++] = c;
622 }
623
624 // Not using Integer.toString() since it creates intermediary String
625 // Also, we want the chars to be copied to our buffer directly
626 void writeInt(int num) {
627 int size;
628 if (num == Integer.MIN_VALUE) {
629 size = INT_MIN_VALUE_CHARS.length;
630 } else {
631 size = (num < 0) ? stringSize(-num) + 1 : stringSize(num);
632 }
633 if (len+size >= buf.length) {
634 flushBuffer();
635 }
636 if (num == Integer.MIN_VALUE) {
637 System.arraycopy(INT_MIN_VALUE_CHARS, 0, buf, len, size);
638 } else {
639 fillIntChars(num, buf, len+size);
640 }
641 len += size;
642 }
643
644 // flushBuffer writes the buffered contents to writer. But incase of
645 // byte stream, an OuputStreamWriter is created and that buffers too.
646 // We may need to call OutputStreamWriter#flushBuffer() using
647 // reflection if that is really required (commented out below)
648 void flushBuffer() {
649 try {
650 if (len > 0) {
651 writer.write(buf, 0, len);
652 len = 0;
653 }
654 } catch (IOException ioe) {
655 throw new JsonException(JsonMessages.GENERATOR_WRITE_IO_ERR(), ioe);
656 }
657 }
658
659// private static final Method flushBufferMethod;
660// static {
661// Method m = null;
662// try {
663// m = OutputStreamWriter.class.getDeclaredMethod("flushBuffer");
664// m.setAccessible(true);
665// } catch (Exception e) {
666// // no-op
667// }
668// flushBufferMethod = m;
669// }
670// void flushBufferOSW() {
671// flushBuffer();
672// if (writer instanceof OutputStreamWriter) {
673// try {
674// flushBufferMethod.invoke(writer);
675// } catch (Exception e) {
676// // no-op
677// }
678// }
679// }
680
681 // Requires positive x
682 private static int stringSize(int x) {
683 for (int i=0; ; i++)
684 if (x <= INT_CHARS_SIZE_TABLE[i])
685 return i+1;
686 }
687
688 /**
689 * Places characters representing the integer i into the
690 * character array buf. The characters are placed into
691 * the buffer backwards starting with the least significant
692 * digit at the specified index (exclusive), and working
693 * backwards from there.
694 *
695 * Will fail if i == Integer.MIN_VALUE
696 */
697 private static void fillIntChars(int i, char[] buf, int index) {
698 int q, r;
699 int charPos = index;
700 char sign = 0;
701
702 if (i < 0) {
703 sign = '-';
704 i = -i;
705 }
706
707 // Generate two digits per iteration
708 while (i >= 65536) {
709 q = i / 100;
710 // really: r = i - (q * 100);
711 r = i - ((q << 6) + (q << 5) + (q << 2));
712 i = q;
713 buf [--charPos] = DIGIT_ONES[r];
714 buf [--charPos] = DIGIT_TENS[r];
715 }
716
717 // Fall thru to fast mode for smaller numbers
718 // assert(i <= 65536, i);
719 for (;;) {
720 q = (i * 52429) >>> (16+3);
721 r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
722 buf [--charPos] = DIGITS[r];
723 i = q;
724 if (i == 0) break;
725 }
726 if (sign != 0) {
727 buf [--charPos] = sign;
728 }
729 }
730
731}
Note: See TracBrowser for help on using the repository browser.