summaryrefslogtreecommitdiffstats
path: root/lib/edoors/particle.rb
blob: 864d1e7eff35300e1e6a52b21e291bf089f59af0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#! /usr/bin/env ruby
# -*- coding: UTF-8 -*-
#
# Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
#
# This file is part of edoors-ruby.
#
# edoors-ruby is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# edoors-ruby is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with edoors-ruby.  If not, see <http://www.gnu.org/licenses/>.

require 'time'
#
module Edoors
    #
    class Particle
        #
        # creates a Particle object from the arguments.
        #
        # @param [Hash] o a customizable set of options
        #
        # @option o 'ts' [String]
        #   creation time
        # @option o 'src' [String]
        #   Iota where it's originated from
        # @option o 'dst' [String]
        #   Iota where it's heading to
        # @option o 'room' [String]
        #   Room path part of the current destination
        # @option o 'door' [String]
        #   Door path part of the current destination
        # @option o 'action' [String]
        #   action part of the current destination
        # @option o 'dsts' [String]
        #   fifo of path?action strings where to travel to
        # @option o 'link_keys' [String]
        #   unordered keys used has payload keys to build link_value
        # @option o 'payload' [String]
        #   the data carried by this particle
        # @option o 'merged' [String]
        #   list of merged particles
        #
        # @see Spin#require_p require a Particle
        #
        def initialize o={}
            @ts = Time.now      # creation time
            @src = nil          # Iota where it's originated from
            @dst = nil          # Iota where it's heading to
            @room = nil         # Room path part of the current destination
            @door = nil         # Door path part of the current destination
            @action = nil       # action part of the current destination
            @dsts = []          # fifo of path?action strings where to travel to
            @link_keys = []     # unordered keys used has payload keys to build link_value
            @link_value = {}    # the payload keys and values corresponding to the link keys
            @payload = {}       # the data carried by this particle
            @merged = []        # list of merged particles
            #
            if not o.empty?
                @ts = Time.parse(o['ts']) if o['ts']
                @room = o['room']
                @door = o['door']
                @action = o['action']
                @payload = o['payload']||{}
                @src = o['spin'].search_down o['src'] if o['src']
                @dst = o['spin'].search_down o['dst'] if o['dst']
                add_dsts *o['dsts'] if o['dsts']
                set_link_keys *o['link_keys'] if o['link_keys']
                o['merged'].each do |particle|
                    merge! Particle.json_create(particle.merge!('spin'=>o['spin']))
                end if o['merged']
            end
        end
        #
        # called by JSON#generate to serialize the Particle object into JSON data
        #
        # @param [Array] a belongs to JSON generator
        #
        def to_json *a
            {
                'kls'       => self.class.name,
                'ts'        => @ts,
                'src'       => (@src ? @src.path : nil ),
                'dst'       => (@dst ? @dst.path : nil ),
                'room'      => @room,
                'door'      => @door,
                'action'    => @action,
                'dsts'      => @dsts,
                'link_keys' => @link_keys,
                'payload'   => @payload,
                'merged'    => @merged
            }.to_json *a
        end
        #
        # creates a Particle object from a JSON data
        #
        # @param [Hash] o belongs to JSON parser
        #
        # @raise Edoors::Exception if the json kls attribute is wrong
        #
        def self.json_create o
            raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name
            self.new o
        end
        #
        # clears all attributes
        #
        # @see Spin#release_p called whe na Particle is released
        #
        def reset!
            clear_merged! ( @src ? @src : ( @dst ? @dst : nil ) )
            @ts = @src = @dst = @room = @door = @action = nil
            @dsts.clear
            @link_value.clear
            @link_keys.clear
            @payload.clear
        end
        #
        # sets @src, @ts, and reset others
        #
        # @see Particle#apply_link! called when a Link is applied
        # @see Door#_send called when a Door sends a Particle
        #
        def init! src
            @src = src
            @ts = Time.now
            @dst = @room = @door = @action = nil
        end
        #
        attr_reader :ts, :src, :dst, :room, :door, :action, :payload, :link_value
        #
        # returns the next destination
        #
        def next_dst
            @dsts[0]
        end
        #
        # clears the destination list
        #
        def clear_dsts!
            @dsts.clear
        end
        #
        # adds destinations to the destination list
        #
        # @param [Array] dsts destinations to add
        #
        # @raise Edoors::Exception if a destination is not acceptable
        #
        # The parameters are checked before beeing added.
        # they must not be empty or be '?' or start with '/'
        # or contain '/?' or '//' or '\s+'
        #
        def add_dsts *dsts
            dsts.each do |dst|
                if dst.empty? or dst==Edoors::ACT_SEP or dst[0]==Edoors::PATH_SEP \
                    or dst=~/\/\?/ or dst=~/\/{2,}/ or dst=~/\s+/
                    raise Edoors::Exception.new "destination #{dst} is not acceptable"
                end
                @dsts << dst
            end
        end
        #
        # adds a destination to the destination list
        #
        # @param [String] a the action
        # @param [String] d the destination
        #
        def add_dst a, d=''
            add_dsts d+Edoors::ACT_SEP+a
        end
        #
        # sets the current destination
        #
        # @param [String] a the action
        # @param [String Iota] d the destination
        #
        def set_dst! a, d
            @action = a
            if d.is_a? Edoors::Iota
                @dst = d
            else
                @dst = nil
                _split_path! d
            end
        end
        #
        # splits the next destination into @room, @door, @action attributes
        #
        # the @dst attribute is set to nil
        # the @room, @door, @action attributes are set to nil if not defined
        #
        def split_dst!
            @dst = @room = @door = @action = nil
            return if (n = next_dst).nil?
            p, @action = n.split Edoors::ACT_SEP
            _split_path! p
        end
        #
        # called by Particle#split_dst! to split the path part of the destination
        #
        # @param [String] p path to be splitted
        #
        def _split_path! p
            i = p.rindex Edoors::PATH_SEP
            if i.nil?
                @room = nil
                @door = p
            else
                @room = p[0..i-1]
                @door = p[i+1..-1]
            end
            @door = nil if @door.empty?
        end
        private :_split_path!
        #
        # sets the current destination and shift the head of destination list
        #
        # @param [Iota] dst the current destination
        #
        # @see Room#_route routing success
        # @see Room#_send routing failure
        #
        def dst_routed! dst
            @dst = dst
            @dsts.shift
        end
        #
        # sets the error message, the destination and action
        #
        # @param [String] e error message
        # @param [Iota] dst the destination, @src if nil
        #
        # the error message is set into @payload[[Edoors::FIELD_ERROR_MSG]
        #
        def error! e, dst=nil
            @action = Edoors::ACT_ERROR
            @dst = dst||@src
            @payload[Edoors::FIELD_ERROR_MSG]=e
        end
        #
        # applies the effects of the given Link
        #
        # @param [Link] lnk the link to apply effects
        #
        # updates @src with Link @src, clears the destination list
        # adds the Link destinations to @dsts, sets the @link_keys
        #
        def apply_link! lnk
            init! lnk.door
            clear_dsts!
            add_dsts *lnk.dsts
            set_link_keys *lnk.keys
        end
        #
        # adds/updates a key value pair into payload
        #
        # @param [String] k the key
        # @param [Object] v the value
        #
        # \@link_value attribute will be updated if impacted
        #
        def []=  k, v
            @link_value[k] = v if @link_keys.include? k
            @payload[k] = v
        end
        alias :set_data :[]=
        #
        # destroys the value paired with a key
        #
        # @param [String] k the key
        #
        # @return the associated value
        #
        # \@link_value attribute will be updated if impacted
        #
        def del_data k
            @link_value.delete k if @link_keys.include? k
            @payload.delete k
        end
        #
        # retrieves a data value from a key
        #
        # @param [String] k the key
        #
        def [] k
            @payload[k]
        end
        #
        alias :get_data :[]
        alias :data :[]
        #
        # clones the payload of the given Particle
        #
        # @param [Particle] p the Particle to clone the payload of
        #
        def clone_data p
            @payload = p.payload.clone
        end
        #
        # sets the links keys
        #
        # @param [Array] args list of keys to set
        #
        # \@link_value attribute will be updated
        #
        def set_link_keys *args
            @link_keys.clear if not @link_keys.empty?
            args.compact!
            args.each do |lf|
                @link_keys << lf
            end
            @link_value = @payload.select { |k,v| @link_keys.include? k }
        end
        #
        # tries to link the Particle with the given Link
        #
        # @param [Link] link the link to try to link with
        #
        # returns true if the value of the Link is nil
        # otherwise checks if the extracted key values pairs from the Particle
        # payload using the Link value keys as selectors, equals the Link value
        #
        # @return [Boolean] true if the Link links with the Particle
        #
        def link_with? link
            return true if link.value.nil?
            link.value.keys.inject({}) { |h,k| h[k]=@payload[k] if @payload.has_key?(k); h }.eql? link.value
        end
        #
        # merges the given Particle in
        #
        # @param [Particle] p the Particle to merge in
        #
        def merge! p
            @merged << p
        end
        #
        # returns a merged Particle
        #
        # @param [Integer] i the index into the merged Particle list
        #
        def merged i
            @merged[i]
        end
        #
        # shifts the merged Particle list
        #
        def merged_shift
            @merged.shift
        end
        #
        # recursively clears the merged Particle list
        #
        # @param [Boolean] r releases the cleared Particle if true
        #
        def clear_merged! r=false
            @merged.each do |p|
                p.clear_merged! r
                r.release_p p if r
            end
            @merged.clear
        end
        #
        # Yields each element in @merged
        #
        def each_merged
            return if not block_given?
            @merged.each do |p| yield p end
        end
        #
    end
    #
end
#
# EOF