firm 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ # FIRM::Serializer - Ruby core serializer extensions
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ module FIRM
6
+ module Serializable
7
+
8
+ # FIRM::Serializable is not included for the Ruby core classes as the would
9
+ # also extend these classes with the engine specific extension that we do not
10
+ # need nor want here.
11
+ # Instead we define the (slim) mixin module CoreExt to extend the non-POD core classes.
12
+ # POD classes (nil, boolean, integer, float) cannot be serialized separately but only
13
+ # as properties of complex serializables.
14
+ module CoreExt
15
+ def serialize(io = nil, pretty: false, format: FIRM::Serializable.default_format)
16
+ FIRM::Serializable[format].dump(self, io, pretty: pretty)
17
+ end
18
+
19
+ def self.included(base)
20
+ base.class_eval do
21
+ # Deserializes object from source data
22
+ # @param [IO,String] source source data (String or IO(-like object))
23
+ # @param [Symbol, String] format data format of source
24
+ # @return [Object] deserialized object
25
+ def self.deserialize(source, format: Serializable.default_format)
26
+ Serializable.deserialize(source, format: format)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ require 'set'
35
+ require 'ostruct'
36
+
37
+ [::Array, ::Hash, ::Struct, ::Range, ::Rational, ::Complex, ::Regexp, ::Set, ::OpenStruct, ::Time, ::Date, ::DateTime].each do |c|
38
+ c.include FIRM::Serializable::CoreExt
39
+ end
40
+
41
+ if ::Object.const_defined?(:BigDecimal)
42
+ ::BigDecimal.include FIRM::Serializable::CoreExt
43
+ end
@@ -0,0 +1,104 @@
1
+ # FIRM::Serializer - FIRM serializable ID class
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ module FIRM
6
+
7
+ module Serializable
8
+
9
+ class ID
10
+
11
+ include FIRM::Serializable
12
+
13
+ class << self
14
+
15
+ # Deserializes object from source data
16
+ # @param [IO,String] source source data (String or IO(-like object))
17
+ # @param [Symbol, String] format data format of source
18
+ # @return [Object] deserialized object
19
+ def deserialize(source, format: Serializable.default_format)
20
+ Serializable.deserialize(source, format: format)
21
+ end
22
+
23
+ end
24
+
25
+ # Serialize this object
26
+ # @overload serialize(pretty: false, format: Serializable.default_format)
27
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
28
+ # @param [Symbol,String] format specifies output format
29
+ # @return [String] serialized data
30
+ # @overload serialize(io, pretty: false, format: Serializable.default_format)
31
+ # @param [IO] io output stream to write serialized data to
32
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
33
+ # @param [Symbol,String] format specifies output format
34
+ # @return [IO]
35
+ def serialize(io = nil, pretty: false, format: Serializable.default_format)
36
+ Serializable[format].dump(self, io, pretty: pretty)
37
+ end
38
+
39
+ # Initializes a newly allocated instance for subsequent deserialization (optionally initializing
40
+ # using the given data hash).
41
+ # The default implementation calls the standard #initialize method without arguments (default constructor)
42
+ # and leaves the property restoration to a subsequent call to the instance method #from_serialized(data).
43
+ # Classes that do not support a default constructor can override this class method and
44
+ # implement a custom initialization scheme.
45
+ # @param [Object] _data hash-like object containing deserialized property data (symbol keys)
46
+ # @return [Object] the initialized object
47
+ def init_from_serialized(_data)
48
+ initialize
49
+ self
50
+ end
51
+ protected :init_from_serialized
52
+
53
+ # Noop for ID instances.
54
+ # @param [Object] hash hash-like property serialization container
55
+ # @param [Set] _excludes ignored
56
+ # @return [Object] property hash-like serialization container
57
+ def for_serialize(hash, _excludes = nil)
58
+ hash
59
+ end
60
+
61
+ protected :for_serialize
62
+
63
+ # Noop for ID instances.
64
+ # @param [Hash] _hash ignored
65
+ # @return [self]
66
+ def from_serialized(_hash)
67
+ # no deserializing necessary
68
+ self
69
+ end
70
+
71
+ protected :from_serialized
72
+
73
+ # Noop for ID instances.
74
+ # @return [self]
75
+ def finalize_from_serialized
76
+ # no finalization necessary
77
+ self
78
+ end
79
+
80
+ protected :finalize_from_serialized
81
+
82
+ # Always returns false for IDs.
83
+ # @return [Boolean]
84
+ def serialize_disabled?
85
+ false
86
+ end
87
+
88
+ def to_s
89
+ "FIRM::Serializable::ID<#{object_id}>"
90
+ end
91
+
92
+ def inspect
93
+ to_s
94
+ end
95
+
96
+ def to_i
97
+ object_id
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,394 @@
1
+ # FIRM::Serializer - shape serializer module
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ require 'json'
6
+ require 'json/add/date'
7
+ require 'json/add/date_time'
8
+ require 'json/add/range'
9
+ require 'json/add/regexp'
10
+ require 'json/add/struct'
11
+ require 'json/add/symbol'
12
+ require 'json/add/time'
13
+ require 'json/add/bigdecimal' if ::Object.const_defined?(:BigDecimal)
14
+ require 'json/add/rational'
15
+ require 'json/add/complex'
16
+ require 'json/add/set'
17
+ require 'json/add/ostruct'
18
+
19
+ module FIRM
20
+
21
+ module Serializable
22
+
23
+ module JSON
24
+
25
+ # Derived Hash class to use for deserialized JSON object data which
26
+ # supports using Symbol keys.
27
+ class ObjectHash < ::Hash
28
+ # Returns the object associated with given key.
29
+ # @param [String,Symbol] key key value
30
+ # @return [Object] associated object
31
+ # @see ::Hash#[]
32
+ def [](key)
33
+ super(key.to_s)
34
+ end
35
+ # Returns true if the given key exists in self otherwise false.
36
+ # @param [String,Symbol] key key value
37
+ # @return [Boolean]
38
+ # @see ::Hash#include?
39
+ def include?(key)
40
+ super(key.to_s)
41
+ end
42
+ alias member? include?
43
+ alias has_key? include?
44
+ alias key? include?
45
+ end
46
+
47
+ # Mixin module to patch singleton_clas of the Hash class to make Hash-es
48
+ # JSON creatable (#json_creatable? returns true).
49
+ module HashClassPatch
50
+ # Create a new Hash instance from deserialized JSON data.
51
+ # @param [Hash] object deserialized JSON object
52
+ # @return [Hash] restored Hash instance
53
+ def json_create(object)
54
+ object['data'].to_h
55
+ end
56
+ end
57
+
58
+ class << self
59
+ def serializables
60
+ set = ::Set.new( [::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::String, ::Array, ::Hash,
61
+ ::Date, ::DateTime, ::Range, ::Rational, ::Complex, ::Regexp, ::Struct, ::Symbol, ::Time, ::Set, ::OpenStruct])
62
+ set << ::BigDecimal if ::Object.const_defined?(:BigDecimal)
63
+ set
64
+ end
65
+
66
+ TLS_SAFE_DESERIALIZE_KEY = :firm_json_safe_deserialize.freeze
67
+ private_constant :TLS_SAFE_DESERIALIZE_KEY
68
+
69
+ TLS_PARSE_STACK_KEY = :firm_json_parse_stack.freeze
70
+ private_constant :TLS_PARSE_STACK_KEY
71
+
72
+ def safe_deserialize
73
+ ::Thread.current[TLS_SAFE_DESERIALIZE_KEY] ||= []
74
+ end
75
+ private :safe_deserialize
76
+
77
+ def start_safe_deserialize
78
+ safe_deserialize.push(true)
79
+ end
80
+
81
+ def end_safe_deserialize
82
+ safe_deserialize.pop
83
+ end
84
+
85
+ def parse_stack
86
+ ::Thread.current[TLS_PARSE_STACK_KEY] ||= []
87
+ end
88
+ private :parse_stack
89
+
90
+ def start_parse
91
+ parse_stack.push(safe_deserialize.pop)
92
+ end
93
+
94
+ def end_parse
95
+ unless (val = parse_stack.pop).nil?
96
+ safe_deserialize.push(val)
97
+ end
98
+ end
99
+
100
+ def safe_parsing?
101
+ !!parse_stack.last
102
+ end
103
+ end
104
+
105
+ def self.dump(obj, io=nil, pretty: false)
106
+ # obj.extend(HashInstancePatch) if obj.is_a?(::Hash)
107
+ begin
108
+ # initialize anchor registry
109
+ Serializable::Aliasing.start_anchor_object_registry
110
+ for_json = obj.respond_to?(:as_json) ? obj.as_json : obj
111
+ if pretty
112
+ if io || io.respond_to?(:write)
113
+ io.write(::JSON.pretty_generate(for_json))
114
+ io
115
+ else
116
+ ::JSON.pretty_generate(for_json)
117
+ end
118
+ else
119
+ ::JSON.dump(for_json, io)
120
+ end
121
+ ensure
122
+ # reset anchor registry
123
+ Serializable::Aliasing.clear_anchor_object_registry
124
+ end
125
+ end
126
+
127
+ def self.load(source)
128
+ begin
129
+ # initialize alias anchor restoration map
130
+ Serializable::Aliasing.start_anchor_references
131
+ # enable safe deserializing
132
+ self.start_safe_deserialize
133
+ ::JSON.parse!(source,
134
+ **{create_additions: true,
135
+ object_class: Serializable::JSON::ObjectHash})
136
+ ensure
137
+ # reset safe deserializing
138
+ self.end_safe_deserialize
139
+ # reset alias anchor restoration map
140
+ Serializable::Aliasing.clear_anchor_references
141
+ end
142
+ end
143
+
144
+
145
+ # extend serialization class methods
146
+ module SerializeClassMethods
147
+
148
+ def json_create(object)
149
+ data = object['data']
150
+ # deserializing (anchor) object or alias
151
+ if data.has_key?('*id')
152
+ if Serializable::Aliasing.restored?(self, data['*id'])
153
+ # resolving an already restored anchor for this alias
154
+ Serializable::Aliasing.resolve_anchor(self, data['*id'])
155
+ else
156
+ # in case of cyclic references JSON will restore aliases before the anchors
157
+ # so in this case we allocate an instance here and register it as
158
+ # the anchor; when the anchor is restored it will re-use this instance to initialize & restore
159
+ # the properties
160
+ Serializable::Aliasing.restore_anchor(data['*id'], self.allocate)
161
+ end
162
+ else
163
+ instance = if data.has_key?('&id')
164
+ anchor_id = data.delete('&id') # extract anchor id
165
+ if Serializable::Aliasing.restored?(self, anchor_id)
166
+ # in case of cyclic references an alias will already have restored the anchor instance
167
+ # (default constructed); retrieve that instance here for deserialization of properties
168
+ Serializable::Aliasing.resolve_anchor(self, anchor_id)
169
+ else
170
+ # restore the anchor here with a newly allocated instance
171
+ Serializable::Aliasing.restore_anchor(anchor_id, self.allocate)
172
+ end
173
+ else
174
+ self.allocate
175
+ end
176
+ instance.__send__(:init_from_serialized, data)
177
+ .__send__(:from_serialized, data)
178
+ .__send__(:finalize_from_serialized)
179
+ end
180
+ end
181
+
182
+ end
183
+
184
+ # extend instance serialization methods
185
+ module SerializeInstanceMethods
186
+
187
+ def as_json(*)
188
+ json_data = {
189
+ ::JSON.create_id => self.class.name
190
+ }
191
+ if (anchor = Serializable::Aliasing.get_anchor(self))
192
+ anchor_data = Serializable::Aliasing.get_anchor_data(self)
193
+ # retroactively insert the anchor in the anchored instance's serialization data
194
+ anchor_data['&id'] = anchor unless anchor_data.has_key?('&id')
195
+ json_data["data"] = {
196
+ '*id' => anchor
197
+ }
198
+ else
199
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
200
+ # references)
201
+ json_data['data'] = for_serialize(Serializable::Aliasing.register_anchor_object(self, {}))
202
+ json_data['data'].transform_values! { |v| v.respond_to?(:as_json) ? v.as_json : v }
203
+ end
204
+ json_data
205
+ end
206
+
207
+ end
208
+
209
+ end
210
+
211
+ module Aliasing
212
+ class << self
213
+ include Serializable::AliasManagement
214
+ end
215
+ end
216
+
217
+ # extend serialization class methods
218
+ module SerializeClassMethods
219
+
220
+ include JSON::SerializeClassMethods
221
+
222
+ end
223
+
224
+ # extend instance serialization methods
225
+ module SerializeInstanceMethods
226
+
227
+ include JSON::SerializeInstanceMethods
228
+
229
+ end
230
+
231
+ class ID
232
+ include JSON::SerializeInstanceMethods
233
+ class << self
234
+ include JSON::SerializeClassMethods
235
+ end
236
+ end
237
+
238
+ register(:json, JSON)
239
+
240
+ end
241
+
242
+ end
243
+
244
+ module ::JSON
245
+ class << self
246
+
247
+ alias :pre_firm_parse! :parse!
248
+ def parse!(*args, **kwargs)
249
+ begin
250
+ # setup parsing stack for safe or normal deserializing
251
+ # the double bracketing provided from FIRM::Serializable::JSON#load and here
252
+ # makes sure to support both nested Wx::SF deserializing as well as nested
253
+ # hybrid deserializing (Wx::SF -> common JSON -> ...)
254
+ FIRM::Serializable::JSON.start_parse
255
+ pre_firm_parse!(*args, **kwargs)
256
+ ensure
257
+ # reset parsing stack
258
+ FIRM::Serializable::JSON.end_parse
259
+ end
260
+ end
261
+
262
+ end
263
+ end
264
+
265
+ class ::Class
266
+
267
+ # override this to be able to do safe deserializing
268
+ def json_creatable?
269
+ if FIRM::Serializable::JSON.safe_parsing?
270
+ return false unless FIRM::Serializable::JSON.serializables.include?(self) ||
271
+ FIRM::Serializable.serializables.include?(self) ||
272
+ ::Struct > self
273
+ end
274
+ respond_to?(:json_create)
275
+ end
276
+
277
+ end
278
+
279
+ class ::Array
280
+ def as_json(*)
281
+ collect { |e| e.respond_to?(:as_json) ? e.as_json : e }
282
+ end
283
+ end
284
+
285
+ class ::Hash
286
+ class << self
287
+ include FIRM::Serializable::JSON::HashClassPatch
288
+ end
289
+ def as_json(*)
290
+ {
291
+ ::JSON.create_id => self.class.name,
292
+ 'data' => collect { |k,v| [k.respond_to?(:as_json) ? k.as_json : k, v.respond_to?(:as_json) ? v.as_json : v] }
293
+ }
294
+ end
295
+ end
296
+
297
+ class ::Set
298
+ def as_json(*)
299
+ {
300
+ JSON.create_id => self.class.name,
301
+ 'a' => to_a.as_json,
302
+ }
303
+ end
304
+ end
305
+
306
+ class ::Struct
307
+ class << self
308
+ def json_create(object)
309
+ # deserializing (anchor) object or alias
310
+ if object.has_key?('*id')
311
+ if FIRM::Serializable::Aliasing.restored?(self, object['*id'])
312
+ # resolving an already restored anchor for this alias
313
+ FIRM::Serializable::Aliasing.resolve_anchor(self, object['*id'])
314
+ else
315
+ # in case of cyclic references JSON will restore aliases before the anchors
316
+ # so in this case we allocate an instance here and register it as
317
+ # the anchor; when the anchor is restored it will re-use this instance to restore
318
+ # the properties
319
+ FIRM::Serializable::Aliasing.restore_anchor(object['*id'], self.allocate)
320
+ end
321
+ else
322
+ if object.has_key?('&id')
323
+ anchor_id = object['&id'] # extract anchor id
324
+ instance = if FIRM::Serializable::Aliasing.restored?(self, anchor_id)
325
+ # in case of cyclic references an alias will already have restored the anchor instance
326
+ # (default constructed); retrieve that instance here for deserialization of properties
327
+ FIRM::Serializable::Aliasing.resolve_anchor(self, anchor_id)
328
+ else
329
+ # restore the anchor here with a newly instantiated instance
330
+ FIRM::Serializable::Aliasing.restore_anchor(anchor_id, self.allocate)
331
+ end
332
+ instance.__send__(:initialize, *object['v'])
333
+ instance
334
+ else
335
+ self.new(*object['v'])
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ def as_json(*)
342
+ klass = self.class.name
343
+ klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
344
+ # {
345
+ # JSON.create_id => klass,
346
+ # 'v' => values.as_json,
347
+ # }
348
+ json_data = {
349
+ ::JSON.create_id => klass
350
+ }
351
+ if (anchor = FIRM::Serializable::Aliasing.get_anchor(self))
352
+ anchor_data = FIRM::Serializable::Aliasing.get_anchor_data(self)
353
+ # retroactively insert the anchor in the anchored instance's serialization data
354
+ anchor_data['&id'] = anchor unless anchor_data.has_key?('&id')
355
+ json_data['*id'] = anchor
356
+ else
357
+ # register anchor object **before** serializing properties to properly handle cycling (bidirectional
358
+ # references)
359
+ FIRM::Serializable::Aliasing.register_anchor_object(self, json_data)
360
+ json_data['v'] = values.as_json
361
+ end
362
+ json_data
363
+ end
364
+ end
365
+
366
+ class ::OpenStruct
367
+ def as_json(*)
368
+ klass = self.class.name
369
+ klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
370
+ {
371
+ JSON.create_id => klass,
372
+ 't' => table.as_json,
373
+ }
374
+ end
375
+ end
376
+
377
+ # fix flawed JSON serializing
378
+ class ::DateTime
379
+
380
+ def as_json(*)
381
+ {
382
+ JSON.create_id => self.class.name,
383
+ 'y' => year,
384
+ 'm' => month,
385
+ 'd' => day,
386
+ 'H' => hour,
387
+ 'M' => min,
388
+ 'S' => sec_fraction.to_f+sec,
389
+ 'of' => offset.to_s,
390
+ 'sg' => start,
391
+ }
392
+ end
393
+
394
+ end