lib/wx/shapes/serializable.rb in wxruby3-shapes-0.9.0.pre.beta.3 vs lib/wx/shapes/serializable.rb in wxruby3-shapes-0.9.5
- old
+ new
@@ -1,440 +1,6 @@
-# Wx::SF::Serializer - shape serializer module
+# Wx::SF - shape serializer module
# Copyright (c) M.J.N. Corino, The Netherlands
-require 'set'
+require 'firm'
-module Wx::SF
-
- module Serializable
-
- class Property
- def initialize(klass, prop, proc=nil, force: false, handler: nil, &block)
- ::Kernel.raise ArgumentError, "Invalid property id [#{prop}]" unless ::String === prop || ::Symbol === prop
- ::Kernel.raise ArgumentError, "Duplicate property id [#{prop}]" if klass.has_serializer_property?(prop)
- @klass = klass
- @id = prop.to_sym
- @forced = force
- if block || handler
- if handler
- ::Kernel.raise ArgumentError,
- "Invalid property handler #{handler} for #{prop}" unless ::Proc === handler || ::Symbol === handler
- if handler.is_a?(::Proc)
- ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
- @getter = ->(obj) { handler.call(@id, obj) }
- @setter = ->(obj, val) { handler.call(@id, obj, val) }
- else
- @getter = ->(obj) { obj.send(handler, @id) }
- @setter = ->(obj, val) { obj.send(handler, @id, val) }
- end
- else
- # any property block MUST accept 2 or 3 args; property name, instance and value (for setter)
- ::Kernel.raise ArgumentError, "Invalid property block #{proc} for #{prop}" unless block.arity == -3
- @getter = ->(obj) { block.call(@id, obj) }
- @setter = ->(obj, val) { block.call(@id, obj, val) }
- end
- elsif proc
- ::Kernel.raise ArgumentError,
- "Invalid property proc #{proc} for #{prop}" unless ::Proc === proc || ::Symbol === proc
- if ::Proc === proc
- # any property proc should be callable with a single arg (instance)
- @getter = proc
- # a property proc combining getter/setter functionality should accept a single or more args (instance + value)
- @setter = (proc.arity == -2) ? proc : nil
- else
- @getter = ->(obj) { obj.send(proc) }
- @setter = ->(obj, val) { obj.send(proc, val) }
- end
- end
- end
-
- attr_reader :id
-
- def serialize(obj, data, excludes)
- unless excludes.include?(@id)
- val = getter.call(obj)
- unless Serializable === val && val.serialize_disabled? && !@forced
- data[@id] = case val
- when ::Array
- val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) }
- when ::Set
- ::Set.new(val.select { |elem| !(Serializable === elem && elem.serialize_disabled?) })
- else
- val
- end
- end
- end
- end
-
- def deserialize(obj, data)
- if data.has_key?(@id)
- setter.call(obj, data[@id])
- end
- end
-
- def get(obj)
- getter.call(obj)
- end
-
- def get_method(id)
- begin
- @klass.instance_method(id)
- rescue NameError
- nil
- end
- end
- private :get_method
-
- def getter
- unless @getter
- inst_meth = get_method(@id)
- inst_meth = get_method("get_#{@id}") unless inst_meth
- if inst_meth
- @getter = ->(obj) { inst_meth.bind(obj).call }
- else
- return self.method(:getter_fail)
- end
- end
- @getter
- end
- private :getter
-
- def setter
- unless @setter
- inst_meth = get_method("#{@id}=")
- inst_meth = get_method("set_#{@id}") unless inst_meth
- unless inst_meth
- im = get_method(@id)
- if im && im.arity == -1
- inst_meth = im
- else
- inst_meth = nil
- end
- end
- if inst_meth
- @setter = ->(obj, val) { inst_meth.bind(obj).call(val) }
- else
- return self.method(:setter_noop)
- end
- end
- @setter
- end
- private :setter
-
- def getter_fail(obj)
- ::Kernel.raise RuntimeError, "Missing getter for property #{@id} of #{@klass}"
- end
- private :getter_fail
-
- def setter_noop(_, _)
- # do nothing
- end
- private :setter_noop
- end
-
- # Serializable unique ids.
- # This class makes sure to maintain uniqueness across serialization/deserialization cycles
- # and keeps all shared instances within a single (serialized/deserialized) object set in
- # sync.
- class ID; end
-
- class << self
-
- def serializables
- @serializables ||= ::Set.new
- end
-
- def formatters
- @formatters ||= {}
- end
- private :formatters
-
- # Registers a serialization formatting engine
- # @param [Symbol,String] format format id
- # @param [Object] engine formatting engine
- def register(format, engine)
- if formatters.has_key?(format.to_s.downcase)
- ::Kernel.raise ArgumentError,
- "Duplicate serialization formatter registration for #{format}"
- end
- formatters[format.to_s.downcase] = engine
- end
-
- # Return a serialization formatting engine
- # @param [Symbol,String] format format id
- # @return [Object] formatting engine
- def [](format)
- formatters[format.to_s.downcase]
- end
-
- def default_format
- @default_format ||= :json
- end
-
- def default_format=(format)
- @default_format = format
- end
-
- end
-
- # Mixin module for classes that get Wx::SF::Serializable included.
- # This module is used to extend the class methods of the serializable class.
- module SerializeClassMethods
-
- # Adds (a) serializable property(-ies) for instances of his class (and derived classes)
- # @overload property(*props, force: false)
- # Specifies one or more serialized properties.
- # The serialization framework will determine the availability of setter and getter methods
- # automatically by looking for methods <code>"#{prop_id}=(v)"</code>, <code>"set_#{prop_id}(v)"</code> or <code>"#{prop}(v)"</code>
- # for setters and <code>"#{prop_id}()"</code> or <code>"get_#{prop_id}"</code> for getters.
- # @param [Symbol,String] props one or more ids of serializable properties
- # @param [Boolean] force overrides any #disable_serialize for the properties specified
- # @overload property(hash, force: false)
- # Specifies one or more serialized properties with associated setter/getter method ids/procs/lambda-s.
- # @example
- # property(
- # prop_a: ->(obj, *val) {
- # obj.my_prop_a_setter(val.first) unless val.empty?
- # obj.my_prop_a_getter
- # },
- # prop_b: Proc.new { |obj, *val|
- # obj.my_prop_b_setter(val.first) unless val.empty?
- # obj.my_prop_b_getter
- # },
- # prop_c: :serialization_method)
- # Procs with setter support MUST accept 1 or 2 arguments (1 for getter, 2 for setter).
- # @note Use `*val` to specify the optional value argument for setter requests instead of `val=nil`
- # to be able to support setting explicit nil values.
- # @param [Hash] hash a hash of pairs of property ids and getter/setter procs
- # @param [Boolean] force overrides any #disable_serialize for the properties specified
- # @overload property(*props, force: false, handler: nil, &block)
- # Specifies one or more serialized properties with a getter/setter handler proc/method/block.
- # The getter/setter proc or block should accept either 2 (property id and object for getter) or 3 arguments
- # (property id, object and value for setter) and is assumed to handle getter/setter requests
- # for all specified properties.
- # The getter/setter method should accept either 1 (property id for getter) or 2 arguments
- # (property id and value for setter) and is assumed to handle getter/setter requests
- # for all specified properties.
- # @example
- # property(:property_a, :property_b, :property_c) do |id, obj, *val|
- # case id
- # when :property_a
- # ...
- # when :property_b
- # ...
- # when :property_c
- # ...
- # end
- # end
- # @note Use `*val` to specify the optional value argument for setter requests instead of `val=nil`
- # to be able to support setting explicit nil values.
- # @param [Symbol,String] props one or more ids of serializable properties
- # @param [Boolean] force overrides any #disable_serialize for the properties specified
- # @yieldparam [Symbol,String] id property id
- # @yieldparam [Object] obj object instance
- # @yieldparam [Object] val optional property value to set in case of setter request
- def property(*props, **kwargs, &block)
- forced = !!kwargs.delete(:force)
- if block || kwargs[:handler]
- props.each do |prop|
- serializer_properties << Property.new(self, prop, force: forced, handler: kwargs[:handler], &block)
- end
- else
- props.flatten.each do |prop|
- if ::Hash === prop
- prop.each_pair do |pn, pp|
- serializer_properties << Property.new(self, pn, pp, force: forced)
- end
- else
- serializer_properties << Property.new(self, prop, force: forced)
- end
- end
- unless kwargs.empty?
- kwargs.each_pair do |pn, pp|
- serializer_properties << Property.new(self, pn, pp, force: forced)
- end
- end
- end
- end
- alias :properties :property
- alias :contains :property
-
- # excludes a serializable property for instances of this class
- # (mostly/only useful to exclude properties from base classes which
- # do not require serialization for derived class)
- def excluded_property(*props)
- excluded_serializer_properties.merge props.flatten.collect { |prop| prop.to_s }
- end
- alias :excluded_properties :excluded_property
- alias :excludes :excluded_property
-
- # Creates a new instance for subsequent deserialization and optionally initialize
- # it using the given data hash.
- # The default implementation creates a new instance using the default constructor
- # (no arguments, no initialization) and leaves the initialization to a subsequent call
- # to the instance method #from_serialized(data).
- # Classes that do not support a default constructor can override this class method and
- # implement a custom creation scheme.
- # @param [Hash] _data hash containing deserialized property data (symbol keys)
- # @return [Object] the newly created object
- def create_for_deserialize(_data)
- self.new
- end
-
- end
-
- # Mixin module for classes that get Wx::SF::Serializable included.
- # This module is used to extend the instance methods of the serializable class.
- module SerializeInstanceMethods
-
- # Serialize this object
- # @overload serialize(pretty: false, format: Serializable.default_format)
- # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
- # @param [Symbol,String] format specifies output format
- # @return [String] serialized data
- # @overload serialize(io, pretty: false, format: Serializable.default_format)
- # @param [IO] io output stream to write serialized data to
- # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
- # @param [Symbol,String] format specifies output format
- # @return [IO]
- def serialize(io = nil, pretty: false, format: Serializable.default_format)
- Serializable[format].dump(self, io, pretty: pretty)
- end
-
- # Returns true if regular serialization for this object has been disabled, false otherwise (default).
- # Disabled serialization can be overridden for single objects (not objects maintained in property containers
- # like arrays and sets).
- # @return [Boolean]
- def serialize_disabled?
- !!@serialize_disabled # true for any value but false
- end
-
- # Disables serialization for this object as a single property or as part of a property container
- # (array or set).
- # @return [void]
- def disable_serialize
- # by default unset (nil) so serializing enabled
- @serialize_disabled = true
- end
-
- # @!method for_serialize(hash, excludes = Set.new)
- # Serializes the properties of a serializable instance to the given hash
- # except when the property id is included in excludes.
- # @param [Hash] hash property serialization hash
- # @param [Set] excludes set with excluded property ids
- # @return [Hash] property serialization hash
-
- # @!method from_serialized(hash)
- # Restores the properties of a deserialized instance.
- # @param [Hash] hash deserialized properties hash
- # @return [self]
-
- end
-
- # Serialize the given object
- # @overload serialize(obj, pretty: false, format: Serializable.default_format)
- # @param [Object] obj object to serialize
- # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
- # @param [Symbol,String] format specifies output format
- # @return [String] serialized data
- # @overload serialize(obj, io, pretty: false, format: Serializable.default_format)
- # @param [Object] obj object to serialize
- # @param [IO] io output stream to write serialized data to
- # @param [Boolean] pretty if true specifies to generate pretty formatted output if possible
- # @param [Symbol,String] format specifies output format
- # @return [IO]
- def self.serialize(obj, io = nil, pretty: false, format: Serializable.default_format)
- self[format].dump(obj, io, pretty: pretty)
- end
-
- # Deserializes object from source data
- # @param [IO,String] source source data (stream)
- # @param [Symbol, String] format data format of source
- # @return [Object] deserialized object
- def self.deserialize(source, format: Serializable.default_format)
- self[format].load(::IO === source || source.respond_to?(:read) ? source.read : source)
- end
-
- def self.included(base)
- ::Kernel.raise RuntimeError, "#{self} should only be included in classes" if base.instance_of?(::Module)
-
- # register as serializable class
- Serializable.serializables << base
-
- return if base == Serializable::ID # special case which does not need the rest
-
- # provide serialized property definition support
-
- # provide serialized classes with their own serialized properties (exclusion) list
- base.singleton_class.class_eval do
- def serializer_properties
- @serializer_props ||= []
- end
- def excluded_serializer_properties
- @excluded_serializer_props ||= ::Set.new
- end
- end
- # add class methods
- base.extend(SerializeClassMethods)
-
- # add instance property (de-)serialization methods for base class
- base.class_eval <<~__CODE
- def for_serialize(hash, excludes = ::Set.new)
- hash[:'@explicit'] = true if serialize_disabled? # mark explicit serialize overriding disabling
- #{base.name}.serializer_properties.each { |prop, h| prop.serialize(self, hash, excludes) }
- hash
- end
- protected :for_serialize
-
- def from_serialized(hash)
- disable_serialize if hash[:'@explicit'] # re-instate serialization disabling
- #{base.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
- self
- end
- protected :from_serialized
-
- def self.has_serializer_property?(id)
- self.serializer_properties.any? { |p| p.id == id.to_sym }
- end
- __CODE
- # add inheritance support
- base.class_eval do
- def self.inherited(derived)
- # add instance property (de-)serialization methods for derived classes
- derived.class_eval <<~__CODE
- module SerializerMethods
- def for_serialize(hash, excludes = ::Set.new)
- hash = super(hash, excludes | #{derived.name}.excluded_serializer_properties)
- #{derived.name}.serializer_properties.each { |prop| prop.serialize(self, hash, excludes) }
- hash
- end
- protected :for_serialize
-
- def from_serialized(hash)
- #{derived.name}.serializer_properties.each { |prop| prop.deserialize(self, hash) }
- super(hash)
- end
- protected :from_serialized
- end
- include SerializerMethods
- __CODE
- derived.class_eval do
- def self.has_serializer_property?(id)
- self.serializer_properties.any? { |p| p.id == id.to_sym } || self.superclass.has_serializer_property?(id)
- end
- end
-
- # register as serializable class
- Serializable.serializables << derived
- end
- end
-
- # add instance serialization method
- base.include(SerializeInstanceMethods)
- end
-
- end # module Serializable
-
-end # module Wx::SF
-
-Dir[File.join(__dir__, 'serializer', '*.rb')].each { |fnm| require "wx/shapes/serializer/#{File.basename(fnm)}" }
Dir[File.join(__dir__, 'serialize', '*.rb')].each { |fnm| require "wx/shapes/serialize/#{File.basename(fnm)}" }