Now one can setup error handler propagation in one way, or two way (sync). The "reset" applies in both way, even if this is a one way propagation to fit current existing usage. Added optional id for the error handlers. Feature renaming according to design changes. Added related autotest cases.
529 lines
11 KiB
Plaintext
529 lines
11 KiB
Plaintext
note
|
|
description : "[
|
|
Error handler or receiver.
|
|
|
|
]"
|
|
legal: "See notice at end of class."
|
|
status: "See notice at end of class."
|
|
date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
|
|
revision: "$Revision: 97980 $"
|
|
|
|
class
|
|
ERROR_HANDLER
|
|
|
|
inherit
|
|
ANY
|
|
|
|
DEBUG_OUTPUT
|
|
|
|
create
|
|
make,
|
|
make_with_id
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
make
|
|
-- Initialize `Current'.
|
|
do
|
|
create {ARRAYED_LIST [ERROR]} errors.make (3)
|
|
create error_added_actions
|
|
end
|
|
|
|
make_with_id (a_id: READABLE_STRING_8)
|
|
-- Build `Current' with optional id `a_id'.
|
|
do
|
|
make
|
|
if a_id = Void then
|
|
id := Void
|
|
else
|
|
create id.make_from_string (a_id)
|
|
end
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
id: detachable IMMUTABLE_STRING_8
|
|
-- Optional identifier for Current handler.
|
|
|
|
primary_error_code: INTEGER
|
|
-- Code of first error in `errors'
|
|
require
|
|
at_least_one_error: has_error
|
|
do
|
|
Result := errors.first.code
|
|
end
|
|
|
|
feature -- Status
|
|
|
|
has_error: BOOLEAN
|
|
-- Has error?
|
|
do
|
|
Result := count > 0
|
|
end
|
|
|
|
count: INTEGER
|
|
-- Number of error
|
|
do
|
|
Result := errors.count
|
|
end
|
|
|
|
is_synchronizing_with (other: ERROR_HANDLER): BOOLEAN
|
|
-- Is Current synchronizing with `other'?
|
|
-- i.e 2 way propagation.
|
|
do
|
|
Result := is_propagating_to (other) and other.is_propagating_to (Current)
|
|
end
|
|
|
|
is_propagating_to (other: ERROR_HANDLER): BOOLEAN
|
|
-- Is Current propagating error to `other'?
|
|
do
|
|
Result := attached propagations as lst and then lst.has (other)
|
|
end
|
|
|
|
feature {ERROR_HANDLER, ERROR_VISITOR} -- Restricted access
|
|
|
|
errors: LIST [ERROR]
|
|
-- Errors container
|
|
|
|
feature -- Status report
|
|
|
|
debug_output: STRING
|
|
-- String that should be displayed in debugger to represent `Current'.
|
|
do
|
|
if has_error then
|
|
Result := count.out + " errors"
|
|
else
|
|
Result := "no error"
|
|
end
|
|
if attached id as l_id then
|
|
Result.prepend ("[" + l_id + "] ")
|
|
end
|
|
if attached propagations as lst then
|
|
check not_empty: not lst.is_empty end
|
|
Result.append_character ('(')
|
|
Result.append (" -> ")
|
|
Result.append_integer (lst.count)
|
|
Result.append_character (':')
|
|
across
|
|
lst as ic
|
|
loop
|
|
if attached ic.item.id as l_id then
|
|
Result.append_character (' ')
|
|
Result.append (l_id)
|
|
end
|
|
end
|
|
Result.append_character (')')
|
|
end
|
|
end
|
|
|
|
feature -- Events
|
|
|
|
error_added_actions: ACTION_SEQUENCE [TUPLE [ERROR]]
|
|
-- Actions triggered when a new error is added
|
|
|
|
feature -- Synchronization
|
|
|
|
add_synchronization (h: ERROR_HANDLER)
|
|
-- Add synchronization between `h' and `Current'
|
|
--| the same handler can be added more than once
|
|
--| it will be synchronized only once
|
|
do
|
|
add_propagation (h)
|
|
h.add_propagation (Current)
|
|
end
|
|
|
|
remove_synchronization (h: ERROR_HANDLER)
|
|
-- Remove synchronization between `h' and `Current'
|
|
do
|
|
remove_propagation (h)
|
|
h.remove_propagation (Current)
|
|
end
|
|
|
|
feature -- One way synchronization: propagation
|
|
|
|
add_propagation (h: ERROR_HANDLER)
|
|
-- Add propagation from `Current' to `h'.
|
|
--| the same handler can be added more than once
|
|
--| it will be synchronized only once
|
|
local
|
|
lst: like propagations
|
|
do
|
|
h.register_propagator (Current)
|
|
lst := propagations
|
|
if lst = Void then
|
|
create {ARRAYED_LIST [ERROR_HANDLER]} lst.make (0)
|
|
lst.compare_references
|
|
propagations := lst
|
|
end
|
|
if not lst.has (h) then
|
|
lst.extend (h)
|
|
end
|
|
end
|
|
|
|
remove_propagation (h: ERROR_HANDLER)
|
|
-- Remove propagation from `Current' to `h'.
|
|
do
|
|
if attached propagations as lst and then not lst.is_empty then
|
|
lst.prune_all (h)
|
|
if lst.is_empty then
|
|
propagations := Void
|
|
end
|
|
end
|
|
h.unregister_propagator (Current)
|
|
end
|
|
|
|
feature {ERROR_HANDLER} -- Synchronization implementation
|
|
|
|
is_associated_with (h: ERROR_HANDLER): BOOLEAN
|
|
do
|
|
if attached propagators as lst then
|
|
Result := lst.has (h)
|
|
end
|
|
end
|
|
|
|
register_propagator (h: ERROR_HANDLER)
|
|
local
|
|
lst: like propagators
|
|
do
|
|
lst := propagators
|
|
if lst = Void then
|
|
create {ARRAYED_LIST [ERROR_HANDLER]} lst.make (1)
|
|
propagators := lst
|
|
end
|
|
if not lst.has (h) then
|
|
lst.extend (h)
|
|
end
|
|
end
|
|
|
|
unregister_propagator (h: ERROR_HANDLER)
|
|
local
|
|
lst: like propagators
|
|
do
|
|
lst := propagators
|
|
if lst /= Void then
|
|
lst.prune_all (h)
|
|
if lst.is_empty then
|
|
propagators := Void
|
|
end
|
|
end
|
|
end
|
|
|
|
propagators: detachable LIST [ERROR_HANDLER]
|
|
-- Handlers propagating to Current.
|
|
-- Needed for `reset'.
|
|
|
|
propagations: detachable LIST [ERROR_HANDLER]
|
|
-- Handlers receiving the propagation.
|
|
|
|
propagate_error_addition (e: ERROR; h_lst: LIST [ERROR_HANDLER])
|
|
-- Called by error_handler during synchronization process.
|
|
-- To prevent infinite cycle, if Currently synchronizing, the `propagations' is Void.
|
|
require
|
|
not h_lst.has (Current)
|
|
local
|
|
lst: like propagations
|
|
do
|
|
h_lst.extend (Current)
|
|
lst := propagations
|
|
propagations := Void
|
|
add_error (e)
|
|
|
|
if lst /= Void then
|
|
across
|
|
lst as c
|
|
loop
|
|
if not h_lst.has (c.item) then
|
|
c.item.propagate_error_addition (e, h_lst)
|
|
end
|
|
end
|
|
propagations := lst
|
|
else
|
|
--| propagating
|
|
end
|
|
end
|
|
|
|
propagate_errors_removal (errs: ITERABLE [ERROR]; h_lst: LIST [ERROR_HANDLER])
|
|
-- Called by error_handler during synchronization process.
|
|
-- To prevent infinite cycle, if Currently synchronizing, the `propagations' is Void.
|
|
require
|
|
not h_lst.has (Current)
|
|
local
|
|
lst: like propagations
|
|
do
|
|
h_lst.extend (Current)
|
|
lst := propagations
|
|
propagations := Void
|
|
across
|
|
errs as ic
|
|
loop
|
|
-- Question: should we use remove_error (ic.item) ?
|
|
errors.prune_all (ic.item)
|
|
end
|
|
|
|
if lst /= Void then
|
|
across
|
|
lst as c
|
|
loop
|
|
if not h_lst.has (c.item) then
|
|
c.item.propagate_errors_removal (errs, h_lst)
|
|
end
|
|
end
|
|
propagations := lst
|
|
else
|
|
--| propagating
|
|
end
|
|
end
|
|
|
|
backpropagate_reset (h_lst: LIST [ERROR_HANDLER])
|
|
-- Called by error_handler during propagation process.
|
|
-- To prevent infinite cycle, if Currently synchronizing, the `propagations' is Void.
|
|
require
|
|
not h_lst.has (Current)
|
|
local
|
|
lst: like propagations
|
|
do
|
|
h_lst.extend (Current)
|
|
lst := propagations
|
|
propagations := Void
|
|
reset
|
|
if lst /= Void then
|
|
across
|
|
lst as c
|
|
loop
|
|
if not h_lst.has (c.item) then
|
|
-- Reset c.item, even if this is not a two way synchronization!
|
|
c.item.backpropagate_reset (h_lst)
|
|
end
|
|
end
|
|
propagations := lst
|
|
else
|
|
--| propagating
|
|
end
|
|
end
|
|
|
|
feature {NONE} -- Event: implementation
|
|
|
|
on_error_added (e: ERROR)
|
|
-- Error `e' was just added.
|
|
local
|
|
sync_list: ARRAYED_LIST [ERROR_HANDLER]
|
|
do
|
|
error_added_actions.call ([e])
|
|
if attached propagations as lst then
|
|
propagations := Void
|
|
create sync_list.make (1 + lst.count)
|
|
sync_list.extend (Current)
|
|
across
|
|
lst as c
|
|
loop
|
|
if not sync_list.has (c.item) then
|
|
c.item.propagate_error_addition (e, sync_list)
|
|
end
|
|
end
|
|
propagations := lst
|
|
end
|
|
end
|
|
|
|
on_errors_removed (errs: ITERABLE [ERROR])
|
|
-- Errors `errs' were just removed.
|
|
local
|
|
sync_list: ARRAYED_LIST [ERROR_HANDLER]
|
|
lst: like propagations
|
|
do
|
|
lst := propagations
|
|
if lst /= Void then
|
|
propagations := Void
|
|
create sync_list.make (1 + lst.count)
|
|
sync_list.extend (Current)
|
|
across
|
|
lst as c
|
|
loop
|
|
if not sync_list.has (c.item) then
|
|
c.item.propagate_errors_removal (errs, sync_list)
|
|
end
|
|
end
|
|
propagations := lst
|
|
end
|
|
end
|
|
|
|
on_reset
|
|
-- `reset' was just called
|
|
local
|
|
sync_list: detachable ARRAYED_LIST [ERROR_HANDLER]
|
|
lst: detachable LIST [ERROR_HANDLER]
|
|
do
|
|
lst := propagators
|
|
if lst /= Void then
|
|
create sync_list.make (1 + lst.count)
|
|
sync_list.extend (Current)
|
|
|
|
propagators := Void
|
|
across
|
|
lst as c
|
|
loop
|
|
if not sync_list.has (c.item) then
|
|
c.item.backpropagate_reset (sync_list)
|
|
end
|
|
end
|
|
propagators := lst
|
|
end
|
|
|
|
-- lst := propagations
|
|
-- propagations := Void
|
|
-- if lst /= Void then
|
|
-- if sync_list = Void then
|
|
-- create sync_list.make (1 + lst.count)
|
|
-- sync_list.extend (Current)
|
|
-- end
|
|
-- across
|
|
-- lst as c
|
|
-- loop
|
|
-- if not sync_list.has (c.item) then
|
|
-- c.item.synchronize_reset_from (sync_list)
|
|
-- end
|
|
-- end
|
|
-- propagations := lst
|
|
-- end
|
|
end
|
|
|
|
feature -- Basic operation
|
|
|
|
add_error (a_error: ERROR)
|
|
-- Add `a_error' to the stack of error
|
|
do
|
|
errors.force (a_error)
|
|
on_error_added (a_error)
|
|
end
|
|
|
|
remove_error (a_error: ERROR)
|
|
-- Remove `a_error' from the stack of error.
|
|
-- And also propagate error removal.
|
|
do
|
|
if propagations /= Void then
|
|
on_errors_removed (<<a_error>>)
|
|
end
|
|
errors.prune_all (a_error)
|
|
end
|
|
|
|
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_GENERAL)
|
|
-- Add custom error to the stack of error
|
|
local
|
|
e: ERROR_CUSTOM
|
|
do
|
|
create e.make (a_code, a_name, a_message)
|
|
add_error (e)
|
|
end
|
|
|
|
append (other: ERROR_HANDLER)
|
|
-- Append errors from `a_err_handler'
|
|
local
|
|
other_errs: LIST [ERROR]
|
|
do
|
|
other_errs := other.errors
|
|
if other_errs.count > 0 then
|
|
from
|
|
other_errs.start
|
|
until
|
|
other_errs.after
|
|
loop
|
|
add_error (other_errs.item)
|
|
other_errs.forth
|
|
end
|
|
end
|
|
ensure
|
|
other_error_appended: other.has_error implies has_error
|
|
new_count: count = old count + other.count
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
as_single_error: detachable ERROR
|
|
-- All error(s) concatenated into one single error.
|
|
do
|
|
if count > 1 then
|
|
create {ERROR_GROUP} Result.make (errors)
|
|
elseif count > 0 then
|
|
Result := errors.first
|
|
end
|
|
ensure
|
|
has_error_implies_result_attached: has_error implies Result /= Void
|
|
end
|
|
|
|
as_string_representation: STRING_32
|
|
-- String representation of all error(s).
|
|
require
|
|
has_error
|
|
do
|
|
if attached as_single_error as e then
|
|
Result := e.string_representation
|
|
else
|
|
check has_error: False end
|
|
Result := {STRING_32} "Error occured"
|
|
end
|
|
end
|
|
|
|
feature -- Element changes
|
|
|
|
concatenate
|
|
-- Concatenate into a single error if any
|
|
do
|
|
if count > 1 and then attached as_single_error as e then
|
|
reset
|
|
add_error (e)
|
|
end
|
|
end
|
|
|
|
reset
|
|
-- Reset Current error handler.
|
|
-- And also reset recursively error handlers propagating to Current (i.e the propagators).
|
|
do
|
|
errors.wipe_out
|
|
on_reset
|
|
ensure
|
|
has_no_error: not has_error
|
|
count = 0
|
|
end
|
|
|
|
remove_all_errors
|
|
-- Remove all errors.
|
|
do
|
|
if errors.count > 0 then
|
|
on_errors_removed (errors)
|
|
errors.wipe_out
|
|
end
|
|
ensure
|
|
has_no_error: not has_error
|
|
count = 0
|
|
end
|
|
|
|
destroy
|
|
-- Destroy Current, and remove any propagations (in the two directions).
|
|
do
|
|
error_added_actions.wipe_out
|
|
if attached propagations as lst then
|
|
propagations := Void
|
|
across
|
|
lst as c
|
|
loop
|
|
c.item.remove_synchronization (Current)
|
|
end
|
|
end
|
|
reset
|
|
end
|
|
|
|
invariant
|
|
propagations_not_empty: attached propagations as lst implies not lst.is_empty
|
|
propagators_not_empty: attached propagators as lst implies not lst.is_empty
|
|
|
|
note
|
|
copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
|
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
|
source: "[
|
|
Eiffel Software
|
|
5949 Hollister Ave., Goleta, CA 93117 USA
|
|
Telephone 805-685-1006, Fax 805-685-6869
|
|
Website http://www.eiffel.com
|
|
Customer support http://support.eiffel.com
|
|
]"
|
|
end
|