230 lines
8.4 KiB
Ruby
230 lines
8.4 KiB
Ruby
require 'concurrent/synchronization/abstract_struct'
|
||
require 'concurrent/synchronization'
|
||
|
||
module Concurrent
|
||
|
||
# An thread-safe variation of Ruby's standard `Struct`. Values can be set at
|
||
# construction or safely changed at any time during the object's lifecycle.
|
||
#
|
||
# @see http://ruby-doc.org/core-2.2.0/Struct.html Ruby standard library `Struct`
|
||
module MutableStruct
|
||
include Synchronization::AbstractStruct
|
||
|
||
# @!macro struct_new
|
||
#
|
||
# Factory for creating new struct classes.
|
||
#
|
||
# ```
|
||
# new([class_name] [, member_name]+>) -> StructClass click to toggle source
|
||
# new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass
|
||
# new(value, ...) -> obj
|
||
# StructClass[value, ...] -> obj
|
||
# ```
|
||
#
|
||
# The first two forms are used to create a new struct subclass `class_name`
|
||
# that can contain a value for each member_name . This subclass can be
|
||
# used to create instances of the structure like any other Class .
|
||
#
|
||
# If the `class_name` is omitted an anonymous struct class will be created.
|
||
# Otherwise, the name of this struct will appear as a constant in the struct class,
|
||
# so it must be unique for all structs under this base class and must start with a
|
||
# capital letter. Assigning a struct class to a constant also gives the class
|
||
# the name of the constant.
|
||
#
|
||
# If a block is given it will be evaluated in the context of `StructClass`, passing
|
||
# the created class as a parameter. This is the recommended way to customize a struct.
|
||
# Subclassing an anonymous struct creates an extra anonymous class that will never be used.
|
||
#
|
||
# The last two forms create a new instance of a struct subclass. The number of value
|
||
# parameters must be less than or equal to the number of attributes defined for the
|
||
# struct. Unset parameters default to nil. Passing more parameters than number of attributes
|
||
# will raise an `ArgumentError`.
|
||
#
|
||
# @see http://ruby-doc.org/core-2.2.0/Struct.html#method-c-new Ruby standard library `Struct#new`
|
||
|
||
# @!macro struct_values
|
||
#
|
||
# Returns the values for this struct as an Array.
|
||
#
|
||
# @return [Array] the values for this struct
|
||
#
|
||
def values
|
||
synchronize { ns_values }
|
||
end
|
||
alias_method :to_a, :values
|
||
|
||
# @!macro struct_values_at
|
||
#
|
||
# Returns the struct member values for each selector as an Array.
|
||
#
|
||
# A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`).
|
||
#
|
||
# @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order)
|
||
def values_at(*indexes)
|
||
synchronize { ns_values_at(indexes) }
|
||
end
|
||
|
||
# @!macro struct_inspect
|
||
#
|
||
# Describe the contents of this struct in a string.
|
||
#
|
||
# @return [String] the contents of this struct in a string
|
||
def inspect
|
||
synchronize { ns_inspect }
|
||
end
|
||
alias_method :to_s, :inspect
|
||
|
||
# @!macro struct_merge
|
||
#
|
||
# Returns a new struct containing the contents of `other` and the contents
|
||
# of `self`. If no block is specified, the value for entries with duplicate
|
||
# keys will be that of `other`. Otherwise the value for each duplicate key
|
||
# is determined by calling the block with the key, its value in `self` and
|
||
# its value in `other`.
|
||
#
|
||
# @param [Hash] other the hash from which to set the new values
|
||
# @yield an options block for resolving duplicate keys
|
||
# @yieldparam [String, Symbol] member the name of the member which is duplicated
|
||
# @yieldparam [Object] selfvalue the value of the member in `self`
|
||
# @yieldparam [Object] othervalue the value of the member in `other`
|
||
#
|
||
# @return [Synchronization::AbstractStruct] a new struct with the new values
|
||
#
|
||
# @raise [ArgumentError] of given a member that is not defined in the struct
|
||
def merge(other, &block)
|
||
synchronize { ns_merge(other, &block) }
|
||
end
|
||
|
||
# @!macro struct_to_h
|
||
#
|
||
# Returns a hash containing the names and values for the struct’s members.
|
||
#
|
||
# @return [Hash] the names and values for the struct’s members
|
||
def to_h
|
||
synchronize { ns_to_h }
|
||
end
|
||
|
||
# @!macro struct_get
|
||
#
|
||
# Attribute Reference
|
||
#
|
||
# @param [Symbol, String, Integer] member the string or symbol name of the member
|
||
# for which to obtain the value or the member's index
|
||
#
|
||
# @return [Object] the value of the given struct member or the member at the given index.
|
||
#
|
||
# @raise [NameError] if the member does not exist
|
||
# @raise [IndexError] if the index is out of range.
|
||
def [](member)
|
||
synchronize { ns_get(member) }
|
||
end
|
||
|
||
# @!macro struct_equality
|
||
#
|
||
# Equality
|
||
#
|
||
# @return [Boolean] true if other has the same struct subclass and has
|
||
# equal member values (according to `Object#==`)
|
||
def ==(other)
|
||
synchronize { ns_equality(other) }
|
||
end
|
||
|
||
# @!macro struct_each
|
||
#
|
||
# Yields the value of each struct member in order. If no block is given
|
||
# an enumerator is returned.
|
||
#
|
||
# @yield the operation to be performed on each struct member
|
||
# @yieldparam [Object] value each struct value (in order)
|
||
def each(&block)
|
||
return enum_for(:each) unless block_given?
|
||
synchronize { ns_each(&block) }
|
||
end
|
||
|
||
# @!macro struct_each_pair
|
||
#
|
||
# Yields the name and value of each struct member in order. If no block is
|
||
# given an enumerator is returned.
|
||
#
|
||
# @yield the operation to be performed on each struct member/value pair
|
||
# @yieldparam [Object] member each struct member (in order)
|
||
# @yieldparam [Object] value each struct value (in order)
|
||
def each_pair(&block)
|
||
return enum_for(:each_pair) unless block_given?
|
||
synchronize { ns_each_pair(&block) }
|
||
end
|
||
|
||
# @!macro struct_select
|
||
#
|
||
# Yields each member value from the struct to the block and returns an Array
|
||
# containing the member values from the struct for which the given block
|
||
# returns a true value (equivalent to `Enumerable#select`).
|
||
#
|
||
# @yield the operation to be performed on each struct member
|
||
# @yieldparam [Object] value each struct value (in order)
|
||
#
|
||
# @return [Array] an array containing each value for which the block returns true
|
||
def select(&block)
|
||
return enum_for(:select) unless block_given?
|
||
synchronize { ns_select(&block) }
|
||
end
|
||
|
||
# @!macro struct_set
|
||
#
|
||
# Attribute Assignment
|
||
#
|
||
# Sets the value of the given struct member or the member at the given index.
|
||
#
|
||
# @param [Symbol, String, Integer] member the string or symbol name of the member
|
||
# for which to obtain the value or the member's index
|
||
#
|
||
# @return [Object] the value of the given struct member or the member at the given index.
|
||
#
|
||
# @raise [NameError] if the name does not exist
|
||
# @raise [IndexError] if the index is out of range.
|
||
def []=(member, value)
|
||
if member.is_a? Integer
|
||
length = synchronize { @values.length }
|
||
if member >= length
|
||
raise IndexError.new("offset #{member} too large for struct(size:#{length})")
|
||
end
|
||
synchronize { @values[member] = value }
|
||
else
|
||
send("#{member}=", value)
|
||
end
|
||
rescue NoMethodError
|
||
raise NameError.new("no member '#{member}' in struct")
|
||
end
|
||
|
||
# @!macro struct_new
|
||
def self.new(*args, &block)
|
||
clazz_name = nil
|
||
if args.length == 0
|
||
raise ArgumentError.new('wrong number of arguments (0 for 1+)')
|
||
elsif args.length > 0 && args.first.is_a?(String)
|
||
clazz_name = args.shift
|
||
end
|
||
FACTORY.define_struct(clazz_name, args, &block)
|
||
end
|
||
|
||
FACTORY = Class.new(Synchronization::LockableObject) do
|
||
def define_struct(name, members, &block)
|
||
synchronize do
|
||
clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::LockableObject, name, members, &block)
|
||
members.each_with_index do |member, index|
|
||
clazz.send :remove_method, member
|
||
clazz.send(:define_method, member) do
|
||
synchronize { @values[index] }
|
||
end
|
||
clazz.send(:define_method, "#{member}=") do |value|
|
||
synchronize { @values[index] = value }
|
||
end
|
||
end
|
||
clazz
|
||
end
|
||
end
|
||
end.new
|
||
private_constant :FACTORY
|
||
end
|
||
end
|