130 lines
3.8 KiB
Ruby
130 lines
3.8 KiB
Ruby
|
require 'concurrent/synchronization/abstract_struct'
|
||
|
require 'concurrent/errors'
|
||
|
require 'concurrent/synchronization'
|
||
|
|
||
|
module Concurrent
|
||
|
|
||
|
# An thread-safe, write-once variation of Ruby's standard `Struct`.
|
||
|
# Each member can have its value set at most once, either at construction
|
||
|
# or any time thereafter. Attempting to assign a value to a member
|
||
|
# that has already been set will result in a `Concurrent::ImmutabilityError`.
|
||
|
#
|
||
|
# @see http://ruby-doc.org/core-2.2.0/Struct.html Ruby standard library `Struct`
|
||
|
# @see http://en.wikipedia.org/wiki/Final_(Java) Java `final` keyword
|
||
|
module SettableStruct
|
||
|
include Synchronization::AbstractStruct
|
||
|
|
||
|
# @!macro struct_values
|
||
|
def values
|
||
|
synchronize { ns_values }
|
||
|
end
|
||
|
alias_method :to_a, :values
|
||
|
|
||
|
# @!macro struct_values_at
|
||
|
def values_at(*indexes)
|
||
|
synchronize { ns_values_at(indexes) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_inspect
|
||
|
def inspect
|
||
|
synchronize { ns_inspect }
|
||
|
end
|
||
|
alias_method :to_s, :inspect
|
||
|
|
||
|
# @!macro struct_merge
|
||
|
def merge(other, &block)
|
||
|
synchronize { ns_merge(other, &block) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_to_h
|
||
|
def to_h
|
||
|
synchronize { ns_to_h }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_get
|
||
|
def [](member)
|
||
|
synchronize { ns_get(member) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_equality
|
||
|
def ==(other)
|
||
|
synchronize { ns_equality(other) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_each
|
||
|
def each(&block)
|
||
|
return enum_for(:each) unless block_given?
|
||
|
synchronize { ns_each(&block) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_each_pair
|
||
|
def each_pair(&block)
|
||
|
return enum_for(:each_pair) unless block_given?
|
||
|
synchronize { ns_each_pair(&block) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_select
|
||
|
def select(&block)
|
||
|
return enum_for(:select) unless block_given?
|
||
|
synchronize { ns_select(&block) }
|
||
|
end
|
||
|
|
||
|
# @!macro struct_set
|
||
|
#
|
||
|
# @raise [Concurrent::ImmutabilityError] if the given member has already been set
|
||
|
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 do
|
||
|
unless @values[member].nil?
|
||
|
raise Concurrent::ImmutabilityError.new('struct member has already been set')
|
||
|
end
|
||
|
@values[member] = value
|
||
|
end
|
||
|
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(SettableStruct, Synchronization::LockableObject, name, members, &block)
|
||
|
members.each_with_index do |member, index|
|
||
|
clazz.send :remove_method, member if clazz.instance_methods.include? member
|
||
|
clazz.send(:define_method, member) do
|
||
|
synchronize { @values[index] }
|
||
|
end
|
||
|
clazz.send(:define_method, "#{member}=") do |value|
|
||
|
synchronize do
|
||
|
unless @values[index].nil?
|
||
|
raise Concurrent::ImmutabilityError.new('struct member has already been set')
|
||
|
end
|
||
|
@values[index] = value
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
clazz
|
||
|
end
|
||
|
end
|
||
|
end.new
|
||
|
private_constant :FACTORY
|
||
|
end
|
||
|
end
|