Class: SDL4R::Serializer

Inherits:
Object
  • Object
show all
Defined in:
lib/sdl4r/serializer.rb

Overview

Allows to serialize/deserialize between SDL and Ruby objects.

Classes which need to implement custom serialization/deserialization should define the to_sdl() and from_sdl() methods as follows:

class MyClass

  attr_accessor :firstname

  def from_sdl(tag, serializer = Serializer.new)
    self.firstname = tag.attribute("name")
    self
  end

  def to_sdl(serializer = Serializer.new, tag = serializer.new_tag(nil, self, nil))
    tag.set_attribute("name", self.firstname)
    tag
  end

end

Custom serialization (using to_sdl()) or deserialization (using from_sdl()) can be more efficient than the standard one as its logic is more straightforward).

Author

Philippe Vosges

Defined Under Namespace

Classes: Ref

Constant Summary collapse

OBJECT_ID_ATTR_NAME =
'oid'
OBJECT_REF_ATTR_NAME =
'oref'
@@DEFAULT_OPTIONS =
{
  :omit_nil_properties => false,
  :default_namespace => ""
}

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Serializer

options:

[:omit_nil_properties]
if true, nil object properties are not exported to the serialized SDL (default: false)
[:default_namespace]
the default namespace of the generated tags (default: ""). This namespace doesn't apply to
attributes.


73
74
75
76
77
78
# File 'lib/sdl4r/serializer.rb', line 73

def initialize(options = {})
  @options = {}.merge(@@DEFAULT_OPTIONS).merge!(options)
  @next_oid = 1
  @ref_by_oid = {}
  @ref_by_object = {}
end

Instance Method Details

#deserialize(tag, o = new_plain_object(tag)) ⇒ Object



363
364
365
366
367
368
369
370
371
372
# File 'lib/sdl4r/serializer.rb', line 363

def deserialize(tag, o = new_plain_object(tag))
  if o.respond_to? :from_sdl
    o = o.from_sdl(tag, self)
    
  else
    o = deserialize_object(tag, o)
  end

  o
end

#deserialize_from_attributes(tag, o) ⇒ Object



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/sdl4r/serializer.rb', line 412

def deserialize_from_attributes(tag, o)
  tag.attributes do |attribute_namespace, attribute_name, attribute_value|
    if attribute_namespace == '' and attribute_name == OBJECT_REF_ATTR_NAME
      # ignore this technical attribute

    elsif attribute_namespace == '' and attribute_name == OBJECT_ID_ATTR_NAME
      # reference and make no impact on 'o'
      ref = reference_object(o, attribute_value)
      ref.inc

    else
      set_serializable_property(o, attribute_name, attribute_value)
    end
  end
end

#deserialize_from_child_tags(tag, o, handle_anonymous) ⇒ Object

handle_anonymous

if true, this method only handles anonymous child tags, if false, it only handles named child tags



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
# File 'lib/sdl4r/serializer.rb', line 440

def deserialize_from_child_tags(tag, o, handle_anonymous)
  # Group the homonym tags together
  child_tags_by_name = {}
  tag.children do |child|
    if handle_anonymous == (child.namespace == '' and child.name == SDL4R::ANONYMOUS_TAG_NAME)
      property_name = get_deserialized_property_name(child, tag, o)
      homonymous_children = (child_tags_by_name[property_name] ||= [])
      homonymous_children << child
    end
  end

  child_tags_by_name.each_pair do |property_name, homonymous_children|
    # Check wether this variable is assignable
    if o.is_a? Array or serializable_property?(o, property_name)
      property_values = []

      homonymous_children.each do |child|
        property_value = nil

        if child.has_attribute?(OBJECT_REF_ATTR_NAME)
          # Object reference
          ref = @ref_by_oid[child.attribute(OBJECT_REF_ATTR_NAME)]
          property_value = deserialize(child, ref.o) if ref

        elsif child.has_values? and not child.has_children? and not child.has_attributes?
          # If the object only has values (no children, no atttributes):
          #  then the values are the variable value
          property_value = child.values
          property_value = property_value[0] if property_value.length == 1

        else
          # Consider this tag as a plain object
          variable_name = "@#{property_name}"
          previous_value =
            (o.instance_variable_defined? variable_name) ?
              o.instance_variable_get(variable_name) :
              nil
          if previous_value.nil? or SDL4R::is_coercible?(previous_value)
            property_value = deserialize(child, new_plain_object(child, nil, o))
          else
            property_value = deserialize(child, new_plain_object(child, previous_value, o))
          end
        end

        property_values << property_value
      end

      if o.is_a? Array
        o.concat(property_values)
      elsif property_values.length == 1
        set_serializable_property(o, property_name, property_values[0])
      elsif property_values.length > 1
        set_serializable_property(o, property_name, property_values)
      end
    end
  end
end

#deserialize_from_values(tag, o) ⇒ Object



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/sdl4r/serializer.rb', line 388

