firm 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,733 @@
1
+ # FIRM::Serializable - serializable mixin
2
+ # Copyright (c) M.J.N. Corino, The Netherlands
3
+
4
+
5
+ require 'set'
6
+
7
+ module FIRM
8
+
9
+ # Mixin module providing (de-)serialization support for user defined classes.
10
+ module Serializable
11
+
12
+ class Exception < RuntimeError; end
13
+
14
+ class Property
15
+ def initialize(klass, prop, proc=nil, force: false, handler: nil, &block)
16
+ ::Kernel.raise ArgumentError, "Invalid property id [#{prop}]" unless ::String === prop || ::Symbol === prop
17
+ ::Kernel.raise ArgumentError, "Duplicate property id [#{prop}]" if klass.has_serializer_property?(prop)
18
+ @klass = klass
19
+ @id = prop.to_sym
20
+ @forced = force
21
+ if block || handler
22
+ if handler
23
+ ::Kernel.raise ArgumentError,
24
+ "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler
25
+ if handler.is_a?(::Proc)
26
+ ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
27
+ @getter = ->(obj) { handler.call(@id, obj) }
28
+ @setter = ->(obj, val) { handler.call(@id, obj, val) }
29
+ else
30
+ @getter = ->(obj) { obj.send(handler, @id) }
31
+ @setter = ->(obj, val) { obj.send(handler, @id, val) }
32
+ end
33
+ else
34
+ # any property block MUST accept 2 or 3 args; property name, instance and value (for setter)
35
+ ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
36
+ @getter = ->(obj) { block.call(@id, obj) }
37
+ @setter = ->(obj, val) { block.call(@id, obj, val) }
38
+ end
39
+ elsif proc
40
+ ::Kernel.raise ArgumentError,
41
+ "Invalid property proc #{proc} for #{prop}" unless ::Proc === proc || ::Symbol === proc
42
+ if ::Proc === proc
43
+ # any property proc should be callable with a single arg (instance)
44
+ @getter = proc
45
+ # a property proc combining getter/setter functionality should accept a single or more args (instance + value)
46
+ @setter = (proc.arity == -2) ? proc : nil
47
+ else
48
+ @getter = ->(obj) { obj.send(proc) }
49
+ @setter = ->(obj, val) { obj.send(proc, val) }
50
+ end
51
+ end
52
+ end
53
+
54
+ attr_reader :id
55
+
56
+ def serialize(obj, data, excludes)
57
+ unless excludes.include?(@id)
58
+ val = getter.call(obj)
59
+ unless Serializable === val && val.serialize_disabled? && !@forced
60
+ data[@id] = case val
61
+ when ::Array
62
+ val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) }
63
+ when ::Set
64
+ ::Set.new(val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) })
65
+ else
66
+ val
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def deserialize(obj, data)
73
+ if data.has_key?(@id)
74
+ setter.call(obj, data[@id])
75
+ end
76
+ end
77
+
78
+ def get(obj)
79
+ getter.call(obj)
80
+ end
81
+
82
+ def get_method(id)
83
+ begin
84
+ @klass.instance_method(id)
85
+ rescue NameError
86
+ nil
87
+ end
88
+ end
89
+ private :get_method
90
+
91
+ def getter
92
+ unless @getter
93
+ inst_meth = get_method(@id)
94
+ inst_meth = get_method("get_#{@id}") unless inst_meth
95
+ if inst_meth
96
+ @getter = ->(obj) { inst_meth.bind(obj).call }
97
+ else
98
+ return self.method(:getter_fail)
99
+ end
100
+ end
101
+ @getter
102
+ end
103
+ private :getter
104
+
105
+ def setter
106
+ unless @setter
107
+ inst_meth = get_method("#{@id}=")
108
+ inst_meth = get_method("set_#{@id}") unless inst_meth
109
+ unless inst_meth
110
+ im = get_method(@id)
111
+ if im && im.arity == -1
112
+ inst_meth = im
113
+ else
114
+ inst_meth = nil
115
+ end
116
+ end
117
+ if inst_meth
118
+ @setter = ->(obj, val) { inst_meth.bind(obj).call(val) }
119
+ else
120
+ return self.method(:setter_noop)
121
+ end
122
+ end
123
+ @setter
124
+ end
125
+ private :setter
126
+
127
+ def getter_fail(_obj)
128
+ ::Kernel.raise Serializable::Exception, "Missing getter for property #{@id} of #{@klass}"
129
+ end
130
+ private :getter_fail
131
+
132
+ def setter_noop(_, _)
133
+ # do nothing
134
+ end
135
+ private :setter_noop
136
+ end
137
+
138
+ # Serializable unique ids.
139
+ # This class makes sure to maintain uniqueness across serialization/deserialization cycles
140
+ # and keeps all shared instances within a single (serialized/deserialized) object set in
141
+ # sync.
142
+ class ID; end
143
+
144
+ class << self
145
+
146
+ def serializables
147
+ @serializables ||= ::Set.new
148
+ end
149
+
150
+ def formatters
151
+ @formatters ||= {}
152
+ end
153
+ private :formatters
154
+
155
+ # Registers a serialization formatting engine
156
+ # @param [Symbol,String] format format id
157
+ # @param [Object] engine formatting engine
158
+ def register(format, engine)
159
+ if formatters.has_key?(format.to_s.downcase)
160
+ ::Kernel.raise ArgumentError,
161
+ "Duplicate serialization formatter registration for #{format}"
162
+ end
163
+ formatters[format.to_s.downcase] = engine
164
+ end
165
+
166
+ # Return a serialization formatting engine
167
+ # @param [Symbol,String] format format id
168
+ # @return [Object] formatting engine
169
+ def [](format)
170
+ ::Kernel.raise ArgumentError, "Format #{format} is not supported." unless formatters.has_key?(format.to_s.downcase)
171
+ formatters[format.to_s.downcase]
172
+ end
173
+
174
+ # Return the default output format symbol id (:json, :yaml, :xml).
175
+ # By default returns :json.
176
+ # @return [Symbol]
177
+ def default_format
178
+ @default_format ||= :json
179
+ end
180
+
181
+ # Set the default output format.
182
+ # @param [Symbol] format Output format id. By default :json, :yaml and :xml (if nokogiri gem is installed) are supported.
183
+ # @return [Symbol] default format
184
+ def default_format=(format)
185
+ @default_format = format
186
+ end
187
+
188
+ end
189
+
190
+ # This module provides alias (de-)serialization management functionality for
191
+ # output engines that do not provide this support out of the box.
192
+ module AliasManagement
193
+
194
+ TLS_ANCHOR_OBJECTS_KEY = :firm_anchors_objects.freeze
195
+ private_constant :TLS_ANCHOR_OBJECTS_KEY
196
+
197
+ TLS_ALIAS_STACK_KEY = :firm_anchor_reference_stack.freeze
198
+ private_constant :TLS_ALIAS_STACK_KEY
199
+
200
+ def anchor_object_registry_stack
201
+ ::Thread.current[TLS_ANCHOR_OBJECTS_KEY] ||= []
202
+ end
203
+ private :anchor_object_registry_stack
204
+
205
+ def start_anchor_object_registry
206
+ anchor_object_registry_stack.push({})
207
+ end
208
+
209
+ def clear_anchor_object_registry
210
+ anchor_object_registry_stack.pop
211
+ end
212
+
213
+ def anchor_object_registry
214
+ anchor_object_registry_stack.last
215
+ end
216
+ private :anchor_object_registry
217
+
218
+ def class_anchor_objects(klass)
219
+ anchor_object_registry[klass] ||= {}
220
+ end
221
+ private :class_anchor_objects
222
+
223
+ # Registers a new anchor object.
224
+ # @param [Object] object anchor instance
225
+ # @param [Object] data serialized property collection object
226
+ # @return [Object] serialized property collection object
227
+ def register_anchor_object(object, data)
228
+ anchors = class_anchor_objects(object.class)
229
+ raise Serializable::Exception, "Duplicate anchor creation for #{object}" if anchors.has_key?(object.object_id)
230
+ anchors[object.object_id] = data
231
+ end
232
+
233
+ # Returns true if the object has an anchor registration, false otherwise.
234
+ # @return [Boolean]
235
+ def anchored?(object)
236
+ class_anchor_objects(object.class).has_key?(object.object_id)
237
+ end
238
+
239
+ # Returns the anchor id if anchored, nil otherwise.
240
+ # @param [Object] object anchor instance
241
+ # @return [Integer, nil]
242
+ def get_anchor(object)
243
+ anchored?(object) ? object.object_id : nil
244
+ end
245
+
246
+ # Retrieves the anchor serialization collection data for an anchored object.
247
+ # Returns nil if the object is not anchored.
248
+ # @return [nil,Object]
249
+ def get_anchor_data(object)
250
+ anchors = class_anchor_objects(object.class)
251
+ anchors[object.object_id]
252
+ end
253
+
254
+ def anchor_references_stack
255
+ ::Thread.current[TLS_ALIAS_STACK_KEY] ||= []
256
+ end
257
+ private :anchor_references_stack
258
+
259
+ def start_anchor_references
260
+ anchor_references_stack.push({})
261
+ end
262
+
263
+ def clear_anchor_references
264
+ anchor_references_stack.pop
265
+ end
266
+
267
+ def anchor_references
268
+ anchor_references_stack.last
269
+ end
270
+ private :anchor_references
271
+
272
+ def class_anchor_references(klass)
273
+ anchor_references[klass] ||= {}
274
+ end
275
+ private :class_anchor_references
276
+
277
+ # Registers a restored anchor object and it's ID.
278
+ # @param [Integer] id anchor ID
279
+ # @param [Object] object anchor instance
280
+ # @return [Object] anchor instance
281
+ def restore_anchor(id, object)
282
+ class_anchor_references(object.class)[id] = object
283
+ end
284
+
285
+ # Returns true if the anchor object for the given class and id has been restored, false otherwise.
286
+ # @param [Class] klass aliasable class of the anchor instance
287
+ # @param [Integer] id anchor id
288
+ # @return [Boolean]
289
+ def restored?(klass, id)
290
+ class_anchor_references(klass).has_key?(id)
291
+ end
292
+
293
+ # Resolves a referenced anchor instance.
294
+ # Returns the instance if found, nil otherwise.
295
+ # @param [Class] klass aliasable class of the anchor instance
296
+ # @param [Integer] id anchor id
297
+ # @return [nil,Object]
298
+ def resolve_anchor(klass, id)
299
+ class_anchor_references(klass)[id]
300
+ end
301
+
302
+ end
303
+
304
+ # Mixin module for classes that get FIRM::Serializable included.
305
+ # This module is used to extend the class methods of the serializable class.
306
+ module SerializeClassMethods
307
+
308
+ # Adds (a) serializable property(-ies) for instances of his class (and derived classes)
309
+ # @overload property(*props, force: false)
310
+ # Specifies one or more serialized properties.
311
+ # The serialization framework will determine the availability of setter and getter methods
312
+ # automatically by looking for methods <code>"#{prop_id}=(v)"</code>, <code>"#set_{prop_id}(v)"</code> or <code>"#{prop_id}(v)"</code>
313
+ # for setters and <code>"#{prop_id}()"</code> or <code>"#get_{prop_id}"</code> for getters.
314
+ # @param [Symbol,String] props one or more ids of serializable properties
315
+ # @param [Boolean] force overrides any #disable_serialize for the properties specified
316
+ # @return [void]
317
+ # @overload property(hash, force: false)
318
+ # Specifies one or more serialized properties with associated setter/getter method ids/procs/lambda-s.
319
+ # @example
320
+ # property(
321
+ # prop_a: ->(obj, *val) {
322
+ # obj.my_prop_a_setter(val.first) unless val.empty?
323
+ # obj.my_prop_a_getter
324
+ # },
325
+ # prop_b: Proc.new { |obj, *val|
326
+ # obj.my_prop_b_setter(val.first) unless val.empty?
327
+ # obj.my_prop_b_getter
328
+ # },
329
+ # prop_c: :serialization_method)
330
+ # Procs with setter support MUST accept 1 or 2 arguments (1 for getter, 2 for setter) where the first
331
+ # argument will always be the property owner's object instance and the second (in case of a setter proc) the
332
+ # value to restore.
333
+ # @note Use `*val` to specify the optional value argument for setter requests instead of `val=nil`
334
+ # to be able to support setting explicit nil values.
335
+ # @param [Hash] hash a hash of pairs of property ids and getter/setter procs
336
+ # @param [Boolean] force overrides any #disable_serialize for the properties specified
337
+ # @return [void]
338
+ # @overload property(*props, force: false, handler: nil, &block)
339
+ # Specifies one or more serialized properties with a getter/setter handler proc/method/block.
340
+ # The getter/setter proc or block should accept either 2 (property id and object for getter) or 3 arguments
341
+ # (property id, object and value for setter) and is assumed to handle getter/setter requests
342
+ # for all specified properties.
343
+ # The getter/setter method should accept either 1 (property id for getter) or 2 arguments
344
+ # (property id and value for setter) and is assumed to handle getter/setter requests
345
+ # for all specified properties.
346
+ # @example
347
+ # property(:property_a, :property_b, :property_c) do |id, obj, *val|
348
+ # case id
349
+ # when :property_a
350
+ # ...
351
+ # when :property_b
352
+ # ...
353
+ # when :property_c
354
+ # ...
355
+ # end
356
+ # end
357
+ # @note Use `*val` to specify the optional value argument for setter requests instead of `val=nil`
358
+ # to be able to support setting explicit nil values.
359
+ # @param [Symbol,String] props one or more ids of serializable properties
360
+ # @param [Boolean] force overrides any #disable_serialize for the properties specified
361
+ # @yieldparam [Symbol,String] id property id
362
+ # @yieldparam [Object] obj object instance
363
+ # @yieldparam [Object] val optional property value to set in case of setter request
364
+ # @return [void]
365
+ def property(*props, **kwargs, &block)
366
+ forced = !!kwargs.delete(:force)
367
+ if block || kwargs[:handler]
368
+ props.each do |prop|
369
+ serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], &block)
370
+ end
371
+ else
372
+ props.flatten.each do |prop|
373
+ if ::Hash === prop
374
+ prop.each_pair do |pn, pp|
375
+ serializer_properties << Property.new(self, pn, pp, force: forced)
376
+ end
377
+ else
378
+ serializer_properties << Property.new(self, prop, force: forced)
379
+ end
380
+ end
381
+ unless kwargs.empty?
382
+ kwargs.each_pair do |pn, pp|
383
+ serializer_properties << Property.new(self, pn, pp, force: forced)
384
+ end
385
+ end
386
+ end
387
+ end
388
+ alias :properties :property
389
+ alias :contains :property
390
+
391
+ # Excludes a serializable property for instances of this class.
392
+ # (mostly/only useful to exclude properties from base classes which
393
+ # do not require serialization for derived class)
394
+ # @param [Symbol,String] props one or more ids of serializable properties
395
+ # @return [void]
396
+ def excluded_property(*props)
397
+ excluded_serializer_properties.merge props.flatten.collect { |prop| prop }
398
+ end
399
+ alias :excluded_properties :excluded_property
400
+ alias :excludes :excluded_property
401
+
402
+ # Defines a finalizer method/proc/block to be called after all properties
403
+ # have been deserialized and restored.
404
+ # Procs or blocks will be called with the deserialized object as the single argument.
405
+ # Unbound methods will be bound to the deserialized object before calling.
406
+ # Explicitly specifying nil will undefine the finalizer.
407
+ # @param [Symbol, String, Proc, UnboundMethod, nil] meth name of instance method, proc or method to call for finalizing
408
+ # @yieldparam [Object] obj deserialized object to finalize
409
+ # @return [void]
410
+ def define_deserialize_finalizer(meth=nil, &block)
411
+ if block and meth.nil?
412
+ # the given block should expect and use the given object instance
413
+ set_deserialize_finalizer(block)
414
+ elsif meth and block.nil?
415
+ h_meth = case meth
416
+ when ::Symbol, ::String
417
+ Serializable::MethodResolver.new(self, meth)
418
+ when ::Proc
419
+ # check arity == 1
420
+ if meth.arity != 1
421
+ Kernel.raise ArgumentError,
422
+ "Deserialize finalizer Proc should expect a single argument",
423
+ caller
424
+ end
425
+ meth
426
+ when ::UnboundMethod
427
+ # check arity == 0
428
+ if meth.arity>0
429
+ Kernel.raise ArgumentError,
430
+ "Deserialize finalizer method should not expect any argument",
431
+ caller
432
+ end
433
+ ->(obj) { meth.bind(obj).call }
434
+ else
435
+ Kernel.raise ArgumentError,
436
+ "Specify deserialize finalizer with a method, name, proc OR block",
437
+ caller
438
+ end
439
+ set_deserialize_finalizer(h_meth)
440
+ elsif meth.nil? and block.nil?
441
+ set_deserialize_finalizer(nil)
442
+ else
443
+ Kernel.raise ArgumentError,
444
+ "Specify deserialize finalizer with a method, name, proc OR block",
445
+ caller
446
+ end
447
+ nil
448
+ end
449
+ alias :deserialize_finalizer :define_deserialize_finalizer
450
+
451
+ # Deserializes object from source data
452
+ # @param [IO,String] source source data (String or IO(-like object))
453
+ # @param [Symbol, String] format data format of source
454
+ # @return [Object] deserialized object
455
+ def deserialize(source, format: Serializable.default_format)
456
+ Serializable.deserialize(source, format: format)
457
+ end
458
+
459
+ end
460
+
461
+ # Mixin module for classes that get FIRM::Serializable included.
462
+ # This module is used to extend the instance methods of the serializable class.
463
+ module SerializeInstanceMethods
464
+
465
+ # Serialize this object
466
+ # @overload serialize(pretty: false, format: Serializable.default_format)
467
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
468
+ # @param [Symbol,String] format specifies output format
469
+ # @return [String] serialized data
470
+ # @overload serialize(io, pretty: false, format: Serializable.default_format)
471
+ # @param [IO] io output stream to write serialized data to
472
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
473
+ # @param [Symbol,String] format specifies output format
474
+ # @return [IO]
475
+ def serialize(io = nil, pretty: false, format: Serializable.default_format)
476
+ Serializable[format].dump(self, io, pretty: pretty)
477
+ end
478
+
479
+ # Returns true if regular serialization for this object has been disabled, false otherwise (default).
480
+ # Disabled serialization can be overridden for single objects (not objects maintained in property containers
481
+ # like arrays and sets).
482
+ # @return [true,false]
483
+ def serialize_disabled?
484
+ !!@serialize_disabled # true for any value but false
485
+ end
486
+
487
+ # Disables serialization for this object as a single property or as part of a property container
488
+ # (array or set).
489
+ # @return [void]
490
+ def disable_serialize
491
+ # by default unset (nil) so serializing enabled
492
+ @serialize_disabled = true
493
+ end
494
+
495
+ # @!method for_serialize(hash, excludes = Set.new)
496
+ # Serializes the properties of a serializable instance to the given hash
497
+ # except when the property id is included in excludes.
498
+ # @param [Object] hash hash-like property serialization container
499
+ # @param [Set] excludes set with excluded property ids
500
+ # @return [Object] hash-like property serialization container
501
+
502
+ # @!method from_serialized(hash)
503
+ # Restores the properties of a deserialized instance.
504
+ # @param [Object] hash hash-like property deserialization container
505
+ # @return [self]
506
+
507
+ # #!method finalize_from_serialized()
508
+ # Finalizes the instance initialization after property restoration.
509
+ # Calls any user defined finalizer.
510
+ # @return [self]
511
+
512
+ end
513
+
514
+ # Serialize the given object
515
+ # @overload serialize(obj, pretty: false, format: Serializable.default_format)
516
+ # @param [Object] obj object to serialize
517
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
518
+ # @param [Symbol,String] format specifies output format
519
+ # @return [String] serialized data
520
+ # @overload serialize(obj, io, pretty: false, format: Serializable.default_format)
521
+ # @param [Object] obj object to serialize
522
+ # @param [IO] io output stream to write serialized data to
523
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
524
+ # @param [Symbol,String] format specifies output format
525
+ # @return [IO]
526
+ def self.serialize(obj, io = nil, pretty: false, format: Serializable.default_format)
527
+ self[format].dump(obj, io, pretty: pretty)
528
+ end
529
+
530
+ # Deserializes object from source data
531
+ # @param [IO,String] source source data (stream)
532
+ # @param [Symbol, String] format data format of source
533
+ # @return [Object] deserialized object
534
+ def self.deserialize(source, format: Serializable.default_format)
535
+ self[format].load(::IO === source || source.respond_to?(:read) ? source.read : source)
536
+ end
537
+
538
+ # Small utility class for delayed method resolving
539
+ class MethodResolver
540
+ def initialize(klass, mtd_id, default=false)
541
+ @klass = klass
542
+ @mtd_id = mtd_id
543
+ @default = default
544
+ end
545
+
546
+ def resolve
547
+ m = @klass.instance_method(@mtd_id) rescue nil
548
+ if m
549
+ # check arity == 0
550
+ if m.arity>0
551
+ unless @default
552
+ Kernel.raise ArgumentError,
553
+ "Deserialize finalizer method #{@klass}#{@mtd_id} should not expect any argument",
554
+ caller
555
+ end
556
+ else
557
+ return ->(obj) { m.bind(obj).call }
558
+ end
559
+ end
560
+ nil
561
+ end
562
+ end
563
+
564
+
565
+ def self.included(base)
566
+ ::Kernel.raise RuntimeError, "#{self} should only be included in classes" if base.instance_of?(::Module)
567
+
568
+ ::Kernel.raise RuntimeError, "#{self} should be included only once in #{base}" if Serializable.serializables.include?(base.name)
569
+
570
+ # register as serializable class
571
+ Serializable.serializables << base
572
+
573
+ return if base == Serializable::ID # special case which does not need the rest
574
+
575
+ # provide serialized property definition support
576
+
577
+ # provide serialized classes with their own serialized properties (exclusion) list
578
+ # and a deserialization finalizer setter/getter
579
+ base.singleton_class.class_eval do
580
+ def serializer_properties
581
+ @serializer_props ||= []
582
+ end
583
+ def excluded_serializer_properties
584
+ @excluded_serializer_props ||= ::Set.new
585
+ end
586
+ def set_deserialize_finalizer(fin)
587
+ @finalize_from_deserialized = fin
588
+ end
589
+ private :set_deserialize_finalizer
590
+ def get_deserialize_finalizer
591
+ case @finalize_from_deserialized
592
+ when Serializable::MethodResolver
593
+ @finalize_from_deserialized = @finalize_from_deserialized.resolve
594
+ else
595
+ @finalize_from_deserialized
596
+ end
597
+ end
598
+ private :get_deserialize_finalizer
599
+ def find_deserialize_finalizer
600
+ get_deserialize_finalizer
601
+ end
602
+ end
603
+
604
+ base.class_eval do
605
+
606
+ # Initializes a newly allocated instance for subsequent deserialization (optionally initializing
607
+ # using the given data hash).
608
+ # The default implementation calls the standard #initialize method without arguments (default constructor)
609
+ # and leaves the property restoration to a subsequent call to the instance method #from_serialized(data).
610
+ # Classes that do not support a default constructor can override this class method and
611
+ # implement a custom initialization scheme.
612
+ # @param [Object] _data hash-like object containing deserialized property data (symbol keys)
613
+ # @return [Object] the initialized object
614
+ def init_from_serialized(_data)
615
+ initialize
616
+ self
617
+ end
618
+ protected :init_from_serialized
619
+
620
+ # Check if the class has the default deserialize finalizer method defined (a #create method
621
+ # without arguments). If so install that method as the deserialize finalizer.
622
+ set_deserialize_finalizer(Serializable::MethodResolver.new(self, :create, true))
623
+ end
624
+
625
+ # add class methods
626
+ base.extend(SerializeClassMethods)
627
+
628
+ # add instance property (de-)serialization methods for base class
629
+ base.class_eval <<~__CODE
630
+ def for_serialize(hash, excludes = ::Set.new)
631
+ #{base.name}.serializer_properties.each { |prop, h| prop.serialize(self, hash, excludes) }
632
+ hash
633
+ end
634
+ protected :for_serialize
635
+
636
+ def from_serialized(hash)
637
+ #{base.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
638
+ self
639
+ end
640
+ protected :from_serialized
641
+
642
+ def finalize_from_serialized
643
+ if (f = self.class.find_deserialize_finalizer)
644
+ f.call(self)
645
+ end
646
+ self
647
+ end
648
+ protected :finalize_from_serialized
649
+
650
+ def self.has_serializer_property?(id)
651
+ self.serializer_properties.any? { |p| p.id == id.to_sym }
652
+ end
653
+ __CODE
654
+ # add inheritance support
655
+ base.class_eval do
656
+ def self.inherited(derived)
657
+ # add instance property (de-)serialization methods for derived classes
658
+ derived.class_eval <<~__CODE
659
+ module SerializerMethods
660
+ def for_serialize(hash, excludes = ::Set.new)
661
+ hash = super(hash, excludes | #{derived.name}.excluded_serializer_properties)
662
+ #{derived.name}.serializer_properties.each { |prop| prop.serialize(self, hash, excludes) }
663
+ hash
664
+ end
665
+ protected :for_serialize
666
+
667
+ def from_serialized(hash)
668
+ #{derived.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
669
+ super(hash)
670
+ end
671
+ protected :from_serialized
672
+ end
673
+ include SerializerMethods
674
+ __CODE
675
+ derived.class_eval do
676
+ def self.has_serializer_property?(id)
677
+ self.serializer_properties.any? { |p| p.id == id.to_sym } || self.superclass.has_serializer_property?(id)
678
+ end
679
+ end
680
+ # add derived class support for deserialization finalizer
681
+ derived.singleton_class.class_eval <<~__CODE
682
+ def find_deserialize_finalizer
683
+ get_deserialize_finalizer || #{derived.name}.superclass.find_deserialize_finalizer
684
+ end
685
+ __CODE
686
+
687
+ # Check if the derived class has the default deserialize finalizer method defined (a #create method
688
+ # without arguments) defined. If so install that method as the deserialize finalizer (it is expected
689
+ # this method will call any superclass finalizer that may be defined).
690
+ derived.class_eval do
691
+ set_deserialize_finalizer(Serializable::MethodResolver.new(self, :create, true))
692
+ end
693
+
694
+ # register as serializable class
695
+ Serializable.serializables << derived
696
+ end
697
+ end
698
+
699
+ # add instance serialization method
700
+ base.include(SerializeInstanceMethods)
701
+ end
702
+
703
+ end # module Serializable
704
+
705
+
706
+ # Serialize the given object
707
+ # @overload serialize(obj, pretty: false, format: Serializable.default_format)
708
+ # @param [Object] obj object to serialize
709
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
710
+ # @param [Symbol,String] format specifies output format
711
+ # @return [String] serialized data
712
+ # @overload serialize(obj, io, pretty: false, format: Serializable.default_format)
713
+ # @param [Object] obj object to serialize
714
+ # @param [IO] io output stream (IO(-like object)) to write serialized data to
715
+ # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
716
+ # @param [Symbol,String] format specifies output format
717
+ # @return [IO]
718
+ def self.serialize(obj, io = nil, pretty: false, format: Serializable.default_format)
719
+ Serializable.serialize(obj, io, pretty: pretty, format: format)
720
+ end
721
+
722
+ # Deserializes object from source data
723
+ # @param [IO,String] source source data (String or IO(-like object))
724
+ # @param [Symbol, String] format data format of source
725
+ # @return [Object] deserialized object
726
+ def self.deserialize(source, format: Serializable.default_format)
727
+ Serializable.deserialize(source, format: format)
728
+ end
729
+
730
+ end # module FIRM
731
+
732
+ Dir[File.join(__dir__, 'serializer', '*.rb')].each { |fnm| require "firm/serializer/#{File.basename(fnm)}" }
733
+ Dir[File.join(__dir__, 'serialize', '*.rb')].each { |fnm| require "firm/serialize/#{File.basename(fnm)}" }