View Javadoc

1   /*
2    * Copyright 2003-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.events.observable;
17  
18  import java.util.Collection;
19  import java.util.Iterator;
20  
21  import org.apache.commons.collections.collection.AbstractCollectionDecorator;
22  import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
23  import org.apache.commons.events.observable.standard.StandardModificationHandler;
24  
25  /**
26   * Decorates a <code>Collection</code> implementation to observe modifications.
27   * <p>
28   * Each modifying method call made on this <code>Collection</code> is forwarded to a
29   * {@link ModificationHandler}.
30   * The handler manages the event, notifying listeners and optionally vetoing changes.
31   * The default handler is {@link StandardModificationHandler}.
32   * See this class for details of configuration available.
33   *
34   * @since Commons Events 1.0
35   * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $
36   * 
37   * @author Stephen Colebourne
38   */
39  public class ObservableCollection extends AbstractCollectionDecorator {
40      
41      /** The list of registered factories, checked in reverse order */
42      private static ModificationHandlerFactory[] factories = new ModificationHandlerFactory[] {
43          ModificationHandler.FACTORY,
44          StandardModificationHandler.FACTORY
45      };
46      
47      /** The handler to delegate event handling to */
48      protected final ModificationHandler handler;
49  
50      // ObservableCollection factories
51      //-----------------------------------------------------------------------
52      /**
53       * Factory method to create an observable collection.
54       * <p>
55       * A {@link StandardModificationHandler} will be created.
56       * This can be accessed by {@link #getHandler()} to add listeners.
57       *
58       * @param coll  the collection to decorate, must not be null
59       * @return the observed collection
60       * @throws IllegalArgumentException if the collection is null
61       */
62      public static ObservableCollection decorate(final Collection coll) {
63          return new ObservableCollection(coll, null);
64      }
65  
66      /**
67       * Factory method to create an observable collection using a listener or a handler.
68       * <p>
69       * A lot of functionality is available through this method.
70       * If you don't need the extra functionality, simply implement the
71       * {@link org.apache.commons.events.observable.standard.StandardModificationListener}
72       * interface and pass it in as the second parameter.
73       * <p>
74       * Internally, an <code>ObservableCollection</code> relies on a {@link ModificationHandler}.
75       * The handler receives all the events and processes them, typically by
76       * calling listeners. Different handler implementations can be plugged in
77       * to provide a flexible event system.
78       * <p>
79       * The handler implementation is determined by the listener parameter via
80       * the registered factories. The listener may be a manually configured 
81       * <code>ModificationHandler</code> instance.
82       * <p>
83       * The listener is defined as an Object for maximum flexibility.
84       * It does not have to be a listener in the classic JavaBean sense.
85       * It is entirely up to the factory and handler as to how the parameter
86       * is interpretted. An IllegalArgumentException is thrown if no suitable
87       * handler can be found for this listener.
88       * <p>
89       * A <code>null</code> listener will create a {@link StandardModificationHandler}.
90       *
91       * @param coll  the collection to decorate, must not be null
92       * @param listener  collection listener, may be null
93       * @return the observed collection
94       * @throws IllegalArgumentException if the collection is null
95       * @throws IllegalArgumentException if there is no valid handler for the listener
96       */
97      public static ObservableCollection decorate(
98              final Collection coll,
99              final Object listener) {
100         
101         if (coll == null) {
102             throw new IllegalArgumentException("Collection must not be null");
103         }
104         return new ObservableCollection(coll, listener);
105     }
106 
107     // Register for ModificationHandlerFactory
108     //-----------------------------------------------------------------------
109     /**
110      * Registers a handler factory to be used for looking up a listener to
111      * a handler.
112      * <p>
113      * This method is used to add your own event handler to the supplied ones.
114      * Registering the factory will enable the {@link #decorate(Collection, Object)}
115      * method to create your handler.
116      * <p>
117      * Each handler added becomes the first in the lookup chain. Thus it is
118      * possible to override the default setup.
119      * Obviously this should be done with care in a shared web environment!
120      * <p>
121      * This method is not guaranteed to be threadsafe.
122      * It should only be called during initialization.
123      * Problems will occur if two threads call this method at the same time.
124      * 
125      * @param factory  the factory to add, may be null
126      */
127     public static void registerFactory(final ModificationHandlerFactory factory) {
128         if (factory != null) {
129             // add at end, as checked in reverse order
130             ModificationHandlerFactory[] array = new ModificationHandlerFactory[factories.length + 1];
131             System.arraycopy(factories, 0, array, 0, factories.length);
132             array[factories.length] = factory;
133             factories = array;  // atomic operation
134         }
135     }
136 
137     // Constructors
138     //-----------------------------------------------------------------------
139     /**
140      * Constructor that wraps (not copies) and takes a handler.
141      * <p>
142      * The handler implementation is determined by the listener parameter via
143      * the registered factories. The listener may be a manually configured 
144      * <code>ModificationHandler</code> instance.
145      * 
146      * @param coll  the collection to decorate, must not be null
147      * @param listener  the observing handler, may be null
148      * @throws IllegalArgumentException if the collection is null
149      */
150     protected ObservableCollection(
151             final Collection coll,
152             final Object listener) {
153         super(coll);
154         this.handler = createHandler(coll, listener);
155         this.handler.init(this, coll);
156     }
157 
158     /**
159      * Constructor used by subclass views, such as subList.
160      * 
161      * @param handler  the observing handler, may be null
162      * @param coll  the collection to decorate, must not be null
163      * @throws IllegalArgumentException if the collection is null
164      */
165     protected ObservableCollection(
166             final ModificationHandler handler,
167             final Collection coll) {
168         super(coll);
169         this.handler = handler;
170     }
171 
172     /**
173      * Creates a handler subclass based on the specified listener.
174      * <p>
175      * The method is defined in terms of an Object to allow for unusual
176      * listeners, such as a Swing model object.
177      * 
178      * @param listener  a listener object to create a handler for
179      * @return an instantiated handler with the listener attached
180      * @throws IllegalArgumentException if no suitable handler
181      */
182     protected ModificationHandler createHandler(final Collection coll, final Object listener) {
183         if (listener == null) {
184             return new StandardModificationHandler();
185         }
186         ModificationHandlerFactory[] array = factories;  // atomic operation
187         for (int i = array.length - 1; i >= 0 ; i--) {
188             ModificationHandler handler = array[i].createHandler(coll, listener);
189             if (handler != null) {
190                 return handler;
191             }
192         }
193         throw new IllegalArgumentException("Unrecognised listener type: " +
194             (listener == null ? "null" : listener.getClass().getName()));
195     }
196 
197     // Handler access
198     //-----------------------------------------------------------------------
199     /**
200      * Gets the handler that is observing this collection.
201      * 
202      * @return the observing handler, never null
203      */
204     public ModificationHandler getHandler() {
205         return handler;
206     }
207     
208     // Collection
209     //-----------------------------------------------------------------------
210     public boolean add(Object object) {
211         boolean result = false;
212         if (handler.preAdd(object)) {
213             result = collection.add(object);
214             handler.postAdd(object, result);
215         }
216         return result;
217     }
218 
219     public boolean addAll(Collection coll) {
220         boolean result = false;
221         if (handler.preAddAll(coll)) {
222             result = collection.addAll(coll);
223             handler.postAddAll(coll, result);
224         }
225         return result;
226     }
227 
228     public void clear() {
229         if (handler.preClear()) {
230             collection.clear();
231             handler.postClear();
232         }
233     }
234 
235     public Iterator iterator() {
236         return new ObservableIterator(collection.iterator());
237     }
238 
239     public boolean remove(Object object) {
240         boolean result = false;
241         if (handler.preRemove(object)) {
242             result = collection.remove(object);
243             handler.postRemove(object, result);
244         }
245         return result;
246     }
247 
248     public boolean removeAll(Collection coll) {
249         boolean result = false;
250         if (handler.preRemoveAll(coll)) {
251             result = collection.removeAll(coll);
252             handler.postRemoveAll(coll, result);
253         }
254         return result;
255     }
256 
257     public boolean retainAll(Collection coll) {
258         boolean result = false;
259         if (handler.preRetainAll(coll)) {
260             result = collection.retainAll(coll);
261             handler.postRetainAll(coll, result);
262         }
263         return result;
264     }
265 
266     // Iterator
267     //-----------------------------------------------------------------------
268     /**
269      * Inner class Iterator for the ObservableCollection.
270      */
271     protected class ObservableIterator extends AbstractIteratorDecorator {
272         
273         protected int lastIndex = -1;
274         protected Object last;
275         
276         protected ObservableIterator(Iterator iterator) {
277             super(iterator);
278         }
279         
280         public Object next() {
281             last = super.next();
282             lastIndex++;
283             return last;
284         }
285 
286         public void remove() {
287             if (handler.preRemoveIterated(lastIndex, last)) {
288                 iterator.remove();
289                 handler.postRemoveIterated(lastIndex, last);
290                 lastIndex--;
291             }
292         }
293     }
294 
295 }