def deserialize_from_values(tag, o)
  if tag.has_values?
    if tag.has_children? or tag.has_attributes?
      property_value = tag.values

      if o.instance_variable_defined?("@value") or not o.instance_variable_defined?("@values")
        # value is preferred
        property_name = "value"
        property_value = property_value[0] if property_value.length == 1
      else
        property_name = "values"
      end

      if serializable_property?(o, property_name)
        set_serializable_property(o, property_name, property_value)
      end

    else
      # the tag only has values
      return property_value.length == 1 ? property_value[0] : property_value
    end
  end
end

#deserialize_object(tag, o = new_plain_object(tag)) ⇒ Object

Called by #deserialize when not using from_sdl(). Deserializes the specified tag into an object using values, attributes, child tags, etc.



377
378
379
380
381
382
383
384
385
386
# File 'lib/sdl4r/serializer.rb', line 377

def deserialize_object(tag, o = new_plain_object(tag))
  deserialize_from_child_tags(tag, o, true)
  deserialize_from_values(tag, o)
  deserialize_from_attributes(tag, o)
  deserialize_from_child_tags(tag, o, false)

  o = apply_anonymous_tag_list_idiom(tag, o)
  
  o
end

#get_deserialized_property_name(tag, parent_tag, parent_object) ⇒ Object

Returns the name of the property to which the specified Tag is supposed to be assigned in the parent object. Default behavior: this implementation returns the tag’s name (ignoring the namespace).



432
433
434
# File 'lib/sdl4r/serializer.rb', line 432

def get_deserialized_property_name(tag, parent_tag, parent_object)
  tag.name
end

#get_method(o, method_name, arity) ⇒ Object

Returns the method of object ‘o’, which has the specified name and arity (with or without default parameters). Returns nil if no such method corresponds.



254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/sdl4r/serializer.rb', line 254

def get_method(o, method_name, arity)
  begin
    m = o.method(method_name)
  rescue NameError
    m = nil
  end

  unless m.nil? or m.arity == arity or m.arity == (-arity - 1)
    m = nil
  end

  return m
end

#new_plain_object(tag, o = nil, parent_object = nil) ⇒ Object

Provides deserialized new plain object instances (i.e. “plain object” as opposed to special cases like SDL values). By default, returns ‘o’ or a new instance of OpenStruct if ‘o’ is nil.

tag

the deserialized Tag

o

the currently used object or nil if there is none yet

parent_object

the parent object or nil if root



355
356
357
358
359
360
361
# File 'lib/sdl4r/serializer.rb', line 355

def new_plain_object(tag, o = nil, parent_object = nil)
  if o.nil?
    OpenStruct.new
  else
    o
  end
end

#new_ref_tag(name, ref, parent_tag) ⇒ Object

Returns a Tag representing the provided Ref.

name

name of the created Tag (if nil, the definition Tag name - i.e. the name of the

first Tag representing the referenced object - is used)
ref

represented reference (Ref)

parent_tag

parent Tag of the created Tag



153
154
155
156
157
158
159
# File 'lib/sdl4r/serializer.rb', line 153

def new_ref_tag(name, ref, parent_tag)
  name = ref.tag.name if name.nil?

  ref_tag = parent_tag.new_child(@options[:default_namespace], name)
  ref_tag.set_attribute('', OBJECT_REF_ATTR_NAME, ref.oid)
  ref_tag
end

#new_tag(name, o, parent_tag = nil) ⇒ Object

Returns a new Tag to be used for serializing ‘o’.

name

name of the Tag to create (can be nil for a root Tag)

o

object/value that the new tag will represent

parent_tag

parent Tag of the new tag



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/sdl4r/serializer.rb', line 131

def new_tag(name, o, parent_tag = nil)
  if name
    namespace = @options[:default_namespace]
  else
    namespace = ''
    name = SDL4R::ROOT_TAG_NAME
  end

  if parent_tag.nil?
    Tag.new(namespace, name)
  else
    parent_tag.new_child(namespace, name)
  end
end

#reference_object(o, oid = nil) ⇒ Object

References the provided object if it is not already referenced. If the object is referenced already, returns the corresponding Ref. The reference counter is not incremented by this method.

oid

the Object ID (ignore if serializing, provide if deserializing).



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/sdl4r/serializer.rb', line 109

def reference_object(o, oid = nil)
  ref = @ref_by_object[o]

  unless ref
    unless oid
      oid = @next_oid
      @next_oid += 1
    end
    
    ref = Ref.new(o, oid)
    @ref_by_oid[oid] = @ref_by_object[o] = ref
  end

  ref
end

#serializable_property?(o, property_name) ⇒ Boolean

Indicates whether the specified property is a serializable property for the given object.

Returns:

  • (Boolean)


270
271
272
273
274
275
# File 'lib/sdl4r/serializer.rb', line 270

def serializable_property?(o, property_name)
  return o.is_a?(OpenStruct) ||
    o.is_a?(Hash) ||
    o.instance_variable_defined?("@#{property_name}") ||
    (get_method(o, property_name, 0) and get_method(o, "#{property_name}=", 1))
end

