This email gives an overview of the design of NotifyingCollections, and
addresses all of the points raised by Stephen in earlier emails.

The NotifyingCollections code is too large to post to the list, but can
be accessed at http://nettool.sourceforge.net/nc/


Design Overview
---------------

NotifyingCollections is a decorator package that provides event-based
notification functionality for the collections api. Decorators are
provided for Collection, List, Set, SortedSet, Bag, SortedBag, Map and
SortedMap. The iterators and sub-views (e.g. List#subList) are also
first class decorators (and are fail-fast). All of the decorator
classes implement the NotifyingDecorator interface, which provides
common functionality for managing listeners (adding, removing, etc.). 

A collection is decorated using the usual static method pattern, e.g.:

 NotifyingCollection nc =
    NotifyingCollectionsUtils.notifyingCollection( c );

There are two types of listeners. A 'post-listener' implements the
CollectionListener interface, and it receives a CollectionEvent *after*
the actual modification has occurred (as you might expect). 

public interface CollectionListener extends EventListener
{
   void collectionEventOccurred(CollectionEvent event);
}


A 'pre-listener' implements the VetoingCollectionListener interface,
and it receives the event *before* the modification takes place. This
gives the pre-listener a chance to veto the proposed modification
before it occurs.

public interface VetoingCollectionListener extends EventListener
{
 /**
  * @throws ModificationVetoException to indicate that the
  *  proposed event should be vetoed.
  */

   void vetoableModificationOccurring( ModifyingEvent event );
}


A notifying decorator can be configured to support either or both of
these listener mechanisms.

The base event type fired by NotifyingCollections is CollectionEvent.
This has two subclasses, ModifyingEvent and NonModifyingEvent.
NonModifyingEvents are only fired in exceptional circumstances, i.e.
when a listener vetoes a modification (ModificationVetoEvent), or when
an exception occurs during an operation on the decorated collection
(CollectionExceptionEvent). All normal collection operations result in
instances of ModifyingEvent being fired.

ModifyingEvent provides limited information about the event (actually
just the size of the collection before and after the modification).
Subclasses of ModifyingEvent (which is an abstract class) can choose to
provide as much or as little detail as desired. This capability is
provided through the use of a pluggable "event package". The author of
an event package implements one or many of the event factory interfaces
defined by NotifyingCollections (one for each main collection type),
and passes an instance of such an event factory to the decorator
factory methods in NotifyingCollectionsUtils.

NotifyingCollections includes two** event package implementations,
'simple' and 'rich'. SimpleCollectionEvent is fast and light, and does
not provide additional functionality above and beyond that specified by
ModifyingEvent. RichCollectionEvent is a heavyweight implementation. It
has an event hierarchy (i.e. AddEvent, RemoveEvent etc.), and
significant additional functionality. In particular,
RichCollectionEvent implements the ReplayableEvent interface. This
interface defines two operations, #replay and #undo, which permit the
event's actions to be replayed (or undone) on the decorated collection,
*or* on any arbitrary target collection. In effect, RichCollectionEvent
can be used to capture the entire history of a collection, and can be
used to roll back a collection to any previous state. This is a very
powerful facility.

** The current build includes a third event package 'monolithic', which
illustrates some of the limitations of dispensing with an event
hierarchy and using a single event class to capture rich mixed-type
data.


FAQ, comments, and responses
----------------------------

>> How much information about the collection modification can an event
capture?

Just about everything. For any given method, the fired event can
access:

- the entire state of the collection before and after the modification,
- the signature of the invoked method,
- the arguments to the method,
- the return value,
- any exceptions that occur,
- the state of the decorator (i.e. access to the listeners).


>> 2) The event dispatch sometimes assumes too much. For example the
List addAll() method validates the index as being in range, but the
LazyList implementation expects out of range indices. The modCount in
the iterators could face the same problem.

This issue didn't occur to me earlier in development as I had not
considered the case of contract violators such as LazyList. And in fact
the explicit range checks by NotifyingList are superfluous as the
decorated list will eventually throw an IndexOutOfBoundsException
anyway. The offending code has been removed and LazyList should work
fine now (though I have not tested against it).


>> 3) Similar to (2), the boolean result flag cannot be relied on to
give accurate results as to whether the collection has changed.

Quite correct. The result flag is no longer relied upon - the event is
fired regardless of the result value. Again thank you for the pointer.


