001    /*
002     * Copyright 2003-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.events.observable;
017    
018    import java.util.Collection;
019    
020    /**
021     * Defines a handler for collection modification events.
022     * <p>
023     * This class defines the event handling methods, following the 
024     * <code>preXxx</code> and <code>postXxx</code> naming convention.
025     * It also provides a default implementation that forwards to single methods.
026     * <p>
027     * To write your own handler, you will normally subclass and override the
028     * <code>preEvent</code> and <code>postEvent</code> methods. However, you
029     * could choose to override any individual event method.
030     * <p>
031     * This class could have been implemented as an interface, however to do so
032     * would prevent the addition of extra events in the future. It does mean
033     * that if you subclass this class, you must check it when you upgrade to a
034     * later release.
035     *
036     * @since Commons Events 1.0
037     * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $
038     * 
039     * @author Stephen Colebourne
040     */
041    public class ModificationHandler {
042        
043        /** Singleton factory */
044        static final ModificationHandlerFactory FACTORY = new Factory();
045        
046        /** The collection being observed */
047        private ObservableCollection obsCollection = null;
048        /** The underlying base collection being decorated */
049        private Collection baseCollection = null;
050        /** The root handler */
051        private final ModificationHandler rootHandler;
052        /** The view offset, 0 if not a view */
053        private final int viewOffset;
054        
055        // Constructors
056        //-----------------------------------------------------------------------
057        /**
058         * Constructor.
059         */
060        protected ModificationHandler() {
061            super();
062            this.rootHandler = this;
063            this.viewOffset = 0;
064        }
065    
066        /**
067         * Constructor.
068         * 
069         * @param rootHandler  the base underlying handler
070         * @param viewOffset  the offset on the base collection
071         */
072        protected ModificationHandler(ModificationHandler rootHandler, int viewOffset) {
073            super();
074            this.rootHandler = rootHandler;
075            this.viewOffset = viewOffset;
076        }
077    
078        /**
079         * Initialize the handler.
080         * <p>
081         * The handler cannot be used until this method is called.
082         * However, the handler's setup methods can be called.
083         * All other methods will throw NullPointerException until then.
084         * 
085         * @param coll  the observed collection, must not be null
086         * @param baseColl  the base collection, must not be null
087         * @throws IllegalArgumentException if the collection is null
088         * @throws IllegalStateException if init has already been called
089         */
090        void init(final ObservableCollection coll, Collection baseColl) {
091            if (coll == null) {
092                throw new IllegalArgumentException("Collection must not be null");
093            }
094            if (baseColl == null) {
095                throw new IllegalArgumentException("Base Collection must not be null");
096            }
097            if (this.obsCollection != null) {
098                throw new IllegalArgumentException("init() has already been called");
099            }
100            this.obsCollection = coll;
101            this.baseCollection = baseColl;
102        }
103    
104        // Field access
105        //-----------------------------------------------------------------------
106        /**
107         * Gets the observed collection.
108         * 
109         * @return the observed collection
110         */
111        public ObservableCollection getObservedCollection() {
112            return obsCollection;
113        }
114        
115        /**
116         * Gets the base collection.
117         * 
118         * @return the base collection
119         */
120        protected Collection getBaseCollection() {
121            return baseCollection;
122        }
123        
124        /**
125         * Gets the root handler.
126         * 
127         * @return the root handler
128         */
129        protected ModificationHandler getRootHandler() {
130            return rootHandler;
131        }
132        
133        /**
134         * Gets the view offset.
135         * 
136         * @return the view offset
137         */
138        protected int getViewOffset() {
139            return viewOffset;
140        }
141        
142        // PreListeners
143        //----------------------------------------------------------------------
144        /**
145         * Gets an array of all the pre listeners active in the handler.
146         * <p>
147         * This implementation throws UnsupportedOperationException.
148         * 
149         * @return the listeners
150         * @throws UnsupportedOperationException if the handler does not support listeners
151         */
152        public Object[] getPreModificationListeners() {
153            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
154        }
155        
156        /**
157         * Adds a pre listener to the list held in the handler.
158         * <p>
159         * No error occurs if the listener is <code>null</code>.
160         * <p>
161         * The listener does not necessarily have to be a listener in the classic
162         * JavaBean sense. It is entirely up to the handler as to how it interprets
163         * the listener parameter. A ClassCastException is thrown if the handler
164         * cannot interpret the parameter.
165         * <p>
166         * This implementation throws UnsupportedOperationException.
167         * 
168         * @param listener  the listener to add, may be null (ignored)
169         * @throws ClassCastException if the listener is not of the correct type
170         * @throws UnsupportedOperationException if the handler does not support listeners
171         */
172        public void addPreModificationListener(Object listener) {
173            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
174        }
175        
176        /**
177         * Removes a pre listener to the list held in the handler.
178         * <p>
179         * No error occurs if the listener is not in the list or the type
180         * of the listener is incorrect.
181         * <p>
182         * This implementation throws UnsupportedOperationException.
183         * 
184         * @param listener  the listener to remove, may be null (ignored)
185         * @throws UnsupportedOperationException if the handler does not support listeners
186         */
187        public void removePreModificationListener(Object listener) {
188            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
189        }
190        
191        // PostListeners
192        //----------------------------------------------------------------------
193        /**
194         * Gets an array of all the post listeners active in the handler.
195         * <p>
196         * This implementation throws UnsupportedOperationException.
197         * 
198         * @return the listeners
199         * @throws UnsupportedOperationException if the handler does not support listeners
200         */
201        public Object[] getPostModificationListeners() {
202            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
203        }
204        
205        /**
206         * Adds a post listener to the list held in the handler.
207         * <p>
208         * No error occurs if the listener is <code>null</code>.
209         * <p>
210         * The listener does not necessarily have to be a listener in the classic
211         * JavaBean sense. It is entirely up to the handler as to how it interprets
212         * the listener parameter. A ClassCastException is thrown if the handler
213         * cannot interpret the parameter.
214         * <p>
215         * This implementation throws UnsupportedOperationException.
216         * 
217         * @param listener  the listener to add, may be null (ignored)
218         * @throws ClassCastException if the listener is not of the correct type
219         * @throws UnsupportedOperationException if the handler does not support listeners
220         */
221        public void addPostModificationListener(Object listener) {
222            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
223        }
224        
225        /**
226         * Removes a post listener to the list held in the handler.
227         * <p>
228         * No error occurs if the listener is not in the list or the type
229         * of the listener is incorrect.
230         * <p>
231         * This implementation throws UnsupportedOperationException.
232         * 
233         * @param listener  the listener to remove, may be null (ignored)
234         * @throws UnsupportedOperationException if the handler does not support listeners
235         */
236        public void removePostModificationListener(Object listener) {
237            throw new UnsupportedOperationException("Listeners not supported by " + getClass().getName());
238        }
239        
240        // Event sending
241        //-----------------------------------------------------------------------
242        /**
243         * Handles the pre event.
244         * <p>
245         * This implementation does nothing.
246         * 
247         * @param type  the event type to send
248         * @param index  the index where the change starts, the method param or derived
249         * @param object  the object that will be added/removed/set, the method param or derived
250         * @param repeat  the number of repeats of the add/remove, the method param or derived
251         * @param previous  the previous value that will be removed/replaced, must exist in coll
252         * @param view  the view collection that the change was actioned on, null if no view
253         * @param viewOffset  the offset of the subList view, -1 if unknown
254         */
255        protected boolean preEvent(
256                int type, int index, Object object, int repeat,
257                Object previous, ObservableCollection view, int viewOffset) {
258            return true;
259        }
260    
261        /**
262         * Handles the post event.
263         * <p>
264         * This implementation does nothing.
265         * 
266         * @param modified  true if the method succeeded in changing the collection
267         * @param type  the event type to send
268         * @param index  the index where the change starts, the method param or derived
269         * @param object  the object that was added/removed/set, the method param or derived
270         * @param repeat  the number of repeats of the add/remove, the method param or derived
271         * @param previous  the previous value that was removed/replace, must have existed in coll
272         * @param view  the view collection that the change was actioned on, null if no view
273         * @param viewOffset  the offset of the subList view, -1 if unknown
274         */
275        protected void postEvent(
276                boolean modified, int type, int index, Object object, int repeat,
277                Object previous, ObservableCollection view, int viewOffset) {
278        }
279    
280        // Event handling
281        //-----------------------------------------------------------------------
282        /**
283         * Store data and send event before add(obj) is called.
284         * <p>
285         * This implementation forwards to {@link #preEvent}.
286         * It does not set the index for List implementations.
287         * 
288         * @param object  the object being added
289         * @return true to process modification
290         */
291        protected boolean preAdd(Object object) {
292            return preEvent(ModificationEventType.ADD, -1, object, 1, null, null, -1);
293        }
294    
295        /**
296         * Send an event after add(obj) is called.
297         * <p>
298         * This implementation forwards to {@link #postEvent}.
299         * It does not set the index for List implementations.
300         * 
301         * @param object  the object being added
302         * @param result  the result from the add method
303         */
304        protected void postAdd(Object object, boolean result) {
305            postEvent(result, ModificationEventType.ADD, -1, object, 1, null, null, -1);
306        }
307    
308        //-----------------------------------------------------------------------
309        /**
310         * Store data and send event before add(int,obj) is called on a List.
311         * <p>
312         * This implementation forwards to {@link #preEvent}.
313         * 
314         * @param index  the index to add at
315         * @param object  the object being added
316         * @return true to process modification
317         */
318        protected boolean preAddIndexed(int index, Object object) {
319            return preEvent(ModificationEventType.ADD_INDEXED, index + viewOffset, object, 1, null, null, -1);
320        }
321    
322        /**
323         * Send an event after add(int,obj) is called on a List.
324         * <p>
325         * This implementation forwards to {@link #postEvent}.
326         * 
327         * @param index  the index to add at
328         * @param object  the object being added
329         */
330        protected void postAddIndexed(int index, Object object) {
331            postEvent(true, ModificationEventType.ADD_INDEXED, index + viewOffset, object, 1, null, null, -1);
332        }
333    
334        //-----------------------------------------------------------------------
335        /**
336         * Store data and send event before add(obj,int) is called on a Bag.
337         * <p>
338         * This implementation forwards to {@link #preEvent}.
339         * 
340         * @param object  the object being added
341         * @param nCopies  the number of copies being added
342         * @return true to process modification
343         */
344        protected boolean preAddNCopies(Object object, int nCopies) {
345            return preEvent(ModificationEventType.ADD_NCOPIES, -1, object, nCopies, null, null, -1);
346        }
347    
348        /**
349         * Send an event after add(obj,int) is called on a Bag.
350         * <p>
351         * This implementation forwards to {@link #postEvent}.
352         * The method result is not used by this implementation (Bag violates the
353         * Collection contract)
354         * 
355         * @param object  the object being added
356         * @param nCopies  the number of copies being added
357         * @param result  the method result
358         */
359        protected void postAddNCopies(Object object, int nCopies, boolean result) {
360            postEvent(true, ModificationEventType.ADD_NCOPIES, -1, object, nCopies, null, null, -1);
361        }
362    
363        //-----------------------------------------------------------------------
364        /**
365         * Store data and send event before add(obj) is called on a ListIterator.
366         * <p>
367         * This implementation forwards to {@link #preEvent}.
368         * 
369         * @param index  the index of the iterator
370         * @param object  the object being added
371         * @return true to process modification
372         */
373        protected boolean preAddIterated(int index, Object object) {
374            return preEvent(ModificationEventType.ADD_ITERATED, index + viewOffset, object, 1, null, null, -1);
375        }
376    
377        /**
378         * Send an event after add(obj) is called on a ListIterator.
379         * <p>
380         * This implementation forwards to {@link #postEvent}.
381         * 
382         * @param index  the index of the iterator
383         * @param object  the object being added
384         */
385        protected void postAddIterated(int index, Object object) {
386            // assume collection changed
387            postEvent(true, ModificationEventType.ADD_ITERATED, index + viewOffset, object, 1, null, null, -1);
388        }
389    
390        //-----------------------------------------------------------------------
391        /**
392         * Store data and send event before addAll(coll) is called.
393         * <p>
394         * This implementation forwards to {@link #preEvent}.
395         * 
396         * @param coll  the collection being added
397         * @return true to process modification
398         */
399        protected boolean preAddAll(Collection coll) {
400            return preEvent(ModificationEventType.ADD_ALL, -1, coll, 1, null, null, -1);
401        }
402    
403        /**
404         * Send an event after addAll(coll) is called.
405         * <p>
406         * This implementation forwards to {@link #postEvent}.
407         * 
408         * @param coll  the collection being added
409         * @param collChanged  the result from the addAll method
410         */
411        protected void postAddAll(Collection coll, boolean collChanged) {
412            postEvent(collChanged, ModificationEventType.ADD_ALL, -1, coll, 1, null, null, -1);
413        }
414    
415        //-----------------------------------------------------------------------
416        /**
417         * Store data and send event before addAll(int,coll) is called on a List.
418         * <p>
419         * This implementation forwards to {@link #preEvent}.
420         * 
421         * @param index  the index to addAll at
422         * @param coll  the collection being added
423         * @return true to process modification
424         */
425        protected boolean preAddAllIndexed(int index, Collection coll) {
426            return preEvent(ModificationEventType.ADD_ALL_INDEXED, index + viewOffset, coll, 1, null, null, -1);
427        }
428    
429        /**
430         * Send an event after addAll(int,coll) is called on a List.
431         * <p>
432         * This implementation forwards to {@link #postEvent}.
433         * 
434         * @param index  the index to addAll at
435         * @param coll  the collection being added
436         * @param collChanged  the result from the addAll method
437         */
438        protected void postAddAllIndexed(int index, Collection coll, boolean collChanged) {
439            postEvent(collChanged, ModificationEventType.ADD_ALL_INDEXED, index + viewOffset, coll, 1, null, null, -1);
440        }
441    
442        //-----------------------------------------------------------------------
443        /**
444         * Store data and send event before clear() is called.
445         * <p>
446         * This implementation forwards to {@link #preEvent}.
447         * 
448         * @return true to process modification
449         */
450        protected boolean preClear() {
451            return preEvent(ModificationEventType.CLEAR, -1, null, 1, null, null, -1);
452        }
453    
454        /**
455         * Send an event after clear() is called.
456         * <p>
457         * This implementation forwards to {@link #postEvent}.
458         */
459        protected void postClear() {
460            // assumes a modification occurred
461            postEvent(true, ModificationEventType.CLEAR, -1, null, 1, null, null, -1);
462        }
463    
464        //-----------------------------------------------------------------------
465        /**
466         * Store data and send event before remove(obj) is called.
467         * <p>
468         * This implementation forwards to {@link #preEvent}.
469         * 
470         * @param object  the object being removed
471         * @return true to process modification
472         */
473        protected boolean preRemove(Object object) {
474            return preEvent(ModificationEventType.REMOVE, -1, object, 1, null, null, -1);
475        }
476    
477        /**
478         * Send an event after remove(obj) is called.
479         * <p>
480         * This implementation forwards to {@link #postEvent}.
481         * 
482         * @param object  the object being removed
483         * @param collChanged  the result from the remove method
484         */
485        protected void postRemove(Object object, boolean collChanged) {
486            postEvent(collChanged, ModificationEventType.REMOVE, -1, object, 1, (collChanged ? object : null), null, -1);
487        }
488    
489        //-----------------------------------------------------------------------
490        /**
491         * Store data and send event before remove(int) is called on a List.
492         * <p>
493         * This implementation forwards to {@link #preEvent}.
494         * 
495         * @param index  the index to remove at
496         * @return true to process modification
497         */
498        protected boolean preRemoveIndexed(int index) {
499            // could do a get(index) to determine previousValue
500            // we don't for performance, but subclass may override
501            return preEvent(ModificationEventType.REMOVE_INDEXED, index + viewOffset, null, 1, null, null, -1);
502        }
503    
504        /**
505         * Send an event after remove(int) is called on a List.
506         * <p>
507         * This implementation forwards to {@link #postEvent}.
508         * 
509         * @param index  the index to remove at
510         * @param previousValue  the result from the remove method
511         */
512        protected void postRemoveIndexed(int index, Object previousValue) {
513            postEvent(true, ModificationEventType.REMOVE_INDEXED, index + viewOffset, null, 1, previousValue, null, -1);
514        }
515    
516        //-----------------------------------------------------------------------
517        /**
518         * Store data and send event before remove(obj,int) is called on a Bag.
519         * <p>
520         * This implementation forwards to {@link #preEvent}.
521         * 
522         * @param object  the object being removed
523         * @param nCopies  the number of copies being removed
524         * @return true to process modification
525         */
526        protected boolean preRemoveNCopies(Object object, int nCopies) {
527            return preEvent(ModificationEventType.REMOVE_NCOPIES, -1, object, nCopies, null, null, -1);
528        }
529    
530        /**
531         * Send an event after remove(obj,int) is called on a Bag.
532         * <p>
533         * This implementation forwards to {@link #postEvent}.
534         * 
535         * @param object  the object being removed
536         * @param nCopies  the number of copies being removed
537         * @param collChanged  the result from the remove method
538         */
539        protected void postRemoveNCopies(Object object, int nCopies, boolean collChanged) {
540            postEvent(collChanged, ModificationEventType.REMOVE_NCOPIES, -1, object, nCopies, (collChanged ? object : null), null, -1);
541        }
542    
543        //-----------------------------------------------------------------------
544        /**
545         * Store data and send event before remove() is called on a Buffer.
546         * <p>
547         * This implementation forwards to {@link #preEvent}.
548         * 
549         * @return true to process modification
550         */
551        protected boolean preRemoveNext() {
552            return preEvent(ModificationEventType.REMOVE_NEXT, -1, null, 1, null, null, -1);
553        }
554    
555        /**
556         * Send an event after remove() is called on a Buffer.
557         * <p>
558         * This implementation forwards to {@link #postEvent}.
559         * 
560         * @param removedValue  the previous value at this index
561         */
562        protected void postRemoveNext(Object removedValue) {
563            // assume collection changed
564            postEvent(true, ModificationEventType.REMOVE_NEXT, -1, removedValue, 1, removedValue, null, -1);
565        }
566    
567        //-----------------------------------------------------------------------
568        /**
569         * Store data and send event before remove(obj) is called on an Iterator.
570         * <p>
571         * This implementation forwards to {@link #preEvent}.
572         * 
573         * @param index  the index of the iterator
574         * @param removedValue  the object being removed
575         * @return true to process modification
576         */
577        protected boolean preRemoveIterated(int index, Object removedValue) {
578            return preEvent(ModificationEventType.REMOVE_ITERATED, index + viewOffset, removedValue, 1, removedValue, null, -1);
579        }
580    
581        /**
582         * Send an event after remove(obj) is called on an Iterator.
583         * <p>
584         * This implementation forwards to {@link #postEvent}.
585         * 
586         * @param index  the index of the iterator
587         * @param removedValue  the previous value at this index
588         */
589        protected void postRemoveIterated(int index, Object removedValue) {
590            // assume collection changed
591            postEvent(true, ModificationEventType.REMOVE_ITERATED, index + viewOffset, removedValue, 1, removedValue, null, -1);
592        }
593    
594        //-----------------------------------------------------------------------
595        /**
596         * Store data and send event before removeAll(coll) is called.
597         * <p>
598         * This implementation forwards to {@link #preEvent}.
599         * 
600         * @param coll  the collection being removed
601         * @return true to process modification
602         */
603        protected boolean preRemoveAll(Collection coll) {
604            return preEvent(ModificationEventType.REMOVE_ALL, -1, coll, 1, null, null, -1);
605        }
606    
607        /**
608         * Send an event after removeAll(coll) is called.
609         * <p>
610         * This implementation forwards to {@link #postEvent}.
611         * 
612         * @param coll  the collection being removed
613         * @param collChanged  the result from the removeAll method
614         */
615        protected void postRemoveAll(Collection coll, boolean collChanged) {
616            postEvent(collChanged, ModificationEventType.REMOVE_ALL, -1, coll, 1, null, null, -1);
617        }
618    
619        //-----------------------------------------------------------------------
620        /**
621         * Store data and send event before retainAll(coll) is called.
622         * <p>
623         * This implementation forwards to {@link #preEvent}.
624         * 
625         * @param coll  the collection being retained
626         * @return true to process modification
627         */
628        protected boolean preRetainAll(Collection coll) {
629            return preEvent(ModificationEventType.RETAIN_ALL, -1, coll, 1, null, null, -1);
630        }
631    
632        /**
633         * Send an event after retainAll(coll) is called.
634         * <p>
635         * This implementation forwards to {@link #postEvent}.
636         * 
637         * @param coll  the collection being retained
638         * @param collChanged  the result from the retainAll method
639         */
640        protected void postRetainAll(Collection coll, boolean collChanged) {
641            postEvent(collChanged, ModificationEventType.RETAIN_ALL, -1, coll, 1, null, null, -1);
642        }
643    
644        //-----------------------------------------------------------------------
645        /**
646         * Store data and send event before set(int,obj) is called on a List.
647         * <p>
648         * This implementation forwards to {@link #preEvent}.
649         * 
650         * @param index  the index to add at
651         * @param object  the object being added
652         * @return true to process modification
653         */
654        protected boolean preSetIndexed(int index, Object object) {
655            // could do a get(index) to determine previousValue
656            // we don't for performance, but subclass may override
657            return preEvent(ModificationEventType.SET_INDEXED, index + viewOffset, object, 1, null, null, -1);
658        }
659    
660        /**
661         * Send an event after set(int,obj) is called on a List.
662         * <p>
663         * This implementation forwards to {@link #postEvent}.
664         * 
665         * @param index  the index to add at
666         * @param object  the object being added
667         * @param previousValue  the result from the set method
668         */
669        protected void postSetIndexed(int index, Object object, Object previousValue) {
670            // reference check for modification, in case equals() has issues (eg. performance)
671            postEvent((object != previousValue), ModificationEventType.SET_INDEXED, index + viewOffset, object, 1, previousValue, null, -1);
672        }
673    
674        //-----------------------------------------------------------------------
675        /**
676         * Store data and send event before set(obj) is called on a ListIterator.
677         * <p>
678         * This implementation forwards to {@link #preEvent}.
679         * 
680         * @param index  the index to set at
681         * @param object  the object being added
682         * @param previousValue  the previous value at this index
683         * @return true to process modification
684         */
685        protected boolean preSetIterated(int index, Object object, Object previousValue) {
686            return preEvent(ModificationEventType.SET_ITERATED, index + viewOffset, object, 1, previousValue, null, -1);
687        }
688    
689        /**
690         * Send an event after set(obj) is called on a ListIterator.
691         * <p>
692         * This implementation forwards to {@link #postEvent}.
693         * 
694         * @param index  the index to set at
695         * @param object  the object being added
696         * @param previousValue  the previous value at this index
697         */
698        protected void postSetIterated(int index, Object object, Object previousValue) {
699            // reference check for modification, in case equals() has issues (eg. performance)
700            postEvent((object != previousValue), ModificationEventType.SET_ITERATED, index + viewOffset, object, 1, previousValue, null, -1);
701        }
702    
703        // SortedSet Views
704        //-----------------------------------------------------------------------
705        /**
706         * Creates a new handler for SortedSet subSet.
707         * 
708         * @param fromElement  the from element
709         * @param toElement  the to element
710         */
711        protected ModificationHandler createSubSetHandler(Object fromElement, Object toElement) {
712            return new SetViewHandler(rootHandler);
713        }
714        
715        /**
716         * Creates a new handler for SortedSet headSet.
717         * 
718         * @param toElement  the to element
719         */
720        protected ModificationHandler createHeadSetHandler(Object toElement) {
721            return new SetViewHandler(rootHandler);
722        }
723        
724        /**
725         * Creates a new handler for SortedSet tailSet.
726         * 
727         * @param fromElement  the from element
728         */
729        protected ModificationHandler createTailSetHandler(Object fromElement) {
730            return new SetViewHandler(rootHandler);
731        }
732        
733        /**
734         * Inner class for views.
735         */    
736        protected static class SetViewHandler extends ModificationHandler {
737            
738            /**
739             * Constructor.
740             * 
741             * @param rootHandler  the base underlying handler
742             */
743            protected SetViewHandler(ModificationHandler rootHandler) {
744                super(rootHandler, 0);
745            }
746    
747            /**
748             * Override the preEvent method to forward all events to the 
749             * underlying handler. This method also inserts details of the view
750             * that caused the event.
751             */
752            protected boolean preEvent(
753                    int type, int index, Object object, int repeat,
754                    Object previous, ObservableCollection ignoredView, int offset) {
755    
756                return getRootHandler().preEvent(
757                    type, index, object, repeat,
758                    previous, getObservedCollection(), offset);
759            }
760    
761            /**
762             * Override the postEvent method to forward all events to the 
763             * underlying handler. This method also inserts details of the view
764             * that caused the event.
765             */
766            protected void postEvent(
767                    boolean modified, int type, int index, Object object, int repeat,
768                    Object previous, ObservableCollection ignoredView, int offset) {
769    
770                getRootHandler().postEvent(
771                    modified, type, index, object, repeat,
772                    previous, getObservedCollection(), offset);
773            }
774        }
775        
776        // List View
777        //-----------------------------------------------------------------------
778        /**
779         * Creates a new handler for subLists that is aware of the offset.
780         * 
781         * @param fromIndex  the sublist fromIndex (inclusive)
782         * @param toIndex  the sublist toIndex (exclusive)
783         */
784        protected ModificationHandler createSubListHandler(int fromIndex, int toIndex) {
785            return new SubListHandler(rootHandler, fromIndex + viewOffset);
786        }
787    
788        /**
789         * Inner class for subLists.
790         */    
791        protected static class SubListHandler extends ModificationHandler {
792            
793            /**
794             * Constructor.
795             * 
796             * @param rootHandler  the base underlying handler
797             * @param viewOffset  the offset on the base collection
798             */
799            protected SubListHandler(ModificationHandler rootHandler, int viewOffset) {
800                super(rootHandler, viewOffset);
801            }
802    
803            /**
804             * Override the preEvent method to forward all events to the 
805             * underlying handler. This method also inserts details of the view
806             * that caused the event.
807             */
808            protected boolean preEvent(
809                    int type, int index, Object object, int repeat,
810                    Object previous, ObservableCollection ignoredView, int ignoredOffset) {
811    
812                return getRootHandler().preEvent(
813                    type, index, object, repeat,
814                    previous, getObservedCollection(), getViewOffset());
815            }
816    
817            /**
818             * Override the postEvent method to forward all events to the 
819             * underlying handler. This method also inserts details of the view
820             * that caused the event.
821             */
822            protected void postEvent(
823                    boolean modified, int type, int index, Object object, int repeat,
824                    Object previous, ObservableCollection ignoredView, int ignoredOffset) {
825    
826                getRootHandler().postEvent(
827                    modified, type, index, object, repeat,
828                    previous, getObservedCollection(), getViewOffset());
829            }
830        }
831        
832        // toString
833        //-----------------------------------------------------------------------
834        /**
835         * Gets a debugging string version of this object.
836         * 
837         * @return a debugging string
838         */
839        public String toString() {
840            String name = getClass().getName();
841            int pos = name.lastIndexOf('.');
842            if (pos != -1) {
843                name = name.substring(pos + 1);
844            }
845            return name + '[' + (obsCollection == null ? "" : "initialised") + ']';
846        }
847    
848        // Factory to create handler from handler
849        //-----------------------------------------------------------------------
850        /**
851         * Factory that casts the listener to a handler.
852         */
853        static class Factory implements ModificationHandlerFactory {
854            public ModificationHandler createHandler(Collection coll, Object listener) {
855                if (listener instanceof ModificationHandler) {
856                    return (ModificationHandler) listener;
857                }
858                return null;
859            }
860        }
861    
862    }