/* * Copyright 2003-2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.events.observable; import java.beans.PropertyChangeSupport ; import java.beans.PropertyChangeListener ; import java.beans.PropertyChangeEvent ; import java.util.Collection; import java.util.Iterator; import org.apache.commons.collections.collection.AbstractCollectionDecorator; import org.apache.commons.collections.iterators.AbstractIteratorDecorator; /** *

* Decorates a Collection implementation with a bound * property named "collection". *

*

* Each modifying method call made on this Collection is * handled as a change to the "collection" property. This * facility serves to notify subscribers of a change to the collection * but does not allow users the option of vetoing the change. To * gain the ability to veto the change, use a {@link ConstrainedCollection} * decorater. *

*

* Registered listeners must implement the {@link * java.beans.PropertyChangeListener} interface. Each change request causes a * {@link CollectionChangeEvent} to be fired after the request * has been executed. The {@link CollectionChangeEvent} provides an * indication of the type of change, the element participating in the * change, and whether or not the collection was actually affected by the * change request. As such, receiving a CollectionChangeEvent * is merely an indication that a change was attempted, not that the * Collection is actually different. * * @see java.beans.PropertyChangeListener * @since Commons Events 1.0 * @version $Revision: 155443 $ $Date: 2005-02-26 06:19:51 -0700 (Sat, 26 Feb 2005) $ * * @author Stephen Colebourne, Bryce Nordgren */ public class BoundCollection extends AbstractCollectionDecorator { /** * Child-accessible factory used to construct {@link CollectionChangeEvent}s. * This field is final and is set by the constructor, so * while the children may use it to instantiate events, they * may not change it. */ protected final CollectionChangeEventFactory eventFactory ; /** Utility to support listener registry and event dispatch. */ private final PropertyChangeSupport changeSupport ; // Constructors //----------------------------------------------------------------------- /** * Constructor that wraps (not copies) and takes a * {@link CollectionChangeEventFactory}. *

* This should be used if the client wants to provide a user-specific * CollectionChangeEventFactory implementation. Note that the * same instance of the factory may not be used with multiple * collection decorators. * * @param coll the collection to decorate, must not be null * @param eventFactory the factory which instantiates * {@link CollectionChangeEvent}s. * @throws IllegalArgumentException if the collection or event factory * is null. * @throws UnsupportedOperationException if the eventFactory * has already been used with another collection decorator. */ protected BoundCollection(Collection coll, CollectionChangeEventFactory eventFactory) { // initialize parent super(coll) ; // Make a default event factory if necessary if (eventFactory == null) { eventFactory = new DefaultCollectionChangeEventFactory() ; } // install the event factory eventFactory.setCollection(this) ; this.eventFactory = eventFactory ; // initialize property change support. changeSupport = new PropertyChangeSupport(this) ; } /** * Constructor that wraps (not copies) and uses the * {@link DefaultCollectionChangeEventFactory}. *

* This should be used if the default change events are considered * adequate to the task of monitoring changes to the collection. * * @param coll the collection to decorate, must not be null * @throws IllegalArgumentException if the collection is null */ protected BoundCollection(Collection coll) { this(coll, null) ; } // BoundCollection factories //----------------------------------------------------------------------- /** * Factory method to create a bound collection using the default * events. *

* A {@link DefaultCollectionChangeEventFactory} will be created. * * @param coll the collection to decorate, must not be null * @return the observed collection * @throws IllegalArgumentException if the collection is null */ public static BoundCollection decorate(final Collection coll) { return new BoundCollection(coll); } /** * Factory method to create an observable collection using user-supplied * events. *

* To implement user-supplied events, extend {@link CollectionChangeEvent}, * implement the {@link CollectionChangeEventFactory} interface. Your * event factory will be called when the collection is changed. Likewise, * your event will be fired to all registered listeners. * * @param coll the collection to decorate, must not be null * @param eventFactory, the factory to create user-defined events. * @return the observed collection * @throws IllegalArgumentException if the collection is null */ public static BoundCollection decorate( final Collection coll, final CollectionChangeEventFactory eventFactory) { return new BoundCollection(coll, eventFactory); } // Listener Management //----------------------------------------------------------------------- /** * Registers a listener with this decorator. The Listener must * implement the PropertyChangeListener interface. * Adding a listener more than once will result in more than * one notification for each change event. * * @param l The listener to register with this decorator. */ public void addPropertyChangeListener(PropertyChangeListener l) { changeSupport.addPropertyChangeListener(l) ; } /** * Unregisters a listener from this decorator. The Listener must * implement the PropertyChangeListener interface. * If the listener was registered more than once, calling this method * cancels out a single registration. If the listener is not * registered with this object, no action is taken. * * @param l The listener to register with this decorator. */ public void removePropertyChangeListener(PropertyChangeListener l) { changeSupport.removePropertyChangeListener(l) ; } /** * This is a utility method to allow subclasses to fire property change * events. * @param evt The pre-constructed event. */ protected void firePropertyChange(PropertyChangeEvent evt) { changeSupport.firePropertyChange(evt) ; } /** * Package private method to create an EventRepeater from within the * context of a particular BoundCollection object. This event repeater * will relay events to all property change listeners subscribed to this * bound collection. * @return the event repeater object. */ EventRepeater createEventRepeater() { return new EventRepeater(this) ; } // Decoration of Collection methods. //----------------------------------------------------------------------- public boolean add(Object element) { boolean changed = collection.add(element) ; CollectionChangeEvent evt = eventFactory.createAdd(element, changed); firePropertyChange(evt) ; return changed ; } public boolean addAll(Collection element) { boolean changed = collection.addAll(element) ; CollectionChangeEvent evt = eventFactory.createAddAll(element, changed); firePropertyChange(evt) ; return changed ; } public void clear() { boolean changed = !(collection.isEmpty()) ; collection.clear() ; CollectionChangeEvent evt = eventFactory.createClear(changed); firePropertyChange(evt) ; } public Iterator iterator() { return new BoundIterator(collection.iterator()) ; } public boolean remove(Object element) { boolean changed = collection.remove(element) ; CollectionChangeEvent evt = eventFactory.createRemove(element, changed); firePropertyChange(evt) ; return changed ; } public boolean removeAll(Collection element) { boolean changed = collection.removeAll(element) ; CollectionChangeEvent evt = eventFactory.createRemoveAll( element, changed); firePropertyChange(evt) ; return changed ; } public boolean retainAll(Collection element) { boolean changed = collection.retainAll(element) ; CollectionChangeEvent evt = eventFactory.createRetainAll( element, changed); firePropertyChange(evt) ; return changed ; } // Utility classes //----------------------------------------------------------------------- /** * This class subscribes to events from another collection and fires them * to all the subscribers of this collection. */ private class EventRepeater implements PropertyChangeListener { private Object myself ; public EventRepeater(Object me) { myself = me ; } /** * Relay events which did not originate with my list. This * prevents an infinite loop. */ public void propertyChange(PropertyChangeEvent evt) { if ((evt != null) && (evt.getSource() != myself)) { firePropertyChange(evt) ; } } } protected class BoundIterator extends AbstractIteratorDecorator { protected int lastIndex = -1; protected Object last; protected BoundIterator(Iterator iterator) { super(iterator); } public Object next() { last = super.next(); lastIndex++; return last; } public void remove() { iterator.remove() ; CollectionChangeEvent evt = eventFactory.createRemoveIterated( lastIndex, last, true) ; firePropertyChange(evt) ; lastIndex--; } } }