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 import java.util.Iterator;
020
021 import org.apache.commons.collections.collection.AbstractCollectionDecorator;
022 import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
023 import org.apache.commons.events.observable.standard.StandardModificationHandler;
024
025 /**
026 * Decorates a <code>Collection</code> implementation to observe modifications.
027 * <p>
028 * Each modifying method call made on this <code>Collection</code> is forwarded to a
029 * {@link ModificationHandler}.
030 * The handler manages the event, notifying listeners and optionally vetoing changes.
031 * The default handler is {@link StandardModificationHandler}.
032 * See this class for details of configuration available.
033 *
034 * @since Commons Events 1.0
035 * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $
036 *
037 * @author Stephen Colebourne
038 */
039 public class ObservableCollection extends AbstractCollectionDecorator {
040
041 /** The list of registered factories, checked in reverse order */
042 private static ModificationHandlerFactory[] factories = new ModificationHandlerFactory[] {
043 ModificationHandler.FACTORY,
044 StandardModificationHandler.FACTORY
045 };
046
047 /** The handler to delegate event handling to */
048 protected final ModificationHandler handler;
049
050 // ObservableCollection factories
051 //-----------------------------------------------------------------------
052 /**
053 * Factory method to create an observable collection.
054 * <p>
055 * A {@link StandardModificationHandler} will be created.
056 * This can be accessed by {@link #getHandler()} to add listeners.
057 *
058 * @param coll the collection to decorate, must not be null
059 * @return the observed collection
060 * @throws IllegalArgumentException if the collection is null
061 */
062 public static ObservableCollection decorate(final Collection coll) {
063 return new ObservableCollection(coll, null);
064 }
065
066 /**
067 * Factory method to create an observable collection using a listener or a handler.
068 * <p>
069 * A lot of functionality is available through this method.
070 * If you don't need the extra functionality, simply implement the
071 * {@link org.apache.commons.events.observable.standard.StandardModificationListener}
072 * interface and pass it in as the second parameter.
073 * <p>
074 * Internally, an <code>ObservableCollection</code> relies on a {@link ModificationHandler}.
075 * The handler receives all the events and processes them, typically by
076 * calling listeners. Different handler implementations can be plugged in
077 * to provide a flexible event system.
078 * <p>
079 * The handler implementation is determined by the listener parameter via
080 * the registered factories. The listener may be a manually configured
081 * <code>ModificationHandler</code> instance.
082 * <p>
083 * The listener is defined as an Object for maximum flexibility.
084 * It does not have to be a listener in the classic JavaBean sense.
085 * It is entirely up to the factory and handler as to how the parameter
086 * is interpretted. An IllegalArgumentException is thrown if no suitable
087 * handler can be found for this listener.
088 * <p>
089 * A <code>null</code> listener will create a {@link StandardModificationHandler}.
090 *
091 * @param coll the collection to decorate, must not be null
092 * @param listener collection listener, may be null
093 * @return the observed collection
094 * @throws IllegalArgumentException if the collection is null
095 * @throws IllegalArgumentException if there is no valid handler for the listener
096 */
097 public static ObservableCollection decorate(
098 final Collection coll,
099 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 }