concurrent-ruby 0.8.0.pre2-java → 0.9.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +114 -3
  3. data/README.md +111 -55
  4. data/lib/concurrent.rb +90 -14
  5. data/lib/concurrent/async.rb +143 -51
  6. data/lib/concurrent/atom.rb +131 -0
  7. data/lib/concurrent/atomic/atomic_boolean.rb +57 -107
  8. data/lib/concurrent/atomic/atomic_fixnum.rb +73 -101
  9. data/lib/concurrent/atomic/atomic_reference.rb +49 -0
  10. data/lib/concurrent/atomic/condition.rb +23 -12
  11. data/lib/concurrent/atomic/count_down_latch.rb +23 -21
  12. data/lib/concurrent/atomic/cyclic_barrier.rb +47 -47
  13. data/lib/concurrent/atomic/event.rb +33 -42
  14. data/lib/concurrent/atomic/read_write_lock.rb +252 -0
  15. data/lib/concurrent/atomic/semaphore.rb +64 -89
  16. data/lib/concurrent/atomic/thread_local_var.rb +130 -58
  17. data/lib/concurrent/atomic/thread_local_var/weak_key_map.rb +236 -0
  18. data/lib/concurrent/atomic_reference/direct_update.rb +34 -3
  19. data/lib/concurrent/atomic_reference/jruby.rb +6 -3
  20. data/lib/concurrent/atomic_reference/mutex_atomic.rb +17 -39
  21. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +3 -0
  22. data/lib/concurrent/atomic_reference/rbx.rb +4 -1
  23. data/lib/concurrent/atomic_reference/ruby.rb +6 -3
  24. data/lib/concurrent/atomics.rb +74 -4
  25. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +115 -0
  26. data/lib/concurrent/collection/copy_on_write_observer_set.rb +119 -0
  27. data/lib/concurrent/collection/priority_queue.rb +300 -245
  28. data/lib/concurrent/concern/deprecation.rb +34 -0
  29. data/lib/concurrent/concern/dereferenceable.rb +88 -0
  30. data/lib/concurrent/concern/logging.rb +27 -0
  31. data/lib/concurrent/concern/obligation.rb +228 -0
  32. data/lib/concurrent/concern/observable.rb +85 -0
  33. data/lib/concurrent/configuration.rb +234 -109
  34. data/lib/concurrent/dataflow.rb +2 -3
  35. data/lib/concurrent/delay.rb +141 -50
  36. data/lib/concurrent/edge.rb +30 -0
  37. data/lib/concurrent/errors.rb +19 -7
  38. data/lib/concurrent/exchanger.rb +25 -1
  39. data/lib/concurrent/executor/cached_thread_pool.rb +51 -33
  40. data/lib/concurrent/executor/executor.rb +46 -299
  41. data/lib/concurrent/executor/executor_service.rb +521 -0
  42. data/lib/concurrent/executor/fixed_thread_pool.rb +196 -23
  43. data/lib/concurrent/executor/immediate_executor.rb +9 -9
  44. data/lib/concurrent/executor/indirect_immediate_executor.rb +4 -3
  45. data/lib/concurrent/executor/java_single_thread_executor.rb +17 -16
  46. data/lib/concurrent/executor/java_thread_pool_executor.rb +55 -102
  47. data/lib/concurrent/executor/ruby_single_thread_executor.rb +14 -16
  48. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +250 -166
  49. data/lib/concurrent/executor/safe_task_executor.rb +5 -4
  50. data/lib/concurrent/executor/serialized_execution.rb +22 -18
  51. data/lib/concurrent/executor/{per_thread_executor.rb → simple_executor_service.rb} +29 -20
  52. data/lib/concurrent/executor/single_thread_executor.rb +32 -21
  53. data/lib/concurrent/executor/thread_pool_executor.rb +73 -60
  54. data/lib/concurrent/executor/timer_set.rb +96 -84
  55. data/lib/concurrent/executors.rb +1 -1
  56. data/lib/concurrent/future.rb +71 -38
  57. data/lib/concurrent/immutable_struct.rb +89 -0
  58. data/lib/concurrent/ivar.rb +152 -60
  59. data/lib/concurrent/lazy_register.rb +40 -20
  60. data/lib/concurrent/maybe.rb +226 -0
  61. data/lib/concurrent/mutable_struct.rb +227 -0
  62. data/lib/concurrent/mvar.rb +44 -43
  63. data/lib/concurrent/promise.rb +229 -136
  64. data/lib/concurrent/scheduled_task.rb +341 -43
  65. data/lib/concurrent/settable_struct.rb +127 -0
  66. data/lib/concurrent/synchronization.rb +17 -0
  67. data/lib/concurrent/synchronization/abstract_object.rb +163 -0
  68. data/lib/concurrent/synchronization/abstract_struct.rb +158 -0
  69. data/lib/concurrent/synchronization/condition.rb +53 -0
  70. data/lib/concurrent/synchronization/java_object.rb +34 -0
  71. data/lib/concurrent/synchronization/lock.rb +32 -0
  72. data/lib/concurrent/synchronization/monitor_object.rb +26 -0
  73. data/lib/concurrent/synchronization/mutex_object.rb +43 -0
  74. data/lib/concurrent/synchronization/object.rb +78 -0
  75. data/lib/concurrent/synchronization/rbx_object.rb +75 -0
  76. data/lib/concurrent/timer_task.rb +92 -103
  77. data/lib/concurrent/tvar.rb +42 -38
  78. data/lib/concurrent/utilities.rb +3 -1
  79. data/lib/concurrent/utility/at_exit.rb +97 -0
  80. data/lib/concurrent/utility/engine.rb +44 -0
  81. data/lib/concurrent/utility/monotonic_time.rb +59 -0
  82. data/lib/concurrent/utility/native_extension_loader.rb +56 -0
  83. data/lib/concurrent/utility/processor_counter.rb +156 -0
  84. data/lib/concurrent/utility/timeout.rb +18 -14
  85. data/lib/concurrent/utility/timer.rb +11 -6
  86. data/lib/concurrent/version.rb +2 -1
  87. data/lib/concurrent_ruby.rb +1 -0
  88. data/lib/concurrent_ruby_ext.jar +0 -0
  89. metadata +46 -66
  90. data/lib/concurrent/actor.rb +0 -103
  91. data/lib/concurrent/actor/behaviour.rb +0 -70
  92. data/lib/concurrent/actor/behaviour/abstract.rb +0 -48
  93. data/lib/concurrent/actor/behaviour/awaits.rb +0 -21
  94. data/lib/concurrent/actor/behaviour/buffer.rb +0 -54
  95. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +0 -12
  96. data/lib/concurrent/actor/behaviour/executes_context.rb +0 -18
  97. data/lib/concurrent/actor/behaviour/linking.rb +0 -45
  98. data/lib/concurrent/actor/behaviour/pausing.rb +0 -77
  99. data/lib/concurrent/actor/behaviour/removes_child.rb +0 -16
  100. data/lib/concurrent/actor/behaviour/sets_results.rb +0 -36
  101. data/lib/concurrent/actor/behaviour/supervised.rb +0 -59
  102. data/lib/concurrent/actor/behaviour/supervising.rb +0 -34
  103. data/lib/concurrent/actor/behaviour/terminates_children.rb +0 -13
  104. data/lib/concurrent/actor/behaviour/termination.rb +0 -54
  105. data/lib/concurrent/actor/context.rb +0 -154
  106. data/lib/concurrent/actor/core.rb +0 -217
  107. data/lib/concurrent/actor/default_dead_letter_handler.rb +0 -9
  108. data/lib/concurrent/actor/envelope.rb +0 -41
  109. data/lib/concurrent/actor/errors.rb +0 -27
  110. data/lib/concurrent/actor/internal_delegations.rb +0 -49
  111. data/lib/concurrent/actor/public_delegations.rb +0 -40
  112. data/lib/concurrent/actor/reference.rb +0 -81
  113. data/lib/concurrent/actor/root.rb +0 -37
  114. data/lib/concurrent/actor/type_check.rb +0 -48
  115. data/lib/concurrent/actor/utils.rb +0 -10
  116. data/lib/concurrent/actor/utils/ad_hoc.rb +0 -21
  117. data/lib/concurrent/actor/utils/balancer.rb +0 -42
  118. data/lib/concurrent/actor/utils/broadcast.rb +0 -52
  119. data/lib/concurrent/actor/utils/pool.rb +0 -59
  120. data/lib/concurrent/actress.rb +0 -3
  121. data/lib/concurrent/agent.rb +0 -209
  122. data/lib/concurrent/atomic.rb +0 -92
  123. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +0 -118
  124. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +0 -117
  125. data/lib/concurrent/atomic/synchronization.rb +0 -51
  126. data/lib/concurrent/channel/buffered_channel.rb +0 -85
  127. data/lib/concurrent/channel/channel.rb +0 -41
  128. data/lib/concurrent/channel/unbuffered_channel.rb +0 -35
  129. data/lib/concurrent/channel/waitable_list.rb +0 -40
  130. data/lib/concurrent/channels.rb +0 -5
  131. data/lib/concurrent/collection/blocking_ring_buffer.rb +0 -71
  132. data/lib/concurrent/collection/ring_buffer.rb +0 -59
  133. data/lib/concurrent/collections.rb +0 -3
  134. data/lib/concurrent/dereferenceable.rb +0 -108
  135. data/lib/concurrent/executor/java_cached_thread_pool.rb +0 -32
  136. data/lib/concurrent/executor/java_fixed_thread_pool.rb +0 -31
  137. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +0 -29
  138. data/lib/concurrent/executor/ruby_fixed_thread_pool.rb +0 -32
  139. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +0 -73
  140. data/lib/concurrent/logging.rb +0 -20
  141. data/lib/concurrent/obligation.rb +0 -171
  142. data/lib/concurrent/observable.rb +0 -73
  143. data/lib/concurrent/options_parser.rb +0 -48
  144. data/lib/concurrent/utility/processor_count.rb +0 -152
  145. data/lib/extension_helper.rb +0 -37
