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

Last change on this file since 8975 was 6756, checked in by Don-vip, 11 years ago

fix #9590 - replace org.json with GPL-compliant jsonp + remove mention of old world image removed in r1680

File size: 24.0 KB
Line 
1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2012-2013 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
12 * or packager/legal/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 packager/legal/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.util.ArrayDeque;
53import java.util.Deque;
54import java.util.Map;
55
56/**
57 * @author Jitendra Kotamraju
58 */
59class JsonGeneratorImpl implements JsonGenerator {
60 private static final Charset UTF_8 = Charset.forName("UTF-8");
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_ARRAY
104 }
105
106 private final BufferPool bufferPool;
107 private final Writer writer;
108 private Context currentContext = new Context(Scope.IN_NONE);
109 private final Deque<Context> stack = new ArrayDeque<Context>();
110
111 // Using own buffering mechanism as JDK's BufferedWriter uses synchronized
112 // methods. Also, flushBuffer() is useful when you don't want to actually
113 // flush the underlying output source
114 private final char buf[]; // capacity >= INT_MIN_VALUE_CHARS.length
115 private int len = 0;
116
117 JsonGeneratorImpl(Writer writer, BufferPool bufferPool) {
118 this.writer = writer;
119 this.bufferPool = bufferPool;
120 this.buf = bufferPool.take();
121 }
122
123 JsonGeneratorImpl(OutputStream out, BufferPool bufferPool) {
124 this(out, UTF_8, bufferPool);
125 }
126
127 JsonGeneratorImpl(OutputStream out, Charset encoding, BufferPool bufferPool) {
128 this(new OutputStreamWriter(out, encoding), bufferPool);
129 }
130
131 @Override
132 public void flush() {
133 flushBuffer();
134 try {
135 writer.flush();
136 } catch (IOException ioe) {
137 throw new JsonException(JsonMessages.GENERATOR_FLUSH_IO_ERR(), ioe);
138 }
139 }
140
141 @Override
142 public JsonGenerator writeStartObject() {
143 if (currentContext.scope == Scope.IN_OBJECT) {
144 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
145 }
146 if (currentContext.scope == Scope.IN_NONE && !currentContext.first) {
147 throw new JsonGenerationException(JsonMessages.GENERATOR_ILLEGAL_MULTIPLE_TEXT());
148 }
149 writeComma();
150 writeChar('{');
151 stack.push(currentContext);
152 currentContext = new Context(Scope.IN_OBJECT);
153 return this;
154 }
155
156 @Override
157 public JsonGenerator writeStartObject(String name) {
158 if (currentContext.scope != Scope.IN_OBJECT) {
159 throw new JsonGenerationException(
160 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
161 }
162 writeName(name);
163 writeChar('{');
164 stack.push(currentContext);
165 currentContext = new Context(Scope.IN_OBJECT);
166 return this;
167 }
168
169 private JsonGenerator writeName(String name) {
170 writeComma();
171 writeEscapedString(name);
172 writeChar(':');
173 return this;
174 }
175
176 @Override
177 public JsonGenerator write(String name, String fieldValue) {
178 if (currentContext.scope != Scope.IN_OBJECT) {
179 throw new JsonGenerationException(
180 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
181 }
182 writeName(name);
183 writeEscapedString(fieldValue);
184 return this;
185 }
186
187 @Override
188 public JsonGenerator write(String name, int value) {
189 if (currentContext.scope != Scope.IN_OBJECT) {
190 throw new JsonGenerationException(
191 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
192 }
193 writeName(name);
194 writeInt(value);
195 return this;
196 }
197
198 @Override
199 public JsonGenerator write(String name, long value) {
200 if (currentContext.scope != Scope.IN_OBJECT) {
201 throw new JsonGenerationException(
202 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
203 }
204 writeName(name);
205 writeString(String.valueOf(value));
206 return this;
207 }
208
209 @Override
210 public JsonGenerator write(String name, double value) {
211 if (currentContext.scope != Scope.IN_OBJECT) {
212 throw new JsonGenerationException(
213 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
214 }
215 if (Double.isInfinite(value) || Double.isNaN(value)) {
216 throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
217 }
218 writeName(name);
219 writeString(String.valueOf(value));
220 return this;
221 }
222
223 @Override
224 public JsonGenerator write(String name, BigInteger value) {
225 if (currentContext.scope != Scope.IN_OBJECT) {
226 throw new JsonGenerationException(
227 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
228 }
229 writeName(name);
230 writeString(String.valueOf(value));
231 return this;
232 }
233
234 @Override
235 public JsonGenerator write(String name, BigDecimal value) {
236 if (currentContext.scope != Scope.IN_OBJECT) {
237 throw new JsonGenerationException(
238 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
239 }
240 writeName(name);
241 writeString(String.valueOf(value));
242 return this;
243 }
244
245 @Override
246 public JsonGenerator write(String name, boolean value) {
247 if (currentContext.scope != Scope.IN_OBJECT) {
248 throw new JsonGenerationException(
249 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
250 }
251 writeName(name);
252 writeString(value? "true" : "false");
253 return this;
254 }
255
256 @Override
257 public JsonGenerator writeNull(String name) {
258 if (currentContext.scope != Scope.IN_OBJECT) {
259 throw new JsonGenerationException(
260 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
261 }
262 writeName(name);
263 writeString("null");
264 return this;
265 }
266
267 @Override
268 public JsonGenerator write(JsonValue value) {
269 if (currentContext.scope != Scope.IN_ARRAY) {
270 throw new JsonGenerationException(
271 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
272 }
273 switch (value.getValueType()) {
274 case ARRAY:
275 JsonArray array = (JsonArray)value;
276 writeStartArray();
277 for(JsonValue child: array) {
278 write(child);
279 }
280 writeEnd();
281 break;
282 case OBJECT:
283 JsonObject object = (JsonObject)value;
284 writeStartObject();
285 for(Map.Entry<String, JsonValue> member: object.entrySet()) {
286 write(member.getKey(), member.getValue());
287 }
288 writeEnd();
289 break;
290 case STRING:
291 JsonString str = (JsonString)value;
292 write(str.getString());
293 break;
294 case NUMBER:
295 JsonNumber number = (JsonNumber)value;
296 writeValue(number.toString());
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 public JsonGenerator write(String value) {
385 if (currentContext.scope != Scope.IN_ARRAY) {
386 throw new JsonGenerationException(
387 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
388 }
389 writeComma();
390 writeEscapedString(value);
391 return this;
392 }
393
394
395 public JsonGenerator write(int value) {
396 if (currentContext.scope != Scope.IN_ARRAY) {
397 throw new JsonGenerationException(
398 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
399 }
400 writeComma();
401 writeInt(value);
402 return this;
403 }
404
405 @Override
406 public JsonGenerator write(long value) {
407 if (currentContext.scope != Scope.IN_ARRAY) {
408 throw new JsonGenerationException(
409 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
410 }
411 writeValue(String.valueOf(value));
412 return this;
413 }
414
415 @Override
416 public JsonGenerator write(double value) {
417 if (currentContext.scope != Scope.IN_ARRAY) {
418 throw new JsonGenerationException(
419 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
420 }
421 if (Double.isInfinite(value) || Double.isNaN(value)) {
422 throw new NumberFormatException(JsonMessages.GENERATOR_DOUBLE_INFINITE_NAN());
423 }
424 writeValue(String.valueOf(value));
425 return this;
426 }
427
428 @Override
429 public JsonGenerator write(BigInteger value) {
430 if (currentContext.scope != Scope.IN_ARRAY) {
431 throw new JsonGenerationException(
432 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
433 }
434 writeValue(value.toString());
435 return this;
436 }
437
438 @Override
439 public JsonGenerator write(BigDecimal value) {
440 if (currentContext.scope != Scope.IN_ARRAY) {
441 throw new JsonGenerationException(
442 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
443 }
444 writeValue(value.toString());
445 return this;
446 }
447
448 public JsonGenerator write(boolean value) {
449 if (currentContext.scope != Scope.IN_ARRAY) {
450 throw new JsonGenerationException(
451 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
452 }
453 writeComma();
454 writeString(value ? "true" : "false");
455 return this;
456 }
457
458 public JsonGenerator writeNull() {
459 if (currentContext.scope != Scope.IN_ARRAY) {
460 throw new JsonGenerationException(
461 JsonMessages.GENERATOR_ILLEGAL_METHOD(currentContext.scope));
462 }
463 writeComma();
464 writeString("null");
465 return this;
466 }
467
468 private void writeValue(String value) {
469 writeComma();
470 writeString(value);
471 }
472
473 private void writeValue(String name, String value) {
474 writeComma();
475 writeEscapedString(name);
476 writeChar(':');
477 writeString(value);
478 }
479
480 @Override
481 public JsonGenerator writeEnd() {
482 if (currentContext.scope == Scope.IN_NONE) {
483 throw new JsonGenerationException("writeEnd() cannot be called in no context");
484 }
485 writeChar(currentContext.scope == Scope.IN_ARRAY ? ']' : '}');
486 currentContext = stack.pop();
487 return this;
488 }
489
490 protected void writeComma() {
491 if (!currentContext.first) {
492 writeChar(',');
493 }
494 currentContext.first = false;
495 }
496
497 private static class Context {
498 boolean first = true;
499 final Scope scope;
500
501 Context(Scope scope) {
502 this.scope = scope;
503 }
504
505 }
506
507 public void close() {
508 if (currentContext.scope != Scope.IN_NONE || currentContext.first) {
509 throw new JsonGenerationException(JsonMessages.GENERATOR_INCOMPLETE_JSON());
510 }
511 flushBuffer();
512 try {
513 writer.close();
514 } catch (IOException ioe) {
515 throw new JsonException(JsonMessages.GENERATOR_CLOSE_IO_ERR(), ioe);
516 }
517 bufferPool.recycle(buf);
518 }
519
520 // begin, end-1 indexes represent characters that need not
521 // be escaped
522 //
523 // XXXssssssssssssXXXXXXXXXXXXXXXXXXXXXXrrrrrrrrrrrrrrXXXXXX
524 // ^ ^ ^ ^
525 // | | | |
526 // begin end begin end
527 void writeEscapedString(String string) {
528 writeChar('"');
529 int len = string.length();
530 for(int i = 0; i < len; i++) {
531 int begin = i, end = i;
532 char c = string.charAt(i);
533 // find all the characters that need not be escaped
534 // unescaped = %x20-21 | %x23-5B | %x5D-10FFFF
535 while(c >= 0x20 && c <= 0x10ffff && c != 0x22 && c != 0x5c) {
536 i++; end = i;
537 if (i < len) {
538 c = string.charAt(i);
539 } else {
540 break;
541 }
542 }
543 // Write characters without escaping
544 if (begin < end) {
545 writeString(string, begin, end);
546 if (i == len) {
547 break;
548 }
549 }
550
551 switch (c) {
552 case '"':
553 case '\\':
554 writeChar('\\'); writeChar(c);
555 break;
556 case '\b':
557 writeChar('\\'); writeChar('b');
558 break;
559 case '\f':
560 writeChar('\\'); writeChar('f');
561 break;
562 case '\n':
563 writeChar('\\'); writeChar('n');
564 break;
565 case '\r':
566 writeChar('\\'); writeChar('r');
567 break;
568 case '\t':
569 writeChar('\\'); writeChar('t');
570 break;
571 default:
572 String hex = "000" + Integer.toHexString(c);
573 writeString("\\u" + hex.substring(hex.length() - 4));
574 }
575 }
576 writeChar('"');
577 }
578
579 void writeString(String str, int begin, int end) {
580 while (begin < end) { // source begin and end indexes
581 int no = Math.min(buf.length - len, end - begin);
582 str.getChars(begin, begin + no, buf, len);
583 begin += no; // Increment source index
584 len += no; // Increment dest index
585 if (len >= buf.length) {
586 flushBuffer();
587 }
588 }
589 }
590
591 void writeString(String str) {
592 writeString(str, 0, str.length());
593 }
594
595 void writeChar(char c) {
596 if (len >= buf.length) {
597 flushBuffer();
598 }
599 buf[len++] = c;
600 }
601
602 // Not using Integer.toString() since it creates intermediary String
603 // Also, we want the chars to be copied to our buffer directly
604 void writeInt(int num) {
605 int size;
606 if (num == Integer.MIN_VALUE) {
607 size = INT_MIN_VALUE_CHARS.length;
608 } else {
609 size = (num < 0) ? stringSize(-num) + 1 : stringSize(num);
610 }
611 if (len+size >= buf.length) {
612 flushBuffer();
613 }
614 if (num == Integer.MIN_VALUE) {
615 System.arraycopy(INT_MIN_VALUE_CHARS, 0, buf, len, size);
616 } else {
617 fillIntChars(num, buf, len+size);
618 }
619 len += size;
620 }
621
622 // flushBuffer writes the buffered contents to writer. But incase of
623 // byte stream, an OuputStreamWriter is created and that buffers too.
624 // We may need to call OutputStreamWriter#flushBuffer() using
625 // reflection if that is really required (commented out below)
626 void flushBuffer() {
627 try {
628 if (len > 0) {
629 writer.write(buf, 0, len);
630 len = 0;
631 }
632 } catch (IOException ioe) {
633 throw new JsonException(JsonMessages.GENERATOR_WRITE_IO_ERR(), ioe);
634 }
635 }
636
637// private static final Method flushBufferMethod;
638// static {
639// Method m = null;
640// try {
641// m = OutputStreamWriter.class.getDeclaredMethod("flushBuffer");
642// m.setAccessible(true);
643// } catch (Exception e) {
644// // no-op
645// }
646// flushBufferMethod = m;
647// }
648// void flushBufferOSW() {
649// flushBuffer();
650// if (writer instanceof OutputStreamWriter) {
651// try {
652// flushBufferMethod.invoke(writer);
653// } catch (Exception e) {
654// // no-op
655// }
656// }
657// }
658
659 // Requires positive x
660 private static int stringSize(int x) {
661 for (int i=0; ; i++)
662 if (x <= INT_CHARS_SIZE_TABLE[i])
663 return i+1;
664 }
665
666 /**
667 * Places characters representing the integer i into the
668 * character array buf. The characters are placed into
669 * the buffer backwards starting with the least significant
670 * digit at the specified index (exclusive), and working
671 * backwards from there.
672 *
673 * Will fail if i == Integer.MIN_VALUE
674 */
675 private static void fillIntChars(int i, char[] buf, int index) {
676 int q, r;
677 int charPos = index;
678 char sign = 0;
679
680 if (i < 0) {
681 sign = '-';
682 i = -i;
683 }
684
685 // Generate two digits per iteration
686 while (i >= 65536) {
687 q = i / 100;
688 // really: r = i - (q * 100);
689 r = i - ((q << 6) + (q << 5) + (q << 2));
690 i = q;
691 buf [--charPos] = DIGIT_ONES[r];
692 buf [--charPos] = DIGIT_TENS[r];
693 }
694
695 // Fall thru to fast mode for smaller numbers
696 // assert(i <= 65536, i);
697 for (;;) {
698 q = (i * 52429) >>> (16+3);
699 r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ...
700 buf [--charPos] = DIGITS[r];
701 i = q;
702 if (i == 0) break;
703 }
704 if (sign != 0) {
705 buf [--charPos] = sign;
706 }
707 }
708
709}
Note: See TracBrowser for help on using the repository browser.