#serialize(o, tag = nil, parent_tag = nil) ⇒ Object

Serializes the given object into a returned Tag.

tag

a Tag representing ‘o’ (if not provided, one will be created).



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/sdl4r/serializer.rb', line 165

def serialize(o, tag = nil, parent_tag = nil)
  root = serialize_impl(o, tag, parent_tag)

  # Assign an OID attribute to tags of objects referenced more than once
  @ref_by_object.each_value { |ref|
    if ref.count > 1
      ref.tag.set_attribute('', OBJECT_ID_ATTR_NAME, ref.oid)
    end
  }

  root
end

#serialize_array(name, array, parent_tag) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/sdl4r/serializer.rb', line 305

def serialize_array(name, array, parent_tag)
  not_literal_values = array.reject { |item| SDL4R::is_coercible?(item) }
  if not_literal_values.empty?
    # If it is an array of SDL-compatible values, we use SDL values.
    array_tag = new_tag(name, array, parent_tag)
    array_tag.values = array

  else
    # Otherwise, we use separate child tags.
    serialize_array_as_child_tags(name, array, parent_tag)
  end
end

#serialize_array_as_child_tags(name, array, parent_tag) ⇒ Object



318
319
320
321
322
323
324
325
326
327
# File 'lib/sdl4r/serializer.rb', line 318

def serialize_array_as_child_tags(name, array, parent_tag)
  array.each { |item|
    if SDL4R::is_coercible?(item)
      value_tag = new_tag(name, item, parent_tag)
      value_tag.values = [item]
    else
      serialize_object_or_ref(name, item, parent_tag)
    end
  }
end

#serialize_hash(hash, tag) ⇒ Object



211
212
213
214
215
216
# File 'lib/sdl4r/serializer.rb', line 211

def serialize_hash(hash, tag)
  hash.each_pair { |key, value|
    serialize_property(key.to_s, value, tag)
  }
  tag
end

#serialize_object_or_ref(name, value, parent_tag) ⇒ Object

Serializes an object (possibly using a reference).

name

name used for the object in the parent Tag



333
334
335
336
337
338
339
340
341
342
343
# File 'lib/sdl4r/serializer.rb', line 333

def serialize_object_or_ref(name, value, parent_tag)
  ref = reference_object(value)
  ref.inc
  
  if ref.count == 1
    ref.tag = new_tag(name, value, parent_tag)
    serialize_impl(value, ref.tag)
  else
    new_ref_tag(name, ref, parent_tag)
  end
end

#serialize_plain_object(o, tag) ⇒ Object



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
# File 'lib/sdl4r/serializer.rb', line 218

def serialize_plain_object(o, tag)
  processed_properties = {}

  # Read+write attributes are used rather than variables
  o.methods.each { |name|
    if name =~ /\w/ and # check this is not an operator
        name =~ /^([^=]+)=$/ # check setter name pattern
      if get_method(o, name, 1)
        getter_name = $1
        getter = get_method(o, getter_name, 0)

        if getter
          # read and write accessors are defined
          value = getter.call
          serialize_property(getter_name, value, tag)
          processed_properties[getter_name] = true
        end
      end
    end
  }

  # Otherwise, we read instance variables
  o.instance_variables.each { |variable_name|
    name = variable_name[1..variable_name.size] if variable_name =~ /^@/
    unless processed_properties.has_key?(name)
      value = o.instance_variable_get(variable_name)
      serialize_property(name, value, tag)
    end
  }

  tag
end

#serialize_property(name, value, parent_tag) ⇒ Object

Serializes a property under a given Tag. Ignores any property whose name is not a valid SDL name.

name

property name

value

property value (SDL value, plain object or Array)



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/sdl4r/serializer.rb', line 283

def serialize_property(name, value, parent_tag)
  return unless SDL4R::valid_identifier?(name)

  if value.is_a? Array
    serialize_array(name, value, parent_tag)

  elsif SDL4R::is_coercible?(value)
    # SDL literal type
    unless value.nil? and @options[:omit_nil_properties]
      if parent_tag.name == SDL4R::ROOT_TAG_NAME
        value_tag = new_tag(name, value, parent_tag)
        value_tag.values = [value]
      else
        parent_tag.set_attribute(name, value)
      end
    end

  else
    serialize_object_or_ref(name, value, parent_tag)
  end
end

#set_serializable_property(o, name, value) ⇒ Object

Sets the given property in the given object. Returns whether the property has been assigned or not.

This method could be redefined in order to convert a value to a custom one, for instance.



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
# File 'lib/sdl4r/serializer.rb', line 536

def set_serializable_property(o, name, value)
  case o
  when Hash
    o[name] = value
    return true

  when OpenStruct
    o.send "#{name}=", value
    return true

  else
    accessor_name = "#{name}="
    if o.respond_to?(accessor_name)
      o.send accessor_name, value
      return true

    else
      variable_name = "@#{name}"
      if o.instance_variable_defined?(variable_name)
        o.instance_variable_set(variable_name, value)
        return true
        
      else
        return false
      end
    end
  end
end