@@ -1,58 +1,78 @@
1
- require 'concurrent/atomic'
1
+ require 'concurrent/atomic/atomic_reference'
2
2
  require 'concurrent/delay'
3
3
 
4
-
5
4
  module Concurrent
6
- # Allows to store lazy evaluated values under keys. Uses `Delay`s.
5
+
6
+ # Hash-like collection that store lazys evaluated values.
7
+ #
7
8
  # @example
8
- # register = Concurrent::LazyRegister.new
9
- # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::Atomic:0x007fd7ecd5e1e0>>
10
- # register[:key]
11
- # #=> nil
12
- # register.add(:key) { Concurrent::Actor.spawn!(Actor::AdHoc, :ping) { -> message { message } } }
13
- # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::Atomic:0x007fd7ecd5e1e0>>
14
- # register[:key]
15
- # #=> #<Concurrent::Actor::Reference /ping (Concurrent::Actor::AdHoc)>
9
+ # register = Concurrent::LazyRegister.new
10
+ # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
11
+ # register[:key]
12
+ # #=> nil
13
+ # register.add(:key) { Concurrent::Actor.spawn!(Actor::AdHoc, :ping) { -> message { message } } }
14
+ # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
15
+ # register[:key]
16
+ # #=> #<Concurrent::Actor::Reference /ping (Concurrent::Actor::AdHoc)>
17
+ #
18
+ # @!macro edge_warning
16
19
  class LazyRegister
