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.beans.PropertyChangeSupport;
019: import java.beans.PropertyChangeListener;
020: import java.beans.PropertyChangeEvent;
021: import java.util.Collection;
022: import java.util.Iterator;
023:
024: import org.apache.commons.collections.collection.AbstractCollectionDecorator;
025: import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
026:
027: /**
028: * <p>
029: * Decorates a <code>Collection</code> implementation with a <b>bound
030: * property</b> named "collection".
031: * </p>
032: * <p>
033: * Each modifying method call made on this <code>Collection</code> is
034: * handled as a change to the "collection" property. This
035: * facility serves to notify subscribers of a change to the collection
036: * but does not allow users the option of vetoing the change. To
037: * gain the ability to veto the change, use a {@link ConstrainedCollection}
038: * decorater.
039: * </p>
040: * <p>
041: * Registered listeners must implement the {@link
042: * java.beans.PropertyChangeListener} interface. Each change request causes a
043: * {@link CollectionChangeEvent} to be fired <i>after</i> the request
044: * has been executed. The {@link CollectionChangeEvent} provides an
045: * indication of the type of change, the element participating in the
046: * change, and whether or not the collection was actually affected by the
047: * change request. As such, receiving a <code>CollectionChangeEvent</code>
048: * is merely an indication that a change was attempted, not that the
049: * Collection is actually different.
050: *
051: * @see java.beans.PropertyChangeListener
052: * @since Commons Events 1.0
053: * @version $Revision: 155443 $ $Date: 2005-02-26 06:19:51 -0700 (Sat, 26 Feb 2005) $
054: *
055: * @author Stephen Colebourne, Bryce Nordgren
056: */
057: public class BoundCollection extends AbstractCollectionDecorator {
058:
059: /**
060: * Child-accessible factory used to construct {@link CollectionChangeEvent}s.
061: * This field is <code>final</code> and is set by the constructor, so
062: * while the children may <i>use</i> it to instantiate events, they
063: * may not <i>change</i> it.
064: */
065: protected final CollectionChangeEventFactory eventFactory;
066: /** Utility to support listener registry and event dispatch. */
067: private final PropertyChangeSupport changeSupport;
068:
069: // Constructors
070: //-----------------------------------------------------------------------
071: /**
072: * Constructor that wraps (not copies) and takes a
073: * {@link CollectionChangeEventFactory}.
074: * <p>
075: * This should be used if the client wants to provide a user-specific
076: * CollectionChangeEventFactory implementation. Note that the
077: * same instance of the factory may not be used with multiple
078: * collection decorators.
079: *
080: * @param coll the collection to decorate, must not be null
081: * @param eventFactory the factory which instantiates
082: * {@link CollectionChangeEvent}s.
083: * @throws IllegalArgumentException if the collection or event factory
084: * is null.
085: * @throws UnsupportedOperationException if the <code>eventFactory</code>
086: * has already been used with another collection decorator.
087: */
088: protected BoundCollection(Collection coll,
089: CollectionChangeEventFactory eventFactory) {
090:
091: // initialize parent
092: super (coll);
093:
094: // Make a default event factory if necessary
095: if (eventFactory == null) {
096: eventFactory = new DefaultCollectionChangeEventFactory();
097: }
098:
099: // install the event factory
100: eventFactory.setCollection(this );
101: this .eventFactory = eventFactory;
102:
103: // initialize property change support.
104: changeSupport = new PropertyChangeSupport(this );
105: }
106:
107: /**
108: * Constructor that wraps (not copies) and uses the
109: * {@link DefaultCollectionChangeEventFactory}.
110: * <p>
111: * This should be used if the default change events are considered
112: * adequate to the task of monitoring changes to the collection.
113: *
114: * @param coll the collection to decorate, must not be null
115: * @throws IllegalArgumentException if the collection is null
116: */
117: protected BoundCollection(Collection coll) {
118: this (coll, null);
119: }
120:
121: // BoundCollection factories
122: //-----------------------------------------------------------------------
123: /**
124: * Factory method to create a bound collection using the default
125: * events.
126: * <p>
127: * A {@link DefaultCollectionChangeEventFactory} will be created.
128: *
129: * @param coll the collection to decorate, must not be null
130: * @return the observed collection
131: * @throws IllegalArgumentException if the collection is null
132: */
133: public static BoundCollection decorate(final Collection coll) {
134: return new BoundCollection(coll);
135: }
136:
137: /**
138: * Factory method to create an observable collection using user-supplied
139: * events.
140: * <p>
141: * To implement user-supplied events, extend {@link CollectionChangeEvent},
142: * implement the {@link CollectionChangeEventFactory} interface. Your
143: * event factory will be called when the collection is changed. Likewise,
144: * your event will be fired to all registered listeners.
145: *
146: * @param coll the collection to decorate, must not be null
147: * @param eventFactory, the factory to create user-defined events.
148: * @return the observed collection
149: * @throws IllegalArgumentException if the collection is null
150: */
151: public static BoundCollection decorate(final Collection coll,
152: final CollectionChangeEventFactory eventFactory) {
153:
154: return new BoundCollection(coll, eventFactory);
155: }
156:
157: // Listener Management
158: //-----------------------------------------------------------------------
159: /**
160: * Registers a listener with this decorator. The Listener must
161: * implement the <code>PropertyChangeListener</code> interface.
162: * Adding a listener more than once will result in more than
163: * one notification for each change event.
164: *
165: * @param l The listener to register with this decorator.
166: */
167: public void addPropertyChangeListener(PropertyChangeListener l) {
168: changeSupport.addPropertyChangeListener(l);
169: }
170:
171: /**
172: * Unregisters a listener from this decorator. The Listener must
173: * implement the <code>PropertyChangeListener</code> interface.
174: * If the listener was registered more than once, calling this method
175: * cancels out a single registration. If the listener is not
176: * registered with this object, no action is taken.
177: *
178: * @param l The listener to register with this decorator.
179: */
180: public void removePropertyChangeListener(PropertyChangeListener l) {
181: changeSupport.removePropertyChangeListener(l);
182: }
183:
184: /**
185: * This is a utility method to allow subclasses to fire property change
186: * events.
187: * @param evt The pre-constructed event.
188: */
189: protected void firePropertyChange(PropertyChangeEvent evt) {
190: changeSupport.firePropertyChange(evt);
191: }
192:
193: /**
194: * Package private method to create an EventRepeater from within the
195: * context of a particular BoundCollection object. This event repeater
196: * will relay events to all property change listeners subscribed to this
197: * bound collection.
198: * @return the event repeater object.
199: */
200: EventRepeater createEventRepeater() {
201: return new EventRepeater(this );
202: }
203:
204: // Decoration of Collection methods.
205: //-----------------------------------------------------------------------
206: public boolean add(Object element) {
207: boolean changed = collection.add(element);
208: CollectionChangeEvent evt = eventFactory.createAdd(element,
209: changed);
210: firePropertyChange(evt);
211: return changed;
212: }
213:
214: public boolean addAll(Collection element) {
215: boolean changed = collection.addAll(element);
216: CollectionChangeEvent evt = eventFactory.createAddAll(element,
217: changed);
218: firePropertyChange(evt);
219: return changed;
220: }
221:
222: public void clear() {
223: boolean changed = !(collection.isEmpty());
224: collection.clear();
225: CollectionChangeEvent evt = eventFactory.createClear(changed);
226: firePropertyChange(evt);
227: }
228:
229: public Iterator iterator() {
230: return new BoundIterator(collection.iterator());
231: }
232:
233: public boolean remove(Object element) {
234: boolean changed = collection.remove(element);
235: CollectionChangeEvent evt = eventFactory.createRemove(element,
236: changed);
237: firePropertyChange(evt);
238: return changed;
239: }
240:
241: public boolean removeAll(Collection element) {
242: boolean changed = collection.removeAll(element);
243: CollectionChangeEvent evt = eventFactory.createRemoveAll(
244: element, changed);
245: firePropertyChange(evt);
246: return changed;
247: }
248:
249: public boolean retainAll(Collection element) {
250: boolean changed = collection.retainAll(element);
251: CollectionChangeEvent evt = eventFactory.createRetainAll(
252: element, changed);
253: firePropertyChange(evt);
254: return changed;
255: }
256:
257: // Utility classes
258: //-----------------------------------------------------------------------
259: /**
260: * This class subscribes to events from another collection and fires them
261: * to all the subscribers of this collection.
262: */
263: private class EventRepeater implements PropertyChangeListener {
264: private Object myself;
265:
266: public EventRepeater(Object me) {
267: myself = me;
268: }
269:
270: /**
271: * Relay events which did not originate with my list. This
272: * prevents an infinite loop.
273: */
274: public void propertyChange(PropertyChangeEvent evt) {
275: if ((evt != null) && (evt.getSource() != myself)) {
276: firePropertyChange(evt);
277: }
278: }
279: }
280:
281: protected class BoundIterator extends AbstractIteratorDecorator {
282:
283: protected int lastIndex = -1;
284: protected Object last;
285:
286: protected BoundIterator(Iterator iterator) {
287: super (iterator);
288: }
289:
290: public Object next() {
291: last = super .next();
292: lastIndex++;
293: return last;
294: }
295:
296: public void remove() {
297: iterator.remove();
298: CollectionChangeEvent evt = eventFactory
299: .createRemoveIterated(lastIndex, last, true);
300: firePropertyChange(evt);
301: lastIndex--;
302: }
303: }
304: }
|