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.standard;
017    
018    import java.util.Collection;
019    
020    import org.apache.commons.events.observable.ModificationEventType;
021    import org.apache.commons.events.observable.ModificationHandler;
022    import org.apache.commons.events.observable.ModificationHandlerFactory;
023    import org.apache.commons.events.observable.ObservableCollection;
024    
025    /**
026     * The standard implementation of a <code>ModificationHandler</code> that
027     * sends standard JavaBean style events to listeners.
028     * <p>
029     * The information gathered by this implementation is all that is available
030     * as parameters or return values.
031     * In addition, the <code>size</code> method is used on the collection.
032     * All objects used are the real objects from the method calls, not clones.
033     * <p>
034     * Each listener can be filtered. There are separate filters for pre and post
035     * modification events.
036     *
037     * @since Commons Events 1.0
038     * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $
039     * 
040     * @author Stephen Colebourne
041     */
042    public class StandardModificationHandler extends ModificationHandler {
043    
044        /** The singleton factory */    
045        public static final ModificationHandlerFactory FACTORY = new Factory();
046    
047        /** A reusable empty holders array. */    
048        protected static final PreHolder[] EMPTY_PRE_HOLDERS = new PreHolder[0];
049        /** A reusable empty holders array. */    
050        protected static final PostHolder[] EMPTY_POST_HOLDERS = new PostHolder[0];
051        
052        /** The event mask as to which event types to send on pre events. */
053        protected int preMask = ModificationEventType.GROUP_NONE;
054        /** The event mask as to which event types to send on post events. */
055        protected int postMask = ModificationEventType.GROUP_NONE;
056        
057        /** The event listeners. */
058        protected PreHolder[] preHolder = EMPTY_PRE_HOLDERS;
059        /** The event listeners. */
060        protected PostHolder[] postHolder = EMPTY_POST_HOLDERS;
061        /**
062         * Temporary store for the size.
063         * This makes the class thread-unsafe, but you should sync collections anyway.
064         */
065        protected int preSize;
066        
067        // Constructors
068        //-----------------------------------------------------------------------
069        /**
070         * Constructor the creates the handler but leaves it invalid.
071         * <p>
072         * The handler can only be used after it has been properly initialized.
073         * This is normally done automatically by
074         * {@link ObservableCollection#decorate(Collection, Object)}.
075         */
076        public StandardModificationHandler() {
077            super();
078        }
079    
080        /**
081         * Constructor the creates the handler but leaves it invalid.
082         * <p>
083         * The handler can only be used after it has been properly initialized.
084         * This is normally done automatically by
085         * {@link ObservableCollection#decorate(Collection, Object)}.
086         * 
087         * @param pre  the pre listener
088         * @param preMask  the mask for the pre listener
089         * @param post  the post listener
090         * @param postMask  the mask for the post listener
091         */
092        public StandardModificationHandler(
093                StandardPreModificationListener pre, int preMask,
094                StandardPostModificationListener post, int postMask) {
095            super();
096            if (pre != null) {
097                preHolder = new PreHolder[1];
098                preHolder[0] = new PreHolder(pre, preMask);
099                this.preMask = preMask;
100            }
101            if (post != null) {
102                postHolder = new PostHolder[1];
103                postHolder[0] = new PostHolder(post, postMask);
104                this.postMask = postMask;
105            }
106        }
107    
108        // Pre Listeners
109        //----------------------------------------------------------------------
110        /**
111         * Gets an array of all the pre listeners active in the handler.
112         * <p>
113         * All listeners will be instances of StandardPreModificationListener.
114         * 
115         * @return the listeners
116         */
117        public synchronized Object[] getPreModificationListeners() {
118            Object[] lnrs = new Object[preHolder.length];
119            for (int i = 0; i < preHolder.length; i++) {
120                lnrs[i] = preHolder[i].listener;
121            }
122            return lnrs;
123        }
124        
125        /**
126         * Adds a listener to the handler for pre modification events.
127         * <p>
128         * No error occurs if the listener is <code>null</code>.
129         * 
130         * @param listener  the listener to add, may be null (ignored)
131         * @throws ClassCastException if the listener is not a StandardPreModificationListener
132         */
133        public void addPreModificationListener(Object listener) {
134            addPreModificationListener((StandardPreModificationListener) listener, -1);
135        }
136        
137        /**
138         * Adds a pre listener to the list held in the handler.
139         * <p>
140         * No error occurs if the listener is <code>null</code>.
141         * 
142         * @param listener  the listener to add, may be null (ignored)
143         * @param mask  the mask for events (0 for none, -1 for all)
144         */
145        public synchronized void addPreModificationListener(StandardPreModificationListener listener, int mask) {
146            if (listener != null) {
147                int oldSize = preHolder.length;
148                PreHolder[] array = new PreHolder[oldSize + 1];
149                System.arraycopy(preHolder, 0, array, 0, oldSize);
150                array[oldSize] = new PreHolder(listener, mask);
151                preHolder = array;
152                calculatePreMask();
153            }
154        }
155        
156        /**
157         * Removes a pre listener to the list held in the handler.
158         * <p>
159         * No error occurs if the listener is not in the list or the type
160         * of the listener is incorrect.
161         * The listener is matched using ==.
162         * 
163         * @param listener  the listener to remove, may be null (ignored)
164         */
165        public synchronized void removePreModificationListener(Object listener) {
166            if (listener != null) {
167                switch (preHolder.length) {
168                    case 0:
169                    return;
170                    
171                    case 1:
172                    if (preHolder[0].listener == listener) {
173                        preHolder = EMPTY_PRE_HOLDERS;
174                        calculatePreMask();
175                    }
176                    return;
177                    
178                    default:
179                    PreHolder[] array = new PreHolder[preHolder.length - 1];
180                    boolean match = false;
181                    for (int i = 0; i < preHolder.length; i++) {
182                        if (match) {
183                            array[i - 1] = preHolder[i];
184                        } else if (preHolder[i].listener == listener) {
185                            match = true;
186                        } else {
187                            array[i] = preHolder[i];
188                        }
189                    }
190                    preHolder = array;
191                    calculatePreMask();
192                    return;
193                }
194            }
195        }
196        
197        /**
198         * Sets the masks of a listener.
199         * <p>
200         * No error occurs if the listener is not in the list.
201         * The listener is matched using ==.
202         * 
203         * @param listener  the listener to change, may be null
204         * @param mask  the new mask (0 for none, -1 for all)
205         */
206        public synchronized void setPreModificationListenerMask(StandardPreModificationListener listener, int mask) {
207            if (listener != null) {
208                for (int i = 0; i < preHolder.length; i++) {
209                    if (preHolder[i].listener == listener) {
210                        preHolder[i].mask = mask;
211                        calculatePreMask();
212                        break;
213                    }
214                }
215            }
216        }
217        
218        /**
219         * Calculate the combined masks.
220         */
221        protected void calculatePreMask() {
222            preMask = ModificationEventType.GROUP_NONE;
223            for (int i = 0; i < preHolder.length; i++) {
224                preMask |= preHolder[i].mask;
225            }
226        }
227        
228        protected static class PreHolder {
229            final StandardPreModificationListener listener;
230            int mask;
231            
232            PreHolder(StandardPreModificationListener listener, int mask) {
233                this.listener = listener;
234                this.mask = mask;
235            }
236            
237            public String toString() {
238                return "[" + listener + "," + ModificationEventType.toString(mask) + "]";
239            }
240    
241        }
242        
243        // Post Listeners
244        //----------------------------------------------------------------------
245        /**
246         * Gets an array of all the post listeners active in the handler.
247         * <p>
248         * All listeners will be instances of StandardModificationListener.
249         * 
250         * @return the listeners
251         */
252        public synchronized Object[] getPostModificationListeners() {
253            Object[] lnrs = new Object[postHolder.length];
254            for (int i = 0; i < postHolder.length; i++) {
255                lnrs[i] = postHolder[i].listener;
256            }
257            return lnrs;
258        }
259        
260        /**
261         * Adds a listener to the handler for post modification events.
262         * <p>
263         * No error occurs if the listener is <code>null</code>.
264         * 
265         * @param listener  the listener to add, may be null (ignored)
266         * @throws ClassCastException if the listener is not a StandardPreModificationListener
267         */
268        public void addPostModificationListener(Object listener) {
269            addPostModificationListener((StandardPostModificationListener) listener, -1);
270        }
271        
272        /**
273         * Adds a post listener to the list held in the handler.
274         * <p>
275         * No error occurs if the listener is <code>null</code>.
276         * 
277         * @param listener  the listener to add, may be null (ignored)
278         * @param mask  the mask for events (0 for none, -1 for all)
279         */
280        public synchronized void addPostModificationListener(StandardPostModificationListener listener, int mask) {
281            if (listener != null) {
282                int oldSize = postHolder.length;
283                PostHolder[] array = new PostHolder[oldSize + 1];
284                System.arraycopy(postHolder, 0, array, 0, oldSize);
285                array[oldSize] = new PostHolder(listener, mask);
286                postHolder = array;
287                calculatePostMask();
288            }
289        }
290        
291        /**
292         * Removes a post listener to the list held in the handler.
293         * <p>
294         * No error occurs if the listener is not in the list or the type
295         * of the listener is incorrect.
296         * The listener is matched using ==.
297         * 
298         * @param listener  the listener to remove, may be null (ignored)
299         */
300        public synchronized void removePostModificationListener(Object listener) {
301            if (listener != null) {
302                switch (postHolder.length) {
303                    case 0:
304                    return;
305                    
306                    case 1:
307                    if (postHolder[0].listener == listener) {
308                        postHolder = EMPTY_POST_HOLDERS;
309                        calculatePostMask();
310                    }
311                    return;
312                    
313                    default:
314                    PostHolder[] array = new PostHolder[postHolder.length - 1];
315                    boolean match = false;
316                    for (int i = 0; i < postHolder.length; i++) {
317                        if (match) {
318                            array[i - 1] = postHolder[i];
319                        } else if (postHolder[i].listener == listener) {
320                            match = true;
321                        } else {
322                            array[i] = postHolder[i];
323                        }
324                    }
325                    postHolder = array;
326                    calculatePostMask();
327                    return;
328                }
329            }
330        }
331        
332        /**
333         * Sets the masks of a listener.
334         * <p>
335         * No error occurs if the listener is not in the list.
336         * The listener is matched using ==.
337         * 
338         * @param listener  the listener to change, may be null
339         * @param mask  the new mask (0 for none, -1 for all)
340         */
341        public synchronized void setPostModificationListenerMask(StandardPostModificationListener listener, int mask) {
342            if (listener != null) {
343                for (int i = 0; i < postHolder.length; i++) {
344                    if (postHolder[i].listener == listener) {
345                        postHolder[i].mask = mask;
346                        calculatePostMask();
347                        break;
348                    }
349                }
350            }
351        }
352        
353        /**
354         * Calculate the combined masks.
355         */
356        protected void calculatePostMask() {
357            postMask = ModificationEventType.GROUP_NONE;
358            for (int i = 0; i < postHolder.length; i++) {
359                postMask |= postHolder[i].mask;
360            }
361        }
362    
363        protected static class PostHolder {
364            final StandardPostModificationListener listener;
365            int mask;
366            
367            PostHolder(StandardPostModificationListener listener, int mask) {
368                this.listener = listener;
369                this.mask = mask;
370            }
371            
372            public String toString() {
373                return "[" + listener + "," + ModificationEventType.toString(mask) + "]";
374            }
375    
376        }
377        
378        // Pre event sending
379        //-----------------------------------------------------------------------
380        /**
381         * Handles the pre event.
382         * 
383         * @param type  the event type to send
384         * @param index  the index where the change starts, the method param or derived
385         * @param object  the object that will be added/removed/set, the method param or derived
386         * @param repeat  the number of repeats of the add/remove, the method param or derived
387         * @param previous  the previous value that will be removed/replaced, must exist in coll
388         * @param view  the view collection that the change was actioned on, null if no view
389         * @param viewOffset  the offset of the subList view, -1 if unknown
390         * @return true to call the decorated collection
391         */
392        protected boolean preEvent(
393                int type, int index, Object object,
394                int repeat, Object previous, ObservableCollection view, int viewOffset) {
395    
396            preSize = getObservedCollection().size();
397            return firePreEvent(type, index, object, repeat, previous, view, viewOffset);
398        }
399    
400        /**
401         * Sends the pre event to the listeners.
402         * 
403         * @param type  the event type to send
404         * @param index  the index where the change starts, the method param or derived
405         * @param object  the object that will be added/removed/set, the method param or derived
406         * @param repeat  the number of repeats of the add/remove, the method param or derived
407         * @param previous  the previous value that will be removed/replaced, must exist in coll
408         * @param view  the view collection that the change was actioned on, null if no view
409         * @param viewOffset  the offset of the subList view, -1 if unknown
410         * @return true to call the decorated collection
411         */
412        protected boolean firePreEvent(
413                int type, int index, Object object, int repeat,
414                Object previous, ObservableCollection view, int viewOffset) {
415    
416            if ((preMask & type) > 0) {
417                StandardPreModificationEvent event = null;
418                synchronized (this) {
419                    for (int i = 0; i < preHolder.length; i++) {
420                        PreHolder holder = preHolder[i];
421                        if ((holder.mask & type) > 0) {
422                            if (event == null) {
423                                event = new StandardPreModificationEvent(
424                                    getObservedCollection(), this, type, preSize, index, object,
425                                    repeat, previous, view, viewOffset);
426                            }
427                            holder.listener.modificationOccurring(event);
428                        }
429                    }
430                }
431            }
432            return true;
433        }
434    
435        // Post event sending
436        //-----------------------------------------------------------------------
437        /**
438         * Handles the post event.
439         * 
440         * @param modified  true if the method succeeded in changing the collection
441         * @param type  the event type to send
442         * @param index  the index where the change starts, the method param or derived
443         * @param object  the object that was added/removed/set, the method param or derived
444         * @param repeat  the number of repeats of the add/remove, the method param or derived
445         * @param previous  the previous value that was removed/replace, must have existed in coll
446         * @param view  the view collection that the change was actioned on, null if no view
447         * @param viewOffset  the offset of the subList view, -1 if unknown
448         */
449        protected void postEvent(
450                boolean modified, int type, int index, Object object,
451                int repeat, Object previous, ObservableCollection view, int viewOffset) {
452    
453            if (modified) {
454                firePostEvent(type, index, object, repeat, previous, view, viewOffset);
455            }
456        }
457        
458        /**
459         * Sends the post event to the listeners.
460         * 
461         * @param type  the event type to send
462         * @param index  the index where the change starts, the method param or derived
463         * @param object  the object that was added/removed/set, the method param or derived
464         * @param repeat  the number of repeats of the add/remove, the method param or derived
465         * @param previous  the previous value that was removed/replace, must have existed in coll
466         * @param view  the view collection that the change was actioned on, null if no view
467         * @param viewOffset  the offset of the subList view, -1 if unknown
468         */
469        protected void firePostEvent(
470                int type, int index, Object object, int repeat,
471                Object previous, ObservableCollection view, int viewOffset) {
472    
473            if ((postMask & type) > 0) {
474                StandardPostModificationEvent event = null;
475                synchronized (this) {
476                    for (int i = 0; i < postHolder.length; i++) {
477                        PostHolder holder = postHolder[i];
478                        if ((holder.mask & type) > 0) {
479                            if (event == null) {
480                                event = new StandardPostModificationEvent(
481                                    getObservedCollection(), this, type, preSize, index,
482                                    object, repeat, previous, view, viewOffset);
483                            }
484                            holder.listener.modificationOccurred(event);
485                        }
486                    }
487                }
488            }
489        }
490    
491        // Event handling
492        //-----------------------------------------------------------------------
493        /**
494         * Send an event after clear() is called.
495         * <p>
496         * Override to only send event if something actually cleared.
497         */
498        protected void postClear() {
499            postEvent(preSize > 0, ModificationEventType.CLEAR, -1, null, 1, null, null, -1);
500        }
501    
502        // Factory
503        //-----------------------------------------------------------------------
504        /**
505         * Factory implementation for the StandardModificationHandler.
506         * 
507         * @author Stephen Colebourne
508         */
509        static class Factory implements ModificationHandlerFactory {
510            
511            /**
512             * Creates a StandardModificationHandler using the listener.
513             * 
514             * @param coll  the collection being decorated
515             * @param listener  a listener object to create a handler for
516             * @return an instantiated handler with the listener attached,
517             *  or null if the listener type is unsuited to this factory
518             */
519            public ModificationHandler createHandler(Collection coll, Object listener) {
520                if (listener instanceof StandardPreModificationListener) {
521                    if (listener instanceof StandardPostModificationListener) {
522                        return new StandardModificationHandler(
523                            (StandardPreModificationListener) listener, -1,
524                            (StandardPostModificationListener) listener, -1);
525                    } else {
526                        return new StandardModificationHandler(
527                            (StandardPreModificationListener) listener, -1, null, 0);
528                    }
529                }
530                if (listener instanceof StandardPostModificationListener) {
531                    return new StandardModificationHandler(
532                        null, 0, (StandardPostModificationListener) listener, -1);
533                }
534                return null;
535            }
536        }
537        
538    }