-
Notifications
You must be signed in to change notification settings - Fork 106
/
DslJson.java
2735 lines (2613 loc) · 96.8 KB
/
DslJson.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package com.dslplatform.json;
import org.w3c.dom.Element;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Main DSL-JSON class.
* Easiest way to use the library is to create an DslJson<Object> instance and reuse it within application.
* DslJson has optional constructor for specifying default readers/writers.
* <p>
* During initialization DslJson will use ServiceLoader API to load registered services.
* This is done through `META-INF/services/com.dslplatform.json.CompiledJson` file.
* <p>
* DslJson can fallback to another serializer in case when it doesn't know how to handle specific type.
* This can be specified by Fallback interface during initialization.
* <p>
* If you wish to use compile time databinding @CompiledJson annotation must be specified on the target class
* or implicit reference to target class must exists from a class with @CompiledJson annotation.
* <p>
* Usage example:
* <pre>
* DslJson<Object> dsl = new DslJson<>();
* dsl.serialize(instance, OutputStream);
* POJO pojo = dsl.deserialize(POJO.class, InputStream);
* </pre>
* <p>
* For best performance use serialization API with JsonWriter and byte[] as target.
* JsonWriter is reused via thread local variable. When custom JsonWriter's are used, reusing them will yield maximum performance.
* JsonWriter can be reused via reset methods.
* For best deserialization performance prefer byte[] API instead of InputStream API.
* JsonReader is reused via thread local variable. When custom JsonReaders are used, reusing them will yield maximum performance.
* JsonReader can be reused via process methods.
* <p>
* During deserialization TContext can be used to pass data into deserialized classes.
* This is useful when deserializing domain objects which require state or service provider.
* For example DSL Platform entities require service locator to be able to perform lazy load.
* <p>
* DslJson doesn't have a String or Reader API since it's optimized for processing bytes and streams.
* If you wish to process String, use String.getBytes("UTF-8") as argument for DslJson.
* Only UTF-8 is supported for encoding and decoding JSON.
* <pre>
* DslJson<Object> dsl = new DslJson<>();
* JsonWriter writer = dsl.newWriter();
* dsl.serialize(writer, instance);
* String json = writer.toString(); //JSON as string - avoid using JSON as Strings whenever possible
* byte[] input = json.getBytes("UTF-8");
* POJO pojo = dsl.deserialize(POJO.class, input, input.length);
* </pre>
*
* @param <TContext> used for library specialization. If unsure, use Object
*/
public class DslJson<TContext> implements UnknownSerializer, TypeLookup {
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final Object unknownValue = new Object();
/**
* The context of this instance.
* Can be used for library specialization
*/
public final TContext context;
protected final Fallback<TContext> fallback;
/**
* Should properties with default values be omitted from the resulting JSON?
* This will leave out nulls, empty collections, zeros and other attributes with default values
* which can be reconstructed from schema information
*/
public final boolean omitDefaults;
/**
* When object supports array format, eg. [prop1, prop2, prop3] this value must be enabled before
* object will be serialized in such a way. Regardless of this value deserialization will support all formats.
*/
public final boolean allowArrayFormat;
protected final StringCache keyCache;
protected final StringCache valuesCache;
protected final List<ConverterFactory<JsonWriter.WriteObject>> writerFactories = new ArrayList<ConverterFactory<JsonWriter.WriteObject>>();
protected final List<ConverterFactory<JsonReader.ReadObject>> readerFactories = new ArrayList<ConverterFactory<JsonReader.ReadObject>>();
protected final List<ConverterFactory<JsonReader.BindObject>> binderFactories = new ArrayList<ConverterFactory<JsonReader.BindObject>>();
private final JsonReader.DoublePrecision doublePrecision;
private final JsonReader.UnknownNumberParsing unknownNumbers;
private final int maxNumberDigits;
private final int maxStringSize;
protected final ThreadLocal<JsonWriter> localWriter;
protected final ThreadLocal<JsonReader> localReader;
public interface Fallback<TContext> {
void serialize(Object instance, OutputStream stream) throws IOException;
Object deserialize(TContext context, Type manifest, byte[] body, int size) throws IOException;
Object deserialize(TContext context, Type manifest, InputStream stream) throws IOException;
}
public interface ConverterFactory<T> {
T tryCreate(Type manifest, DslJson dslJson);
}
/**
* Configuration for DslJson options.
* By default key cache is enabled. Everything else is not configured.
* To load `META-INF/services` call `includeServiceLoader()`
*
* @param <TContext> DslJson context
*/
public static class Settings<TContext> {
private TContext context;
private boolean javaSpecifics;
private Fallback<TContext> fallback;
private boolean omitDefaults;
private boolean allowArrayFormat;
private StringCache keyCache = new SimpleStringCache();
private StringCache valuesCache;
private boolean withServiceLoader;
private JsonReader.DoublePrecision doublePrecision = JsonReader.DoublePrecision.DEFAULT;
private JsonReader.UnknownNumberParsing unknownNumbers = JsonReader.UnknownNumberParsing.LONG_AND_BIGDECIMAL;
private int maxNumberDigits = 512;
private int maxStringBuffer = 128 * 1024 * 1024;
private final List<Configuration> configurations = new ArrayList<Configuration>();
private final List<ConverterFactory<JsonWriter.WriteObject>> writerFactories = new ArrayList<ConverterFactory<JsonWriter.WriteObject>>();
private final List<ConverterFactory<JsonReader.ReadObject>> readerFactories = new ArrayList<ConverterFactory<JsonReader.ReadObject>>();
private final List<ConverterFactory<JsonReader.BindObject>> binderFactories = new ArrayList<ConverterFactory<JsonReader.BindObject>>();
/**
* Pass in context for DslJson.
* Context will be available in JsonReader for objects which needs it.
*
* @param context context propagated to JsonReaders
* @return itself
*/
public Settings<TContext> withContext(TContext context) {
this.context = context;
return this;
}
/**
* Enable converters for Java specific types (Graphics API) not available on Android.
*
* @param javaSpecifics should register Java specific converters
* @return itself
*/
public Settings<TContext> withJavaConverters(boolean javaSpecifics) {
this.javaSpecifics = javaSpecifics;
return this;
}
/**
* Will be eventually replaced with writer/reader factories.
* Used by DslJson to call into when trying to serialize/deserialize object which is not supported.
*
* @param fallback how to handle unsupported type
* @return which fallback to use in case of unsupported type
*/
@Deprecated
public Settings<TContext> fallbackTo(Fallback<TContext> fallback) {
this.fallback = fallback;
return this;
}
/**
* DslJson can exclude some properties from resulting JSON which it can reconstruct fully from schema information.
* Eg. int with value 0 can be omitted since that is default value for the type.
* Null values can be excluded since they are handled the same way as missing property.
*
* @param omitDefaults should exclude default values from resulting JSON
* @return itself
*/
public Settings<TContext> skipDefaultValues(boolean omitDefaults) {
this.omitDefaults = omitDefaults;
return this;
}
/**
* Some encoders/decoders support writing objects in array format.
* For encoder to write objects in such format, Array format must be defined before the Default and minified formats
* and array format must be allowed via this setting.
* If objects support multiple formats decoding will work regardless of this setting.
*
* @param allowArrayFormat allow serialization via array format
* @return itself
*/
public Settings<TContext> allowArrayFormat(boolean allowArrayFormat) {
this.allowArrayFormat = allowArrayFormat;
return this;
}
/**
* Use specific key cache implementation.
* Key cache is enabled by default and it's used when deserializing unstructured objects such as Map<String, Object>
* to avoid allocating new String key instance. Instead StringCache will provide a new or an old instance.
* This improves memory usage and performance since there is usually small number of keys.
* It does have some performance overhead, but this is dependant on the implementation.
* <p>
* To disable key cache, provide null for it.
*
* @param keyCache which key cache to use
* @return itself
*/
public Settings<TContext> useKeyCache(StringCache keyCache) {
this.keyCache = keyCache;
return this;
}
/**
* Use specific string values cache implementation.
* By default string values cache is disabled.
* <p>
* To support memory restricted scenarios where there is limited number of string values,
* values cache can be used.
* <p>
* Not every "JSON string" will use this cache... eg UUID, LocalDate don't create an instance of string
* and therefore don't use this cache.
*
* @param valuesCache which values cache to use
* @return itself
*/
public Settings<TContext> useStringValuesCache(StringCache valuesCache) {
this.valuesCache = valuesCache;
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering writer converter factory allows for constructing JSON converter lazily.
*
* @param writer registered writer factory
* @return itself
*/
public Settings<TContext> resolveWriter(ConverterFactory<JsonWriter.WriteObject> writer) {
if (writer == null) throw new IllegalArgumentException("writer can't be null");
writerFactories.add(writer);
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering reader converter factory allows for constructing JSON converter lazily.
*
* @param reader registered reader factory
* @return itself
*/
public Settings<TContext> resolveReader(ConverterFactory<JsonReader.ReadObject> reader) {
if (reader == null) throw new IllegalArgumentException("reader can't be null");
readerFactories.add(reader);
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering binder converter factory allows for constructing JSON converter lazily.
*
* @param binder registered binder factory
* @return itself
*/
public Settings<TContext> resolveBinder(ConverterFactory<JsonReader.BindObject> binder) {
if (binder == null) throw new IllegalArgumentException("binder can't be null");
binderFactories.add(binder);
return this;
}
/**
* Load converters using `ServiceLoader.load(Configuration.class)`
* Will scan through `META-INF/services/com.dslplatform.json.Configuration` file and register implementation during startup.
* This will pick up compile time databindings if they are available in specific folder.
* <p>
* Note that gradle on Android has issues with preserving that file, in which case it can be provided manually.
* DslJson will fall back to "expected" class name if it doesn't find anything during scanning.
*
* @return itself
*/
public Settings<TContext> includeServiceLoader() {
withServiceLoader = true;
for (Configuration c : ServiceLoader.load(Configuration.class)) {
boolean hasConfiguration = false;
Class<?> manifest = c.getClass();
for (Configuration cur : configurations) {
if (cur.getClass() == manifest) {
hasConfiguration = true;
break;
}
}
if (!hasConfiguration) {
configurations.add(c);
}
}
return this;
}
/**
* By default doubles are not deserialized into an exact value in some rare edge cases.
*
* @param precision type of double deserialization
* @return itself
*/
public Settings<TContext> doublePrecision(JsonReader.DoublePrecision precision) {
if (precision == null) throw new IllegalArgumentException("precision can't be null");
this.doublePrecision = precision;
return this;
}
/**
* When processing JSON without a schema numbers can be deserialized in various ways:
*
* - as longs and decimals
* - as longs and doubles
* - as decimals only
* - as doubles only
*
* Default is as long and BigDecimal
*
* @param unknownNumbers how to deserialize numbers without a schema
* @return itself
*/
public Settings<TContext> unknownNumbers(JsonReader.UnknownNumberParsing unknownNumbers) {
if (unknownNumbers == null) throw new IllegalArgumentException("unknownNumbers can't be null");
this.unknownNumbers = unknownNumbers;
return this;
}
/**
* Specify maximum allowed size for digits buffer. Default is 512.
* Digits buffer is used when processing strange/large input numbers.
*
* @param size maximum allowed size for digit buffer
* @return itself
*/
public Settings<TContext> limitDigitsBuffer(int size) {
if (size < 1) throw new IllegalArgumentException("size can't be smaller than 1");
this.maxNumberDigits = size;
return this;
}
/**
* Specify maximum allowed size for string buffer. Default is 128MB
* To protect against malicious inputs, maximum allowed string buffer can be reduced.
*
* @param size maximum size of buffer in bytes
* @return itself
*/
public Settings<TContext> limitStringBuffer(int size) {
if (size < 1) throw new IllegalArgumentException("size can't be smaller than 1");
this.maxStringBuffer = size;
return this;
}
/**
* Configure DslJson with custom Configuration during startup.
* Configurations are extension points for setting up readers/writers during DslJson initialization.
*
* @param conf custom extensibility point
* @return itself
*/
public Settings<TContext> with(Configuration conf) {
if (conf == null) throw new IllegalArgumentException("conf can't be null");
configurations.add(conf);
return this;
}
private Settings<TContext> with(Iterable<Configuration> confs) {
if (confs != null) {
withServiceLoader = true;
for (Configuration c : confs)
configurations.add(c);
}
return this;
}
}
/**
* Simple initialization entry point.
* Will provide null for TContext
* Java graphics readers/writers will not be registered.
* Fallback will not be configured.
* Key cache will be enables, values cache will be disabled.
* Default ServiceLoader.load method will be used to setup services from META-INF
*/
public DslJson() {
this(new Settings<TContext>().includeServiceLoader());
}
/**
* Will be removed. Use DslJson(Settings) instead.
* Fully configurable entry point.
*
* @param context context instance which can be provided to deserialized objects. Use null if not sure
* @param javaSpecifics register Java graphics specific classes such as java.awt.Point, Image, ...
* @param fallback in case of unsupported type, try serialization/deserialization through external API
* @param omitDefaults should serialization produce minified JSON (omit nulls and default values)
* @param keyCache parsed keys can be cached (this is only used in small subset of parsing)
* @param serializers additional serializers/deserializers which will be immediately registered into readers/writers
*/
@Deprecated
public DslJson(
final TContext context,
final boolean javaSpecifics,
final Fallback<TContext> fallback,
final boolean omitDefaults,
final StringCache keyCache,
final Iterable<Configuration> serializers) {
this(new Settings<TContext>()
.withContext(context)
.withJavaConverters(javaSpecifics)
.fallbackTo(fallback)
.skipDefaultValues(omitDefaults)
.useKeyCache(keyCache)
.with(serializers)
);
}
/**
* Fully configurable entry point.
* Provide settings for DSL-JSON initialization.
*
* @param settings DSL-JSON configuration
*/
public DslJson(final Settings<TContext> settings) {
if (settings == null) throw new IllegalArgumentException("settings can't be null");
final DslJson<TContext> self = this;
this.localWriter = new ThreadLocal<JsonWriter>() {
@Override
protected JsonWriter initialValue() {
return new JsonWriter(4096, self);
}
};
this.localReader = new ThreadLocal<JsonReader>() {
@Override
protected JsonReader initialValue() {
return new JsonReader<TContext>(new byte[4096], 4096, self.context, new char[64], self.keyCache, self.valuesCache, self, self.doublePrecision, self.unknownNumbers, self.maxNumberDigits, self.maxStringSize);
}
};
this.context = settings.context;
this.fallback = settings.fallback;
this.omitDefaults = settings.omitDefaults;
this.allowArrayFormat = settings.allowArrayFormat;
this.keyCache = settings.keyCache;
this.valuesCache = settings.valuesCache;
this.unknownNumbers = settings.unknownNumbers;
this.doublePrecision = settings.doublePrecision;
this.maxNumberDigits = settings.maxNumberDigits;
this.maxStringSize = settings.maxStringBuffer;
this.writerFactories.addAll(settings.writerFactories);
this.readerFactories.addAll(settings.readerFactories);
this.binderFactories.addAll(settings.binderFactories);
registerReader(byte[].class, BinaryConverter.Base64Reader);
registerWriter(byte[].class, BinaryConverter.Base64Writer);
registerReader(boolean.class, BoolConverter.READER);
registerWriter(boolean.class, BoolConverter.WRITER);
registerDefault(boolean.class, false);
registerReader(boolean[].class, BoolConverter.ARRAY_READER);
registerWriter(boolean[].class, BoolConverter.ARRAY_WRITER);
registerReader(Boolean.class, BoolConverter.NULLABLE_READER);
registerWriter(Boolean.class, BoolConverter.WRITER);
if (settings.javaSpecifics) {
registerJavaSpecifics(this);
}
registerReader(LinkedHashMap.class, ObjectConverter.MapReader);
registerReader(HashMap.class, ObjectConverter.MapReader);
registerReader(Map.class, ObjectConverter.MapReader);
registerWriter(Map.class, new JsonWriter.WriteObject<Map>() {
@Override
public void write(JsonWriter writer, Map value) {
if (value == null) {
writer.writeNull();
} else {
try {
serializeMap(value, writer);
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
}
});
registerReader(URI.class, NetConverter.UriReader);
registerWriter(URI.class, NetConverter.UriWriter);
registerReader(InetAddress.class, NetConverter.AddressReader);
registerWriter(InetAddress.class, NetConverter.AddressWriter);
registerReader(double.class, NumberConverter.DOUBLE_READER);
registerWriter(double.class, NumberConverter.DOUBLE_WRITER);
registerDefault(double.class, 0.0);
registerReader(double[].class, NumberConverter.DOUBLE_ARRAY_READER);
registerWriter(double[].class, NumberConverter.DOUBLE_ARRAY_WRITER);
registerReader(Double.class, NumberConverter.NULLABLE_DOUBLE_READER);
registerWriter(Double.class, NumberConverter.DOUBLE_WRITER);
registerReader(float.class, NumberConverter.FLOAT_READER);
registerWriter(float.class, NumberConverter.FLOAT_WRITER);
registerDefault(float.class, 0.0f);
registerReader(float[].class, NumberConverter.FLOAT_ARRAY_READER);
registerWriter(float[].class, NumberConverter.FLOAT_ARRAY_WRITER);
registerReader(Float.class, NumberConverter.NULLABLE_FLOAT_READER);
registerWriter(Float.class, NumberConverter.FLOAT_WRITER);
registerReader(int.class, NumberConverter.INT_READER);
registerWriter(int.class, NumberConverter.INT_WRITER);
registerDefault(int.class, 0);
registerReader(int[].class, NumberConverter.INT_ARRAY_READER);
registerWriter(int[].class, NumberConverter.INT_ARRAY_WRITER);
registerReader(Integer.class, NumberConverter.NULLABLE_INT_READER);
registerWriter(Integer.class, NumberConverter.INT_WRITER);
registerReader(short.class, NumberConverter.ShortReader);
registerWriter(short.class, NumberConverter.ShortWriter);
registerDefault(short.class, (short)0);
registerReader(short[].class, NumberConverter.ShortArrayReader);
registerWriter(short[].class, NumberConverter.ShortArrayWriter);
registerReader(Short.class, NumberConverter.NullableShortReader);
registerWriter(Short.class, NumberConverter.ShortWriter);
registerReader(long.class, NumberConverter.LONG_READER);
registerWriter(long.class, NumberConverter.LONG_WRITER);
registerDefault(long.class, 0L);
registerReader(long[].class, NumberConverter.LONG_ARRAY_READER);
registerWriter(long[].class, NumberConverter.LONG_ARRAY_WRITER);
registerReader(Long.class, NumberConverter.NULLABLE_LONG_READER);
registerWriter(Long.class, NumberConverter.LONG_WRITER);
registerReader(BigDecimal.class, NumberConverter.DecimalReader);
registerWriter(BigDecimal.class, NumberConverter.DecimalWriter);
registerReader(String.class, StringConverter.READER);
registerWriter(String.class, StringConverter.WRITER);
registerReader(UUID.class, UUIDConverter.READER);
registerWriter(UUID.class, UUIDConverter.WRITER);
registerReader(Number.class, NumberConverter.NumberReader);
registerWriter(CharSequence.class, StringConverter.WRITER_CHARS);
registerReader(StringBuilder.class, StringConverter.READER_BUILDER);
registerReader(StringBuffer.class, StringConverter.READER_BUFFER);
for (Configuration serializer : settings.configurations) {
serializer.configure(this);
}
if (settings.withServiceLoader && settings.configurations.isEmpty()) {
//TODO: workaround common issue with failed services registration. try to load common external name if exists
loadDefaultConverters(this, "dsl_json_Annotation_Processor_External_Serialization");
loadDefaultConverters(this, "dsl_json.json.ExternalSerialization");
loadDefaultConverters(this, "dsl_json_ExternalSerialization");
}
}
/**
* Simplistic string cache implementation.
* It uses a fixed String[] structure in which it caches string value based on it's hash.
* Eg, hash & mask provide index into the structure. Different string with same hash will overwrite the previous one.
*/
public static class SimpleStringCache implements StringCache {
private final int mask;
private final String[] cache;
/**
* Will use String[] with 1024 elements.
*/
public SimpleStringCache() {
this(10);
}
public SimpleStringCache(int log2Size) {
int size = 2;
for (int i = 1; i < log2Size; i++) {
size *= 2;
}
mask = size - 1;
cache = new String[size];
}
/**
* Calculates hash of the provided "string" and looks it up from the String[]
* It it doesn't exists of a different string is already there a new String instance is created
* and saved into the String[]
*
* @param chars buffer into which string was parsed
* @param len the string length inside the buffer
* @return String instance matching the char[]/int pair
*/
@Override
public String get(char[] chars, int len) {
long hash = 0x811c9dc5;
for (int i = 0; i < len; i++) {
hash ^= (byte) chars[i];
hash *= 0x1000193;
}
final int index = (int) hash & mask;
final String value = cache[index];
if (value == null) return createAndPut(index, chars, len);
if (value.length() != len) return createAndPut(index, chars, len);
for (int i = 0; i < value.length(); i++) {
if (value.charAt(i) != chars[i]) return createAndPut(index, chars, len);
}
return value;
}
private String createAndPut(int index, char[] chars, int len) {
final String value = new String(chars, 0, len);
cache[index] = value;
return value;
}
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @return bound writer
*/
public JsonWriter newWriter() {
return new JsonWriter(this);
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @param size initial buffer size
* @return bound writer
*/
public JsonWriter newWriter(int size) {
return new JsonWriter(size, this);
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @param buffer initial buffer
* @return bound writer
*/
public JsonWriter newWriter(byte[] buffer) {
if (buffer == null) throw new IllegalArgumentException("null value provided for buffer");
return new JsonWriter(buffer, this);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @return bound reader
*/
public JsonReader<TContext> newReader() {
return new JsonReader<TContext>(new byte[4096], 4096, context, new char[64], keyCache, valuesCache, this, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @param bytes input bytes
* @return bound reader
*/
public JsonReader<TContext> newReader(byte[] bytes) {
return new JsonReader<TContext>(bytes, bytes.length, context, new char[64], keyCache, valuesCache, this, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @param bytes input bytes
* @param length use input bytes up to specified length
* @return bound reader
*/
public JsonReader<TContext> newReader(byte[] bytes, int length) {
return new JsonReader<TContext>(bytes, length, context, new char[64], keyCache, valuesCache, this, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* Pass in initial string buffer.
* This reader can be reused via process method.
*
* @param bytes input bytes
* @param length use input bytes up to specified length
* @param tmp string parsing buffer
* @return bound reader
*/
public JsonReader<TContext> newReader(byte[] bytes, int length, char[] tmp) {
return new JsonReader<TContext>(bytes, length, context, tmp, keyCache, valuesCache, this, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* Created reader can be reused (using process method).
* This is convenience method for creating a new reader and binding it to stream.
*
* @param stream input stream
* @param buffer temporary buffer
* @return bound reader
* @throws java.io.IOException unable to read from stream
*/
public JsonReader<TContext> newReader(InputStream stream, byte[] buffer) throws IOException {
final JsonReader<TContext> reader = newReader(buffer);
reader.process(stream);
return reader;
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This method id Deprecated since it should be avoided.
* It's better to use byte[] or InputStream based readers
*
* @param input JSON string
* @return bound reader
*/
@Deprecated
public JsonReader<TContext> newReader(String input) {
final byte[] bytes = input.getBytes(UTF8);
return new JsonReader<TContext>(bytes, bytes.length, context, new char[64], keyCache, valuesCache, this, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
private static void loadDefaultConverters(final DslJson json, final String name) {
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> external = loader.loadClass(name);
Configuration instance = (Configuration) external.newInstance();
instance.configure(json);
} catch (NoClassDefFoundError ignore) {
} catch (Exception ignore) {
}
}
static void registerJavaSpecifics(final DslJson json) {
json.registerReader(java.awt.geom.Point2D.Double.class, JavaGeomConverter.LocationReader);
json.registerReader(java.awt.geom.Point2D.class, JavaGeomConverter.LocationReader);
json.registerWriter(java.awt.geom.Point2D.class, JavaGeomConverter.LocationWriter);
json.registerReader(java.awt.Point.class, JavaGeomConverter.PointReader);
json.registerWriter(java.awt.Point.class, JavaGeomConverter.PointWriter);
json.registerReader(java.awt.geom.Rectangle2D.Double.class, JavaGeomConverter.RectangleReader);
json.registerReader(java.awt.geom.Rectangle2D.class, JavaGeomConverter.RectangleReader);
json.registerWriter(java.awt.geom.Rectangle2D.class, JavaGeomConverter.RectangleWriter);
json.registerReader(java.awt.image.BufferedImage.class, JavaGeomConverter.ImageReader);
json.registerReader(java.awt.Image.class, JavaGeomConverter.ImageReader);
json.registerWriter(java.awt.Image.class, JavaGeomConverter.ImageWriter);
json.registerReader(Element.class, XmlConverter.Reader);
json.registerWriter(Element.class, XmlConverter.Writer);
}
private final Map<Type, Object> defaults = new HashMap<Type, Object>();
<T> void registerDefault(Class<T> manifest, T instance) {
defaults.put(manifest, instance);
}
public final Object getDefault(Type manifest) {
if (manifest == null) return null;
Object instance = defaults.get(manifest);
if (instance != null) return instance;
final Class<?> rawType;
if (manifest instanceof Class<?>) {
rawType = (Class<?>) manifest;
} else if (manifest instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) manifest;
if (pt.getRawType() instanceof Class<?>) {
rawType = (Class<?>)pt.getRawType();
} else return null;
} else return null;
if (rawType.isPrimitive()) {
return Array.get(Array.newInstance(rawType, 1), 0);
}
return defaults.get(rawType);
}
private final ConcurrentMap<Class<?>, JsonReader.ReadJsonObject<JsonObject>> objectReaders =
new ConcurrentHashMap<Class<?>, JsonReader.ReadJsonObject<JsonObject>>();
private final ConcurrentMap<Type, JsonReader.ReadObject<?>> readers = new ConcurrentHashMap<Type, JsonReader.ReadObject<?>>();
private final ConcurrentMap<Type, JsonReader.BindObject<?>> binders = new ConcurrentHashMap<Type, JsonReader.BindObject<?>>();
public final Set<Type> getRegisteredDecoders() {
return readers.keySet();
}
public final Set<Type> getRegisteredBinders() {
return binders.keySet();
}
public final Set<Type> getRegisteredEncoders() {
return jsonWriters.keySet();
}
/**
* Register custom reader for specific type (JSON -> instance conversion).
* Reader is used for conversion from input byte[] -> target object instance
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a reader this will disable deserialization of specified type
*
* @param manifest specified type
* @param reader provide custom implementation for reading JSON into an object instance
* @param <T> type
* @param <S> type or subtype
*/
public <T, S extends T> void registerReader(final Class<T> manifest, final JsonReader.ReadObject<S> reader) {
if (reader == null) readers.remove(manifest);
else readers.put(manifest, reader);
}
/**
* Register custom reader for specific type (JSON -> instance conversion).
* Reader is used for conversion from input byte[] -> target object instance
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a reader this will disable deserialization of specified type
*
* @param manifest specified type
* @param reader provide custom implementation for reading JSON into an object instance
* @return old registered value
*/
public JsonReader.ReadObject registerReader(final Type manifest, final JsonReader.ReadObject<?> reader) {
if (reader == null) return readers.remove(manifest);
try {
return readers.get(manifest);
} finally {
readers.put(manifest, reader);
}
}
/**
* Register custom binder for specific type (JSON -> instance conversion).
* Binder is used for conversion from input byte[] -> existing target object instance.
* It's similar to reader, with the difference that it accepts target instance.
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a binder this will disable binding of specified type
*
* @param manifest specified type
* @param binder provide custom implementation for binding JSON to an object instance
* @param <T> type
* @param <S> type or subtype
*/
public <T, S extends T> void registerBinder(final Class<T> manifest, final JsonReader.BindObject<S> binder) {
if (binder == null) binders.remove(manifest);
else binders.put(manifest, binder);
}
/**
* Register custom binder for specific type (JSON -> instance conversion).
* Binder is used for conversion from input byte[] -> existing target object instance.
* It's similar to reader, with the difference that it accepts target instance.
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a binder this will disable binding of specified type
*
* @param manifest specified type
* @param binder provide custom implementation for binding JSON to an object instance
*/
public void registerBinder(final Type manifest, final JsonReader.BindObject<?> binder) {
if (binder == null) binders.remove(manifest);
else binders.put(manifest, binder);
}
private final HashMap<Type, JsonWriter.WriteObject<?>> jsonWriters = new HashMap<Type, JsonWriter.WriteObject<?>>();
/**
* Register custom writer for specific type (instance -> JSON conversion).
* Writer is used for conversion from object instance -> output byte[]
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a writer this will disable serialization of specified type
*
* @param manifest specified type
* @param writer provide custom implementation for writing JSON from object instance
* @param <T> type
*/
public <T> void registerWriter(final Class<T> manifest, final JsonWriter.WriteObject<T> writer) {
if (writer == null) {
writerMap.remove(manifest);
jsonWriters.remove(manifest);
} else {
writerMap.put(manifest, manifest);
jsonWriters.put(manifest, writer);
}
}
/**
* Register custom writer for specific type (instance -> JSON conversion).
* Writer is used for conversion from object instance -> output byte[]
* <p>
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
* <p>
* If null is registered for a writer this will disable serialization of specified type
*
* @param manifest specified type
* @param writer provide custom implementation for writing JSON from object instance
* @return old registered value
*/
public JsonWriter.WriteObject registerWriter(final Type manifest, final JsonWriter.WriteObject<?> writer) {
if (writer == null) return jsonWriters.remove(manifest);
try {
return jsonWriters.get(manifest);
} finally {
jsonWriters.put(manifest, writer);
}
}
private final ConcurrentMap<Class<?>, Class<?>> writerMap = new ConcurrentHashMap<Class<?>, Class<?>>();
/**
* Try to find registered writer for provided type.
* If writer is not found, null will be returned.
* If writer for exact type is not found, type hierarchy will be scanned for base writer.
* <p>
* Writer is used for conversion from object instance into JSON representation.
*
* @param manifest specified type
* @return writer for specified type if found
*/
public JsonWriter.WriteObject<?> tryFindWriter(final Type manifest) {
JsonWriter.WriteObject writer = jsonWriters.get(manifest);
if (writer != null) return writer;
for (ConverterFactory<JsonWriter.WriteObject> wrt : writerFactories) {
writer = wrt.tryCreate(manifest, this);
if (writer != null) {
jsonWriters.put(manifest, writer);
return writer;
}
}
if (manifest instanceof Class<?> == false) {
return null;
}
Class<?> found = writerMap.get(manifest);
if (found != null) {
return jsonWriters.get(found);
}
Class<?> container = (Class<?>) manifest;
final ArrayList<Class<?>> signatures = new ArrayList<Class<?>>();
findAllSignatures(container, signatures);
for (final Class<?> sig : signatures) {
writer = jsonWriters.get(sig);
if (writer != null) {
writerMap.putIfAbsent(container, sig);
return writer;
}
}
return null;
}
/**
* Try to find registered reader for provided type.
* If reader is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative readers.
* <p>
* If you wish to use alternative reader for specific type, register it manually with something along the lines of
* <pre>
* DslJson dslJson = ...
* dslJson.registerReader(Interface.class, dslJson.tryFindReader(Implementation.class));
* </pre>
*
* @param manifest specified type
* @return found reader for specified type
*/
public JsonReader.ReadObject<?> tryFindReader(final Type manifest) {
JsonReader.ReadObject found = readers.get(manifest);
if (found != null) return found;
for (ConverterFactory<JsonReader.ReadObject> rdr : readerFactories) {
found = rdr.tryCreate(manifest, this);
if (found != null) {
readers.put(manifest, found);
return found;
}
}
return null;
}
/**
* Try to find registered binder for provided type.
* If binder is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative binders.
* <p>
* If you wish to use alternative binder for specific type, register it manually with something along the lines of
* <pre>
* DslJson dslJson = ...
* dslJson.registerBinder(Interface.class, dslJson.tryFindBinder(Implementation.class));
* </pre>