note description: "[ A notion for a type of event based on the work in a paper by Volkan Arslan, Piotr Nienaltowski and Karine Arnout. The interface generally conforms to the interface written in the paper by Volkan Arslan et al, but has been modified to be have implementation aspect flexibility as well as the ability to stack suspension of events. ]" legal : "See notice at end of class." status : "See notice at end of class."; author : "Paul Bates (paul.a.bates@gmail.com)" date : "$Date$" revision: "$Revision$" class EVENT_TYPE [EVENT_DATA -> TUPLE] inherit SAFE_DISPOSABLE_2 redefine default_create, safe_dispose end feature {NONE} -- Initialization default_create -- Default initialization do subscribers := create_subscribers subscribers.compare_objects end feature {NONE} -- Clean up safe_dispose (a_disposing: BOOLEAN) -- Action to be executed just before garbage collection -- reclaims an object. -- -- `a_disposing': True if Current is being explictly disposed of, False to indicate finalization. do -- Remove all subscribers to prevent potential memory leaks. debug ("warnings") if is_suspended then print ("WARNING: Event is being disposed while in a suspended state, this could indicate a synchronization issue.") end end subscribers.wipe_out subscribers := Void ensure then subscribers_emptied: (old subscribers).is_empty subscribers_detached: subscribers /= Void end feature -- Query is_subscribed (a_action: PROCEDURE [ANY, EVENT_DATA]): BOOLEAN -- Determines if the event already has a subscription for a specified action -- -- `a_action': An action to check an existing subscription for -- `Result': True if the action is already subscribed, False otherwise. require not_is_zombie: not is_zombie do Result := subscribers.has (a_action) end feature -- Element change subscribe (a_action: PROCEDURE [ANY, EVENT_DATA]) -- Subscribes an action to the event. -- -- `a_action': The action to subscribe. require not_is_zombie: not is_zombie a_action_attached: a_action /= Void not_a_action_is_subscribed: not is_subscribed (a_action) do subscribers.force (a_action) ensure a_action_subscribed: is_subscribed (a_action) and then subscribers.count = old subscribers.count + 1 subscribers_cursor_unmoved: subscribers.index = old subscribers.index end unsubscribe (a_action: PROCEDURE [ANY, EVENT_DATA]) -- Unsubscribes an action from the event. -- -- `a_action': A previously subscribed action to unsubscribe. require not_is_zombie: not is_zombie a_action_attached: a_action /= Void a_action_is_subscribed: is_subscribed (a_action) local l_subscribers: like subscribers i: INTEGER do l_subscribers := subscribers i := l_subscribers.index l_subscribers.start l_subscribers.search (a_action) l_subscribers.remove if l_subscribers.valid_index (i) then l_subscribers.go_i_th (i) end ensure a_action_unsubscribed: not is_subscribed (a_action) and then subscribers.count = old subscribers.count - 1 index_at_same_position: (old subscribers.after implies subscribers.after) and (not old subscribers.after implies subscribers.index = old subscribers.index) end feature -- Publication publish (a_args: EVENT_DATA) -- Publish all not suspended actions from the subscription list. -- -- `a_args': Public context arguments to forward to all subscribers require not_is_zombie: not is_zombie a_args_attached: a_args /= Void do if not is_suspended then subscribers.do_all (agent {PROCEDURE [ANY, EVENT_DATA]}.call (a_args)) else debug ("warnings") print ("WARNING: Event will not be published because the event is suspended!%N") end end ensure subscribers_index_unmoved: subscribers.index = old subscribers.index end feature {NONE} -- Access suspension_count: NATURAL_16 -- Number of clients requesting event publising be suspsend. -- Note: Publication will be suspended as long as a single client request suspension feature -- Status report is_suspended: BOOLEAN -- Is the publication of all actions from the subscription list suspended? -- (Answer: no by default.) do Result := suspension_count > 0 end feature -- Status settings suspend_subscription -- Ignore the call of all actions from the subscription list, -- until feature `restore_subscription' is called. -- -- Note: Suspension is based on a stacked number of calls. 3 calls to `suspend_subscription' -- must be match with 3 calls to `restore_subscription' for publication to occur. require not_is_zombie: not is_zombie do suspension_count := suspension_count + 1 ensure subscription_suspended: is_suspended suspension_count_incremented: suspension_count = old suspension_count + 1 end restore_subscription -- Consider again the call of all actions from the subscription list, -- until feature `suspend_subscription' is called. -- -- Note: see `suspend_subscription' for information on stacked suspension. require not_is_zombie: not is_zombie is_suspended: is_suspended do suspension_count := suspension_count - 1 ensure suspension_count_incremented: suspension_count = old suspension_count - 1 end feature {NONE} -- Factory create_subscribers: like subscribers -- Create a new subscriber list. -- Note: Redefine to use an alternative list structure suited to specific needs. -- -- `Result': A list structure used to store subscribers in. do create {LINKED_LIST [PROCEDURE [ANY, EVENT_DATA]]}Result.make ensure result_attached: Result /= Void end feature {NONE} -- Implementation frozen subscribers: LIST [PROCEDURE [ANY, EVENT_DATA]] -- List of routines currently subscribed to the event invariant subscribers_attached: not is_zombie implies subscribers /= Void subscribers_compares_objects: subscribers.object_comparison is_suspended_implies_has_suspensions: is_suspended implies suspension_count > 0 not_is_suspended_implies_has_no_suspensions: not is_suspended implies suspension_count = 0 end