20
+
17
21
  def initialize
18
- @data = Atomic.new Hash.new
22
+ @data = AtomicReference.new(Hash.new)
19
23
  end
20
24
 
25
+ # Element reference. Retrieves the value object corresponding to the
26
+ # key object. Returns nil if the key is not found. Raises an exception
27
+ # if the stored item raised an exception when the block was evaluated.
28
+ #
21
29
  # @param [Object] key
22
- # @return value stored under the key
30
+ # @return [Object] value stored for the key or nil if the key is not found
31
+ #
23
32
  # @raise Exception when the initialization block fails
24
33
  def [](key)
25
34
  delay = @data.get[key]
26
- delay.value! if delay
35
+ delay ? delay.value! : nil
27
36
  end
28
37
 
38
+ # Returns true if the given key is present.
39
+ #
29
40
  # @param [Object] key
30
41
  # @return [true, false] if the key is registered
31
42
  def registered?(key)
32
- @data.get.key? key
43
+ @data.get.key?(key)
33
44
  end
34
45
 
35
46
  alias_method :key?, :registered?
47
+ alias_method :has_key?, :registered?
36
48
 
49
+ # Element assignment. Associates the value given by value with the
50
+ # key given by key.
51
+ #
37
52
  # @param [Object] key
38
53
  # @yield the object to store under the key
