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    }