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 }