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