39
- # @return self
54
+ #
55
+ # @return [LazyRegister] self
40
56
  def register(key, &block)
41
- delay = Delay.new(&block)
42
- @data.update { |h| h.merge key => delay }
57
+ delay = Delay.new(executor: :immediate, &block)
58
+ @data.update { |h| h.merge(key => delay) }
43
59
  self
44
60
  end
45
61
 
46
62
  alias_method :add, :register
63
+ alias_method :store, :register
47
64
 
48
- # Un-registers the object under key, realized or not
49
- # @return self
65
+ # Un-registers the object under key, realized or not.
66
+ #
50
67
  # @param [Object] key
68
+ #
69
+ # @return [LazyRegister] self
51
70
  def unregister(key)
52
71
  @data.update { |h| h.dup.tap { |j| j.delete(key) } }
53
72
  self
54
73
  end
55
74
 
56
75
  alias_method :remove, :unregister
76
+ alias_method :delete, :unregister
57
77
  end
58
78
  end
@@ -0,0 +1,226 @@
1
+ module Concurrent
2
+
3
+ # A `Maybe` encapsulates an optional value. A `Maybe` either contains a value
4
+ # of (represented as `Just`), or it is empty (represented as `Nothing`). Using
5
+ # `Maybe` is a good way to deal with errors or exceptional cases without
6
+ # resorting to drastic measures such as exceptions.
7
+ #
8
+ # `Maybe` is a replacement for the use of `nil` with better type checking.
9
+ #
10
+ # For compatibility with {Concurrent::Concern::Obligation} the predicate and
11
+ # accessor methods are aliased as `fulfilled?`, `rejected?`, `value`, and
12
+ # `reason`.
13
+ #
14
+ # ## Motivation
15
+ #
16
+ # A common pattern in languages with pattern matching, such as Erlang and
17
+ # Haskell, is to return *either* a value *or* an error from a function
18
+ # Consider this Erlang code:
19
+ #
20
+ # ```erlang
21
+ # case file:consult("data.dat") of
22
+ # {ok, Terms} -> do_something_useful(Terms);
23
+ # {error, Reason} -> lager:error(Reason)
24
+ # end.
25
+ # ```
26
+ #
27
+ # In this example the standard library function `file:consult` returns a
28
+ # [tuple](http://erlang.org/doc/reference_manual/data_types.html#id69044)
29
+ # with two elements: an [atom](http://erlang.org/doc/reference_manual/data_types.html#id64134)
30
+ # (similar to a ruby symbol) and a variable containing ancillary data. On
31
+ # success it returns the atom `ok` and the data from the file. On failure it
32
+ # returns `error` and a string with an explanation of the problem. With this
33
+ # pattern there is no ambiguity regarding success or failure. If the file is
34
+ # empty the return value cannot be misinterpreted as an error. And when an
35
+ # error occurs the return value provides useful information.
36
+ #
37
+ # In Ruby we tend to return `nil` when an error occurs or else we raise an
38
+ # exception. Both of these idioms are problematic. Returning `nil` is
39
+ # ambiguous because `nil` may also be a valid value. It also lacks
40
+ # information pertaining to the nature of the error. Raising an exception
41
+ # is both expensive and usurps the normal flow of control. All of these
42
+ # problems can be solved with the use of a `Maybe`.
43
+ #
44
+ # A `Maybe` is unambiguous with regard to whether or not it contains a value.
45
+ # When `Just` it contains a value, when `Nothing` it does not. When `Just`
46
+ # the value it contains may be `nil`, which is perfectly valid. When
47
+ # `Nothing` the reason for the lack of a value is contained as well. The
48
+ # previous Erlang example can be duplicated in Ruby in a principled way by
49
+ # having functions return `Maybe` objects:
50
+ #
51
+ # ```ruby
52
+ # result = MyFileUtils.consult("data.dat") # returns a Maybe
53
+ # if result.just?
54
+ # do_something_useful(result.value) # or result.just
55
+ # else
56
+ # logger.error(result.reason) # or result.nothing
57
+ # end
58
+ # ```
59
+ #
60
+ # @example Returning a Maybe from a Function
61
+ # module MyFileUtils
62
+ # def self.consult(path)
63
+ # file = File.open(path, 'r')
64
+ # Concurrent::Maybe.just(file.read)
65
+ # rescue => ex
66
+ # return Concurrent::Maybe.nothing(ex)
67
+ # ensure
68
+ # file.close if file
69
+ # end
70
+ # end
71
+ #
72
+ # maybe = MyFileUtils.consult('bogus.file')
73
+ # maybe.just? #=> false
74
+ # maybe.nothing? #=> true
75
+ # maybe.reason #=> #<Errno::ENOENT: No such file or directory @ rb_sysopen - bogus.file>
76
+ #
77
+ # maybe = MyFileUtils.consult('README.md')
78
+ # maybe.just? #=> true
79
+ # maybe.nothing? #=> false
80
+ # maybe.value #=> "# Concurrent Ruby\n[![Gem Version..."
81
+ #
82
+ # @example Using Maybe with a Block
83
+ # result = Concurrent::Maybe.from do
84
+ # Client.find(10) # Client is an ActiveRecord model
85
+ # end
86
+ #
87
+ # # -- if the record was found
88
+ # result.just? #=> true
89
+ # result.value #=> #<Client id: 10, first_name: "Ryan">
90
+ #
91
+ # # -- if the record was not found
92
+ # result.just? #=> false
93
+ # result.reason #=> ActiveRecord::RecordNotFound
94
+ #
95
+ # @example Using Maybe with the Null Object Pattern
96
+ # # In a Rails controller...
97
+ # result = ClientService.new(10).find # returns a Maybe
98
+ # render json: result.or(NullClient.new)
99
+ #
100
+ # @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe
101
+ # @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe
102
+ class Maybe
103
+ include Comparable
104
+
105
+ # Indicates that the given attribute has not been set.
106
+ # When `Just` the {#nothing} getter will return `NONE`.
107
+ # When `Nothing` the {#just} getter will return `NONE`.
108
+ NONE = Object.new.freeze
109
+
110
+ # The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`.
111
+ attr_reader :just
112
+
113
+ # The reason for the `Maybe` when `Nothing`. Will be `NONE` when `Just`.
114
+ attr_reader :nothing
115
+
116
+ private_class_method :new
117
+
118
+ # Create a new `Maybe` using the given block.
119
+ #
120
+ # Runs the given block passing all function arguments to the block as block
121
+ # arguments. If the block runs to completion without raising an exception
122
+ # a new `Just` is created with the value set to the return value of the
123
+ # block. If the block raises an exception a new `Nothing` is created with
124
+ # the reason being set to the raised exception.
125
+ #
126
+ # @param [Array<Object>] args Zero or more arguments to pass to the block.
127
+ # @yield The block from which to create a new `Maybe`.
128
+ # @yieldparam [Array<Object>] args Zero or more block arguments passed as
129
+ # arguments to the function.
130
+ #
131
+ # @return [Maybe] The newly created object.
132
+ #
133
+ # @raise [ArgumentError] when no block given.
134
+ def self.from(*args)
135
+ raise ArgumentError.new('no block given') unless block_given?
136
+ begin
137
+ value = yield(*args)
138
+ return new(value, NONE)
139
+ rescue => ex
140
+ return new(NONE, ex)
141
+ end
142
+ end
143
+
144
+ # Create a new `Just` with the given value.
145
+ #
146
+ # @param [Object] value The value to set for the new `Maybe` object.
147
+ #
148
+ # @return [Maybe] The newly created object.
149
+ def self.just(value)
150
+ return new(value, NONE)
151
+ end
152
+
153
+ # Create a new `Nothing` with the given (optional) reason.
154
+ #
155
+ # @param [Exception] error The reason to set for the new `Maybe` object.
156
+ # When given a string a new `StandardError` will be created with the
157
+ # argument as the message. When no argument is given a new
158
+ # `StandardError` with an empty message will be created.
159
+ #
160
+ # @return [Maybe] The newly created object.
161
+ def self.nothing(error = '')
162
+ if error.is_a?(Exception)
163
+ nothing = error
164
+ else
165
+ nothing = StandardError.new(error.to_s)
166
+ end
167
+ return new(NONE, nothing)
168
+ end
169
+
170
+ # Is this `Maybe` a `Just` (successfully fulfilled with a value)?
171
+ #
172
+ # @return [Boolean] True if `Just` or false if `Nothing`.
173
+ def just?
174
+ ! nothing?
175
+ end
176
+ alias :fulfilled? :just?
177
+
178
+ # Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)?
179
+ #
180
+ # @return [Boolean] True if `Nothing` or false if `Just`.
181
+ def nothing?
182
+ @nothing != NONE
183
+ end
184
+ alias :rejected? :nothing?
185
+
186
+ alias :value :just
187
+
188
+ alias :reason :nothing
189
+
190
+ # Comparison operator.
191
+ #
192
+ # @return [Integer] 0 if self and other are both `Nothing`;
193
+ # -1 if self is `Nothing` and other is `Just`;
194
+ # 1 if self is `Just` and other is nothing;
195
+ # `self.just <=> other.just` if both self and other are `Just`.
196
+ def <=>(other)
197
+ if nothing?
198
+ other.nothing? ? 0 : -1
199
+ else
200
+ other.nothing? ? 1 : just <=> other.just
201
+ end
202
+ end
203
+
204
+ # Return either the value of self or the given default value.
205
+ #
206
+ # @return [Object] The value of self when `Just`; else the given default.
207
+ def or(other)
208
+ just? ? just : other
209
+ end
210
+
211
+ private
212
+
213
+ # Create a new `Maybe` with the given attributes.
214
+ #
215
+ # @param [Object] just The value when `Just` else `NONE`.
216
+ # @param [Exception, Object] nothing The exception when `Nothing` else `NONE`.
217
+ #
218
+ # @return [Maybe] The new `Maybe`.
219
+ #
220
+ # @!visibility private
221
+ def initialize(just, nothing)
222
+ @just = just
223
+ @nothing = nothing
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,227 @@
1
+ require 'concurrent/synchronization/abstract_struct'
2
+ require 'concurrent/synchronization'
3
+
4
+ module Concurrent
5
+
6
+ # An thread-safe variation of Ruby's standard `Struct`. Values can be set at
7
+ # construction or safely changed at any time during the object's lifecycle.
8
+ #
9
+ # @see http://ruby-doc.org/core-2.2.0/Struct.html Ruby standard library `Struct`
10
+ module MutableStruct
11
+ include Synchronization::AbstractStruct
12
+
13
+ # @!macro [new] struct_new
14
+ #
15
+ # Factory for creating new struct classes.
16
+ #
17
+ # ```
18
+ # new([class_name] [, member_name]+>) -> StructClass click to toggle source
19
+ # new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass
20
+ # new(value, ...) -> obj
21
+ # StructClass[value, ...] -> obj
22
+ # ```
23
+ #
24
+ # The first two forms are used to create a new struct subclass `class_name`
25
+ # that can contain a value for each member_name . This subclass can be
26
+ # used to create instances of the structure like any other Class .
27
+ #
28
+ # If the `class_name` is omitted an anonymous struct class will be created.
29
+ # Otherwise, the name of this struct will appear as a constant in the struct class,
30
+ # so it must be unique for all structs under this base class and must start with a
31
+ # capital letter. Assigning a struct class to a constant also gives the class
32
+ # the name of the constant.
33
+ #
34
+ # If a block is given it will be evaluated in the context of `StructClass`, passing
35
+ # the created class as a parameter. This is the recommended way to customize a struct.
36
+ # Subclassing an anonymous struct creates an extra anonymous class that will never be used.
37
+ #
38
+ # The last two forms create a new instance of a struct subclass. The number of value
39
+ # parameters must be less than or equal to the number of attributes defined for the
40
+ # struct. Unset parameters default to nil. Passing more parameters than number of attributes
41
+ # will raise an `ArgumentError`.
42
+ #
43
+ # @see http://ruby-doc.org/core-2.2.0/Struct.html#method-c-new Ruby standard library `Struct#new`
44
+
45
+ # @!macro [attach] struct_values
46
+ #
47
+ # Returns the values for this struct as an Array.
48
+ #
49
+ # @return [Array] the values for this struct
50
+ #
51
+ def values
52
+ synchronize { ns_values }
53
+ end
54
+ alias_method :to_a, :values
55
+
56
+ # @!macro [attach] struct_values_at
57
+ #
58
+ # Returns the struct member values for each selector as an Array.
59
+ #
60
+ # A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`).
61
+ #
62
+ # @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order)
63
+ def values_at(*indexes)
64
+ synchronize { ns_values_at(indexes) }
65
+ end
66
+
67
+ # @!macro [attach] struct_inspect
68
+ #
69
+ # Describe the contents of this struct in a string.
70
+ #
71
+ # @return [String] the contents of this struct in a string
72
+ def inspect
73
+ synchronize { ns_inspect }
74
+ end
75
+ alias_method :to_s, :inspect
76
+
77
+ # @!macro [attach] struct_merge
78
+ #
79
+ # Returns a new struct containing the contents of `other` and the contents
80
+ # of `self`. If no block is specified, the value for entries with duplicate
81
+ # keys will be that of `other`. Otherwise the value for each duplicate key
82
+ # is determined by calling the block with the key, its value in `self` and
83
+ # its value in `other`.
84
+ #
85
+ # @param [Hash] other the hash from which to set the new values
86
+ # @yield an options block for resolving duplicate keys
87
+ # @yieldparam [String, Symbol] member the name of the member which is duplicated
88
+ # @yieldparam [Object] selfvalue the value of the member in `self`
89
+ # @yieldparam [Object] othervalue the value of the member in `other`
90
+ #
91
+ # @return [Synchronization::AbstractStruct] a new struct with the new values
92
+ #
93
+ # @raise [ArgumentError] of given a member that is not defined in the struct
94
+ def merge(other, &block)
95
+ synchronize { ns_merge(other, &block) }
96
+ end
97
+
98
+ # @!macro [attach] struct_to_h
99
+ #
100
+ # Returns a hash containing the names and values for the struct’s members.
101
+ #
102
+ # @return [Hash] the names and values for the struct’s members
103
+ def to_h
104
+ synchronize { ns_to_h }
105
+ end
106
+
107
+ # @!macro [attach] struct_get
108
+ #
109
+ # Attribute Reference
110
+ #
111
+ # @param [Symbol, String, Integer] member the string or symbol name of the memeber
112
+ # for which to obtain the value or the member's index
113
+ #
114
+ # @return [Object] the value of the given struct member or the member at the given index.
115
+ #
116
+ # @raise [NameError] if the member does not exist
117
+ # @raise [IndexError] if the index is out of range.
118
+ def [](member)
119
+ synchronize { ns_get(member) }
120
+ end
121
+
122
+ # @!macro [attach] struct_equality
123
+ #
124
+ # Equality
125
+ #
126
+ # @return [Boolean] true if other has the same struct subclass and has
127
+ # equal member values (according to `Object#==`)
128
+ def ==(other)
129
+ synchronize { ns_equality(other) }
130
+ end
131
+
132
+ # @!macro [attach] struct_each
133
+ #
134
+ # Yields the value of each struct member in order. If no block is given
135
+ # an enumerator is returned.
136
+ #
137
+ # @yield the operation to be performed on each struct member
138
+ # @yieldparam [Object] value each struct value (in order)
139
+ def each(&block)
140
+ return enum_for(:each) unless block_given?
141
+ synchronize { ns_each(&block) }
142
+ end
143
+
144
+ # @!macro [attach] struct_each_pair
145
+ #
146
+ # Yields the name and value of each struct member in order. If no block is
147
+ # given an enumerator is returned.
148
+ #
149
+ # @yield the operation to be performed on each struct member/value pair
150
+ # @yieldparam [Object] member each struct member (in order)
151
+ # @yieldparam [Object] value each struct value (in order)
152
+ def each_pair(&block)
153
+ return enum_for(:each_pair) unless block_given?
154
+ synchronize { ns_each_pair(&block) }
155
+ end
156
+
157
+ # @!macro [attach] struct_select
158
+ #
159
+ # Yields each member value from the struct to the block and returns an Array
160
+ # containing the member values from the struct for which the given block
161
+ # returns a true value (equivalent to `Enumerable#select`).
162
+ #
163
+ # @yield the operation to be performed on each struct member
164
+ # @yieldparam [Object] value each struct value (in order)
165
+ #
166
+ # @return [Array] an array containing each value for which the block returns true
167
+ def select(&block)
168
+ return enum_for(:select) unless block_given?
169
+ synchronize { ns_select(&block) }
170
+ end
171
+
172
+ # @!macro [new] struct_set
173
+ #
174
+ # Attribute Assignment
175
+ #
176
+ # Sets the value of the given struct member or the member at the given index.
177
+ #
178
+ # @param [Symbol, String, Integer] member the string or symbol name of the memeber
179
+ # for which to obtain the value or the member's index
180
+ #
181
+ # @return [Object] the value of the given struct member or the member at the given index.
182
+ #
183
+ # @raise [NameError] if the name does not exist
184
+ # @raise [IndexError] if the index is out of range.
185
+ def []=(member, value)
186
+ if member.is_a? Integer
187
+ if member >= @values.length
188
+ raise IndexError.new("offset #{member} too large for struct(size:#{@values.length})")
189
+ end
190
+ synchronize { @values[member] = value }
191
+ else
192
+ send("#{member}=", value)
193
+ end
194
+ rescue NoMethodError
195
+ raise NameError.new("no member '#{member}' in struct")
196
+ end
197
+
198
+ # @!macro struct_new
199
+ def self.new(*args, &block)
200
+ clazz_name = nil
201
+ if args.length == 0
202
+ raise ArgumentError.new('wrong number of arguments (0 for 1+)')
203
+ elsif args.length > 0 && args.first.is_a?(String)
204
+ clazz_name = args.shift
205
+ end
206
+ FACTORY.define_struct(clazz_name, args, &block)
207
+ end
208
+
209
+ FACTORY = Class.new(Synchronization::Object) do
210
+ def define_struct(name, members, &block)
211
+ synchronize do
212
+ clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::Object, name, members, &block)
213
+ members.each_with_index do |member, index|
214
+ clazz.send(:define_method, member) do
215
+ synchronize { @values[index] }
216
+ end
217
+ clazz.send(:define_method, "#{member}=") do |value|
218
+ synchronize { @values[index] = value }
219
+ end
220
+ end
221
+ clazz
222
+ end
223
+ end
224
+ end.new
225
+ private_constant :FACTORY
226
+ end
227
+ end