Improved error library by refactorying the sync as two way propagation.

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.
This commit is contained in:
2016-01-12 16:09:29 +01:00
parent 1b2496b7f0
commit 3bb9101b07
5 changed files with 385 additions and 95 deletions

View File

@@ -5,11 +5,13 @@ project
error = "error.ecf" error = "error.ecf"
note note
-- title: title: Error framework
-- description: description: "[
-- tags: Errors and associated handler, to manage errors and also provides a way to synchronize one or many error handlers.
-- license: This is convenient to propagate error from a layer to another without adding unwanted dependencies.
-- copyright: tags: error
-- link[doc]: "Documentation" http:// license: Eiffel Forum License v2
copyright: Jocelyn Fiat, Eiffel Software and others.
link[license]: http://www.eiffel.com/licensing/forum.txt
end end

View File

@@ -16,13 +16,13 @@ create
feature {NONE} -- Initialization feature {NONE} -- Initialization
make (a_code: INTEGER; a_name: like name; a_message: detachable like message) make (a_code: INTEGER; a_name: like name; a_message: detachable READABLE_STRING_GENERAL)
-- Initialize `Current'. -- Initialize `Current'.
do do
code := a_code code := a_code
name := a_name name := a_name
if a_message /= Void then if a_message /= Void then
message := a_message message := a_message.as_string_32
else else
message := {STRING_32} "Error: " + a_name + " (code=" + a_code.out + ")" message := {STRING_32} "Error: " + a_name + " (code=" + a_code.out + ")"
end end
@@ -45,7 +45,7 @@ feature -- Visitor
end end
note note
copyright: "2011-2012, Eiffel Software and others" copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[ source: "[
Eiffel Software Eiffel Software

View File

@@ -1,5 +1,8 @@
note note
description : "Objects that handle error..." description : "[
Error handler or receiver.
]"
legal: "See notice at end of class." legal: "See notice at end of class."
status: "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) $" date: "$Date: 2015-10-10 00:55:41 +0200 (sam., 10 oct. 2015) $"
@@ -14,7 +17,8 @@ inherit
DEBUG_OUTPUT DEBUG_OUTPUT
create create
make make,
make_with_id
feature {NONE} -- Initialization feature {NONE} -- Initialization
@@ -25,8 +29,22 @@ feature {NONE} -- Initialization
create error_added_actions create error_added_actions
end 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 feature -- Access
id: detachable IMMUTABLE_STRING_8
-- Optional identifier for Current handler.
primary_error_code: INTEGER primary_error_code: INTEGER
-- Code of first error in `errors' -- Code of first error in `errors'
require require
@@ -49,6 +67,19 @@ feature -- Status
Result := errors.count Result := errors.count
end 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 feature {ERROR_HANDLER, ERROR_VISITOR} -- Restricted access
errors: LIST [ERROR] errors: LIST [ERROR]
@@ -64,6 +95,25 @@ feature -- Status report
else else
Result := "no error" Result := "no error"
end 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 end
feature -- Events feature -- Events
@@ -77,134 +127,263 @@ feature -- Synchronization
-- Add synchronization between `h' and `Current' -- Add synchronization between `h' and `Current'
--| the same handler can be added more than once --| the same handler can be added more than once
--| it will be synchronized only once --| it will be synchronized only once
local
lst: like synchronized_handlers
do do
lst := synchronized_handlers add_propagation (h)
if lst = Void then h.add_propagation (Current)
create {ARRAYED_LIST [ERROR_HANDLER]} lst.make (0)
lst.compare_references
synchronized_handlers := lst
end
if lst.has (h) then
check attached h.synchronized_handlers as h_lst and then h_lst.has (Current) end
else
lst.extend (h)
h.add_synchronization (Current)
end
end end
remove_synchronization (h: ERROR_HANDLER) remove_synchronization (h: ERROR_HANDLER)
-- Remove synchronization between `h' and `Current' -- Remove synchronization between `h' and `Current'
do do
if attached synchronized_handlers as lst and then not lst.is_empty then remove_propagation (h)
synchronized_handlers := Void 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) lst.prune_all (h)
h.remove_synchronization (Current)
synchronized_handlers := lst
if lst.is_empty then if lst.is_empty then
synchronized_handlers := Void propagations := Void
end end
end end
h.unregister_propagator (Current)
end end
feature {ERROR_HANDLER} -- Synchronization implementation feature {ERROR_HANDLER} -- Synchronization implementation
synchronized_handlers: detachable LIST [ERROR_HANDLER] is_associated_with (h: ERROR_HANDLER): BOOLEAN
-- Synchronized handlers
synchronize_error_from (e: ERROR; h_lst: LIST [ERROR_HANDLER])
-- Called by error_handler during synchronization process
-- if `synchronized_handlers' is Void, this means Current is synchronizing
-- this is to prevent infinite cycle iteration
require
not h_lst.has (Current)
do do
h_lst.extend (Current) if attached propagators as lst then
Result := lst.has (h)
if attached synchronized_handlers as lst then
synchronized_handlers := Void
add_error (e)
across
lst as c
loop
if not h_lst.has (c.item) then
c.item.synchronize_error_from (e, h_lst)
end
end
synchronized_handlers := lst
else
-- In synchronization
end end
end end
synchronize_reset_from (h_lst: LIST [ERROR_HANDLER]) register_propagator (h: ERROR_HANDLER)
-- Called by error_handler during synchronization process local
-- if `synchronized_handlers' is Void, this means Current is synchronizing lst: like propagators
-- this is to prevent infinite cycle iteration 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 require
not h_lst.has (Current) not h_lst.has (Current)
local
lst: like propagations
do do
h_lst.extend (Current) h_lst.extend (Current)
if attached synchronized_handlers as lst then lst := propagations
synchronized_handlers := Void propagations := Void
reset add_error (e)
if lst /= Void then
across across
lst as c lst as c
loop loop
if not h_lst.has (c.item) then if not h_lst.has (c.item) then
c.item.synchronize_reset_from (h_lst) c.item.propagate_error_addition (e, h_lst)
end end
end end
synchronized_handlers := lst propagations := lst
else else
-- In synchronization --| 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
end end
feature {NONE} -- Event: implementation feature {NONE} -- Event: implementation
on_error_added (e: ERROR) on_error_added (e: ERROR)
-- Error `e' was just added -- Error `e' was just added.
local local
sync_list: LINKED_LIST [ERROR_HANDLER] sync_list: ARRAYED_LIST [ERROR_HANDLER]
do do
error_added_actions.call ([e]) error_added_actions.call ([e])
if attached synchronized_handlers as lst then if attached propagations as lst then
synchronized_handlers := Void propagations := Void
create sync_list.make create sync_list.make (1 + lst.count)
sync_list.extend (Current) sync_list.extend (Current)
across across
lst as c lst as c
loop loop
if not sync_list.has (c.item) then if not sync_list.has (c.item) then
c.item.synchronize_error_from (e, sync_list) c.item.propagate_error_addition (e, sync_list)
end end
end end
synchronized_handlers := lst 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
end end
on_reset on_reset
-- `reset' was just called -- `reset' was just called
local local
sync_list: LINKED_LIST [ERROR_HANDLER] sync_list: detachable ARRAYED_LIST [ERROR_HANDLER]
lst: detachable LIST [ERROR_HANDLER]
do do
if attached synchronized_handlers as lst then lst := propagators
synchronized_handlers := Void if lst /= Void then
create sync_list.make create sync_list.make (1 + lst.count)
sync_list.extend (Current) sync_list.extend (Current)
propagators := Void
across across
lst as c lst as c
loop loop
if not sync_list.has (c.item) then if not sync_list.has (c.item) then
c.item.synchronize_reset_from (sync_list) c.item.backpropagate_reset (sync_list)
end end
end end
synchronized_handlers := lst propagators := lst
end 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 end
feature -- Basic operation feature -- Basic operation
@@ -216,7 +395,17 @@ feature -- Basic operation
on_error_added (a_error) on_error_added (a_error)
end end
add_error_details, add_custom_error (a_code: INTEGER; a_name: STRING; a_message: detachable READABLE_STRING_32) 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 -- Add custom error to the stack of error
local local
e: ERROR_CUSTOM e: ERROR_CUSTOM
@@ -285,11 +474,22 @@ feature -- Element changes
end end
reset reset
-- Reset error handler -- 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 do
if errors.count > 0 then if errors.count > 0 then
on_errors_removed (errors)
errors.wipe_out errors.wipe_out
on_reset
end end
ensure ensure
has_no_error: not has_error has_no_error: not has_error
@@ -297,22 +497,26 @@ feature -- Element changes
end end
destroy destroy
-- Destroy Current, and remove any synchronization -- Destroy Current, and remove any propagations (in the two directions).
do do
error_added_actions.wipe_out error_added_actions.wipe_out
if attached synchronized_handlers as lst then if attached propagations as lst then
propagations := Void
across across
lst as c lst as c
loop loop
c.item.remove_synchronization (Current) c.item.remove_synchronization (Current)
end end
end end
synchronized_handlers := Void
reset reset
end 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 note
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others" copyright: "2011-2016, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[ source: "[
Eiffel Software Eiffel Software

View File

@@ -38,6 +38,30 @@ feature -- Test routines
end end
test_propagation_2
note
testing: "error"
local
h1, h2: ERROR_HANDLER
-- cl: CELL [INTEGER]
do
create h1.make_with_id ("h1")
create h2.make_with_id ("h2")
h1.add_propagation (h2)
h1.add_custom_error (123, "abc", "abc error occurred")
h1.add_custom_error (456, "def", "def error occurred")
assert ("has 2 errors", h1.count = 2 and h2.count = h1.count)
h2.add_custom_error (789, "ghi", "error occurred only for h2")
assert ("h1 has 2 errors, h2 has 3 errors", h1.count = 2 and h2.count = h1.count + 1)
h1.remove_all_errors
assert ("remove all error from h1, and one remaining on h2", h1.count = 0 and h2.count = 1)
end
test_sync_2 test_sync_2
note note
testing: "error" testing: "error"
@@ -50,7 +74,7 @@ feature -- Test routines
h1.add_synchronization (h2) h1.add_synchronization (h2)
h1.add_custom_error (123, "abc", "abc error occurred") h1.add_custom_error (123, "abc", "abc error occurred")
h1.add_custom_error (456, "abc", "abc error occurred") h1.add_custom_error (456, "def", "def error occurred")
assert ("has 2 errors", h1.count = 2 and h2.count = h1.count) assert ("has 2 errors", h1.count = 2 and h2.count = h1.count)
@@ -108,6 +132,62 @@ feature -- Test routines
end end
test_tree_propag
--| Testing tree propagation
--|
--| |-----> batch ..........
--| | :
--| db ---> core ---> app ----> ui :
--| : : : : :
--| :.......:.........:........:...:.....> logger
--|
note
testing: "error"
local
hdb, hcore, happ, hui, hlog, hbat: ERROR_HANDLER
do
create hdb.make_with_id ("db")
create hcore.make_with_id ("core")
create happ.make_with_id ("app")
create hui.make_with_id ("ui")
create hlog.make_with_id ("logger")
create hbat.make_with_id ("batch")
hdb.add_propagation (hcore)
hcore.add_propagation (happ)
hcore.add_propagation (hbat)
happ.add_propagation (hui)
hdb.add_propagation (hlog)
hcore.add_propagation (hlog)
happ.add_propagation (hlog)
hui.add_propagation (hlog)
hbat.add_propagation (hlog)
hdb.add_custom_error (1, "db", "database connection error")
hdb.add_custom_error (2, "db", "database query error")
assert ("2 errors", hdb.count = 2 and hcore.count = 2 and happ.count = 2 and hui.count = 2 and hlog.count = 2)
hcore.add_custom_error (11, "core", "core error")
assert ("3 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 3 and hui.count = 3 and hlog.count = 3)
happ.add_custom_error (101, "app", "app issue")
assert ("2 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 4 and hui.count = 4 and hlog.count = 4)
hui.add_custom_error (1001, "ui", "user interface trouble")
assert ("2 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 4 and hui.count = 5 and hlog.count = 5)
assert ("hbat", hbat.count = 3 and hlog.count = 5)
hbat.add_custom_error (10001, "bat", "Batch issue")
assert ("2 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 4 and hui.count = 5 and hbat.count = 4 and hlog.count = 6)
hbat.remove_all_errors
assert ("2 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 4 and hui.count = 5 and hbat.count = 0 and hlog.count = 2)
hui.remove_all_errors
assert ("2 errors", hdb.count = 2 and hcore.count = 3 and happ.count = 4 and hui.count = 0 and hlog.count = 0)
hdb.reset
assert ("no more errors", hdb.count = 0 and hcore.count = 3 and happ.count = 4 and hui.count = 0 and hlog.count = 0)
end
test_remove_sync test_remove_sync
note note
testing: "error" testing: "error"
@@ -145,9 +225,9 @@ feature -- Test routines
h1, h2, h3: ERROR_HANDLER h1, h2, h3: ERROR_HANDLER
-- cl: CELL [INTEGER] -- cl: CELL [INTEGER]
do do
create h1.make create h1.make_with_id ("h1")
create h2.make create h2.make_with_id ("h2")
create h3.make create h3.make_with_id ("h3")
h1.add_synchronization (h2) h1.add_synchronization (h2)
h2.add_synchronization (h3) h2.add_synchronization (h3)

View File

@@ -1,17 +1,21 @@
<?xml version="1.0" encoding="ISO-8859-1"?> <?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="error_tests" uuid="54F59BB2-AD49-42C7-ABAA-B60765F4F926"> <system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="error_tests" uuid="54F59BB2-AD49-42C7-ABAA-B60765F4F926">
<target name="error_tests"> <target name="error_tests">
<root class="TEST_ERROR" feature="default_create"/> <root class="TEST_ERROR" feature="default_create"/>
<file_rule> <file_rule>
<exclude>/.git$</exclude> <exclude>/.git$</exclude>
<exclude>/EIFGENs$</exclude>
<exclude>/.svn$</exclude> <exclude>/.svn$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule> </file_rule>
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="transitional"> <option warning="true" full_class_checking="true" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true"/> <assertions precondition="true" postcondition="true" check="true" invariant="true"/>
</option> </option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/> <library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="error" location="..\error-safe.ecf" readonly="false"/> <library name="error" location="..\error-safe.ecf" readonly="false">
<option>
<assertions precondition="true" postcondition="true" check="true" invariant="true" supplier_precondition="true"/>
</option>
</library>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/> <library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<tests name="tests" location=".\"/> <tests name="tests" location=".\"/>
</target> </target>