>> 1) I dislike that a new instance of the event factory has to be
created for each collection. The factory should be capable of being
shared. This can be done by passing the init() parameters to each of
the other methods.

This is one point on which I disagree with Stephen. Indeed a new
instance of the appropriate event factory is referenced by each
decorator instance. This instance is initialized with references to the
decorator and the decorated collection. I do not consider this to be
significant in terms of storage (in the context of the number of
objects/events likely to be generated by the decorator). The
alternative (as suggested above) is to pass these instances with each
call to the factory, i.e. one call per event. This means that a method
like this:

 public ModifyingEvent createAddEvent(Object addItem);

becomes

 public ModifyingEvent createAddEvent(
        NotifyingCollection decorator,
        Collection backing,
        Object addItem);

or maybe even something like this:

 public ModifyingEvent createAddEvent(
        NotifyingList decorator,
        List backing,
        Object addItem,
        int index);
        

This essentially returns us to the days of procedural programming, in
the name of unproven optimization. Though I have not run any
comparative tests, it's questionable if in an intensive session (where
thousands or millions of events are produced in seconds) the saving in
storage (two object references) is worth the additional overhead of
pushing the same two object references onto the stack each time an
event occurs. The optimization to rid ourselves of those superflous
stack operations is to cache the two references in an object instance,
i.e. the current implementation. And of course the existing
implementation is cleaner and easier to understand and to code to, the
usual benefits of object-oriented programming.

Also, some event factory implementations may have reasons to maintain
state. For example, the 'rich' event package's ListEventFactory keeps a
member "indexOffset" which is used to calculate the indices of sub-list
elements relative to the master list. If the factory instance is shared
it means that the indexOffset parameter would have to be passed in each
time an event occurs (stack ops again), and it would also mean that
ListEventFactory could not inherit from CollectionEventFactory as the
indexOffset parameter would require changing the signatures of the
methods inherited from CollectionEventFactory.



>> 4) What if I want vetoes to have no effect, rather than throw an
exception.

While it would be very simple to provide a mechanism to disable veto
exception throwing, to do so would result in a violation of the
Collections API. Let's say we have an empty NotifyingCollection and we
attempt to add an element 'a' as below, but this addition gets vetoed
by a VetoingCollectionListener.

 boolean result = nc.add( "a" ); // gets vetoed

The *only* thing we can do here is throw an exception, as the API is
quite specific:

Collection#add: ... If a collection refuses to add a particular element
for any reason other than that it already contains the element, it must
throw an exception (rather than returning false). This preserves the
invariant that a collection always contains the specified element after
this call returns.


>> 6) There are a lot of classes to get your head around - its very OO.

The number of event classes is dependent upon the event package. The
'simple' event package has only one event class. I will agree that in
the earlier releases of NotifyingCollections, the main event package
was bloated and didn't even have the most useful semantics. It's been
entirely rewritten, and is smaller and hopefully much improved now.
Also, the factory classes are hidden away in subpackages to reduce the
clutter (most users will never need to know anything about the
factories). I hope you'll find that the latest releases are much leaner
and cleaner. 


>> 5) It is quite hard to get info out about events. Typecasting is
always needed.

The typecasting is an inevitable consequence of the flexible event
system. Either we tie ourselves to one set of events, or there must be
typecasting. I don't see this as a major problem - it is fair to say
that users of the Collections API are well used to typecasting. Also,
it should be easier to 'get info' now, as the ModifyingEvent class
(which is in the main package) now includes the most basic event info,
i.e. the change in size caused by the event. Previously one had to
typecast to a specific event package implementation to find out
anything about the event, so this concern certainly was valid.


>> Event data is theoretically flexible, but limited in practice. In
particular, the post-event cannot contain a copy of the state of the
collection, or the size before the change occurred.

This is true for earlier versions, but the design has been corrected
(once again thanks for your assistance). Event data is now flexible and
complete. (In particular see the #finalizeState mechanism).


>> [NotifyingCollections is] Unsuited to certain collections (e.g.
LazyList)

This was because of explicit range checks in NotifyingList which turned
out to be superflous, and have been removed. Thank you for pointing
this out.


>> Should give reasonable performance.

Performance depends upon the chosen event package. The default 'simple'
package is lightning fast.


- Neil












---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to