1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package com.allanbank.mongodb.bson.element;
22
23 import java.io.IOException;
24 import java.io.Writer;
25 import java.text.SimpleDateFormat;
26 import java.util.Date;
27 import java.util.List;
28 import java.util.TimeZone;
29 import java.util.regex.Pattern;
30
31 import com.allanbank.mongodb.bson.Document;
32 import com.allanbank.mongodb.bson.Element;
33 import com.allanbank.mongodb.bson.Visitor;
34 import com.allanbank.mongodb.error.JsonException;
35 import com.allanbank.mongodb.util.IOUtils;
36
37
38
39
40
41
42
43
44
45 public class JsonSerializationVisitor implements Visitor {
46
47
48 public static final String NL = System.getProperty("line.separator", "\n");
49
50
51 public static final Pattern SYMBOL_PATTERN = Pattern
52 .compile("\\p{Alpha}\\p{Alnum}*");
53
54
55 public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
56
57
58 private int myIndentLevel = 0;
59
60
61 private final boolean myOneLine;
62
63
64 private final Writer mySink;
65
66
67
68
69
70 private boolean mySuppressNames = false;
71
72
73
74
75
76
77
78
79
80
81
82 public JsonSerializationVisitor(final Writer sink, final boolean oneLine) {
83 mySink = sink;
84 myOneLine = oneLine;
85 myIndentLevel = 0;
86 }
87
88
89
90
91
92
93
94
95 @Override
96 public void visit(final List<Element> elements) {
97 try {
98 if (elements.isEmpty()) {
99 mySink.write("{}");
100 }
101 else if ((elements.size() == 1)
102 && !(elements.get(0) instanceof DocumentElement)
103 && !(elements.get(0) instanceof ArrayElement)) {
104 mySink.write("{ ");
105
106 final boolean oldSuppress = mySuppressNames;
107 mySuppressNames = false;
108
109 elements.get(0).accept(this);
110
111 mySuppressNames = oldSuppress;
112 mySink.write(" }");
113 }
114 else {
115 mySink.write('{');
116 myIndentLevel += 1;
117 final boolean oldSuppress = mySuppressNames;
118 mySuppressNames = false;
119
120 boolean first = true;
121 for (final Element element : elements) {
122 if (!first) {
123 mySink.write(",");
124 }
125 nl();
126 element.accept(this);
127 first = false;
128 }
129
130 mySuppressNames = oldSuppress;
131 myIndentLevel -= 1;
132 nl();
133 mySink.write('}');
134 }
135 mySink.flush();
136 }
137 catch (final IOException ioe) {
138 throw new JsonException(ioe);
139 }
140 }
141
142
143
144
145
146
147
148
149 @Override
150 public void visitArray(final String name, final List<Element> elements) {
151 try {
152 writeName(name);
153 if (elements.isEmpty()) {
154 mySink.write("[]");
155 }
156 else if ((elements.size() == 1)
157 && !(elements.get(0) instanceof DocumentElement)
158 && !(elements.get(0) instanceof ArrayElement)) {
159 mySink.write("[ ");
160 final boolean oldSuppress = mySuppressNames;
161 mySuppressNames = true;
162
163 elements.get(0).accept(this);
164
165 mySuppressNames = oldSuppress;
166 mySink.write(" ]");
167 }
168 else {
169 mySink.write("[");
170 myIndentLevel += 1;
171 final boolean oldSuppress = mySuppressNames;
172 mySuppressNames = true;
173
174 boolean first = true;
175 for (final Element element : elements) {
176 if (!first) {
177 mySink.write(", ");
178 }
179 nl();
180 element.accept(this);
181 first = false;
182 }
183
184 mySuppressNames = oldSuppress;
185 myIndentLevel -= 1;
186 nl();
187 mySink.append(']');
188 }
189 mySink.flush();
190 }
191 catch (final IOException ioe) {
192 throw new JsonException(ioe);
193 }
194 }
195
196
197
198
199
200
201
202
203
204 @Override
205 public void visitBinary(final String name, final byte subType,
206 final byte[] data) {
207 try {
208 writeName(name);
209 mySink.write("BinData( ");
210 mySink.write(Integer.toString(subType));
211 mySink.write(", '");
212 mySink.write(IOUtils.toBase64(data));
213 mySink.write("' )");
214 mySink.flush();
215 }
216 catch (final IOException ioe) {
217 throw new JsonException(ioe);
218 }
219 }
220
221
222
223
224
225
226
227
228 @Override
229 public void visitBoolean(final String name, final boolean value) {
230 try {
231 writeName(name);
232 mySink.write(Boolean.toString(value));
233 mySink.flush();
234 }
235 catch (final IOException ioe) {
236 throw new JsonException(ioe);
237 }
238 }
239
240
241
242
243
244
245
246
247
248 @Override
249 public void visitDBPointer(final String name, final String databaseName,
250 final String collectionName, final ObjectId id) {
251 try {
252 writeName(name);
253 mySink.write("DBPointer( ");
254 writeQuotedString(databaseName);
255 mySink.write(", ");
256 writeQuotedString(collectionName);
257 mySink.write(", ");
258 writeObjectId(id);
259 mySink.write(" )");
260 mySink.flush();
261 }
262 catch (final IOException ioe) {
263 throw new JsonException(ioe);
264 }
265 }
266
267
268
269
270
271
272
273
274 @Override
275 public void visitDocument(final String name, final List<Element> elements) {
276 try {
277 writeName(name);
278 visit(elements);
279 }
280 catch (final IOException ioe) {
281 throw new JsonException(ioe);
282 }
283 }
284
285
286
287
288
289
290
291
292 @Override
293 public void visitDouble(final String name, final double value) {
294 try {
295 writeName(name);
296 mySink.write(Double.toString(value));
297 mySink.flush();
298 }
299 catch (final IOException ioe) {
300 throw new JsonException(ioe);
301 }
302 }
303
304
305
306
307
308
309
310
311 @Override
312 public void visitInteger(final String name, final int value) {
313 try {
314 writeName(name);
315 mySink.write(Integer.toString(value));
316 mySink.flush();
317 }
318 catch (final IOException ioe) {
319 throw new JsonException(ioe);
320 }
321 }
322
323
324
325
326
327
328
329
330
331 @Override
332 public void visitJavaScript(final String name, final String code) {
333 try {
334 writeName(name);
335 mySink.write("{ $code : ");
336 writeQuotedString(code);
337 mySink.write(" }");
338 mySink.flush();
339 }
340 catch (final IOException ioe) {
341 throw new JsonException(ioe);
342 }
343 }
344
345
346
347
348
349
350
351
352
353
354
355 @Override
356 public void visitJavaScript(final String name, final String code,
357 final Document scope) {
358 try {
359 writeName(name);
360 mySink.write("{ $code : ");
361 writeQuotedString(code);
362 mySink.write(", $scope : ");
363 scope.accept(this);
364 mySink.write(" }");
365 mySink.flush();
366 }
367 catch (final IOException ioe) {
368 throw new JsonException(ioe);
369 }
370 }
371
372
373
374
375
376
377
378
379
380 @Override
381 public void visitLong(final String name, final long value) {
382 try {
383 writeName(name);
384 mySink.write("NumberLong('");
385 mySink.write(Long.toString(value));
386 mySink.write("')");
387 mySink.flush();
388 }
389 catch (final IOException ioe) {
390 throw new JsonException(ioe);
391 }
392 }
393
394
395
396
397
398
399
400
401
402 @Override
403 public void visitMaxKey(final String name) {
404 try {
405 writeName(name);
406 mySink.write("MaxKey()");
407 mySink.flush();
408 }
409 catch (final IOException ioe) {
410 throw new JsonException(ioe);
411 }
412 }
413
414
415
416
417
418
419
420
421
422 @Override
423 public void visitMinKey(final String name) {
424 try {
425 writeName(name);
426 mySink.write("MinKey()");
427 mySink.flush();
428 }
429 catch (final IOException ioe) {
430 throw new JsonException(ioe);
431 }
432 }
433
434
435
436
437
438
439
440
441
442 @Override
443 public void visitMongoTimestamp(final String name, final long value) {
444 try {
445 final long time = (value >> Integer.SIZE) & 0xFFFFFFFFL;
446 final long increment = value & 0xFFFFFFFFL;
447
448 writeName(name);
449 mySink.write("Timestamp(");
450 mySink.write(Long.toString(time * 1000));
451 mySink.write(", ");
452 mySink.write(Long.toString(increment));
453 mySink.write(')');
454 mySink.flush();
455 }
456 catch (final IOException ioe) {
457 throw new JsonException(ioe);
458 }
459 }
460
461
462
463
464
465
466
467
468 @Override
469 public void visitNull(final String name) {
470 try {
471 writeName(name);
472 mySink.write("null");
473 mySink.flush();
474 }
475 catch (final IOException ioe) {
476 throw new JsonException(ioe);
477 }
478 }
479
480
481
482
483
484
485
486
487
488 @Override
489 public void visitObjectId(final String name, final ObjectId id) {
490 try {
491 writeName(name);
492 writeObjectId(id);
493 mySink.flush();
494 }
495 catch (final IOException ioe) {
496 throw new JsonException(ioe);
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510 @Override
511 public void visitRegularExpression(final String name, final String pattern,
512 final String options) {
513 try {
514 writeName(name);
515 mySink.write("{ $regex : '");
516 mySink.write(pattern);
517 if (options.isEmpty()) {
518 mySink.write("' }");
519 }
520 else {
521 mySink.write("', $options : '");
522 mySink.write(options);
523 mySink.write("' }");
524 }
525 mySink.flush();
526 }
527 catch (final IOException ioe) {
528 throw new JsonException(ioe);
529 }
530 }
531
532
533
534
535
536
537
538
539 @Override
540 public void visitString(final String name, final String value) {
541 try {
542 writeName(name);
543 writeQuotedString(value);
544 mySink.flush();
545 }
546 catch (final IOException ioe) {
547 throw new JsonException(ioe);
548 }
549 }
550
551
552
553
554
555
556
557
558 @Override
559 public void visitSymbol(final String name, final String symbol) {
560 try {
561 writeName(name);
562 if (SYMBOL_PATTERN.matcher(symbol).matches()) {
563 mySink.write(symbol);
564 }
565 else {
566 writeQuotedString(symbol);
567 }
568 mySink.flush();
569 }
570 catch (final IOException ioe) {
571 throw new JsonException(ioe);
572 }
573
574 }
575
576
577
578
579
580
581
582
583
584 @Override
585 public void visitTimestamp(final String name, final long timestamp) {
586 final SimpleDateFormat sdf = new SimpleDateFormat(
587 "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
588 sdf.setTimeZone(UTC);
589
590 try {
591 writeName(name);
592 mySink.write("ISODate('");
593 mySink.write(sdf.format(new Date(timestamp)));
594 mySink.write("')");
595 mySink.flush();
596 }
597 catch (final IOException ioe) {
598 throw new JsonException(ioe);
599 }
600
601 }
602
603
604
605
606
607
608
609
610 protected boolean isSuppressNames() {
611 return mySuppressNames;
612 }
613
614
615
616
617
618
619
620
621 protected void nl() throws IOException {
622 if (!myOneLine) {
623 mySink.write(NL);
624 for (int i = 0; i < myIndentLevel; ++i) {
625 mySink.write(" ");
626 }
627 }
628 else {
629 mySink.write(' ');
630 }
631 }
632
633
634
635
636
637
638
639
640 protected void setSuppressNames(final boolean suppressNames) {
641 mySuppressNames = suppressNames;
642 }
643
644
645
646
647
648
649
650
651
652 protected void writeName(final String name) throws IOException {
653 if (!mySuppressNames) {
654 if (SYMBOL_PATTERN.matcher(name).matches()) {
655 mySink.write(name);
656 }
657 else {
658 writeQuotedString(name);
659 }
660 mySink.write(" : ");
661 }
662 }
663
664
665
666
667
668
669
670
671
672 protected void writeObjectId(final ObjectId id) throws IOException {
673 mySink.write("ObjectId('");
674
675 String hex = Integer.toHexString(id.getTimestamp());
676 mySink.write("00000000".substring(hex.length()));
677 mySink.write(hex);
678
679 hex = Long.toHexString(id.getMachineId());
680 mySink.write("0000000000000000".substring(hex.length()));
681 mySink.write(hex);
682
683 mySink.write("')");
684 }
685
686
687
688
689
690
691
692
693
694 protected void writeQuotedString(final String string) throws IOException {
695 if (string.indexOf('\'') < 0) {
696 mySink.write('\'');
697 mySink.write(string);
698 mySink.write('\'');
699 }
700 else if (string.indexOf('"') < 0) {
701 mySink.write('"');
702 mySink.write(string);
703 mySink.write('"');
704 }
705 else {
706 mySink.write('\'');
707
708 mySink.write(string.replaceAll("'", "\\\\'"));
709 mySink.write('\'');
710 }
711 }
712 }