001: package org.apache.commons.events.observable;
002:
003: import java.beans.PropertyChangeEvent;
004: import java.beans.PropertyChangeListener;
005:
006: import java.util.List;
007: import java.util.ListIterator;
008: import java.util.Collection;
009:
010: import org.apache.commons.collections.iterators.AbstractListIteratorDecorator;
011:
012: /**
013: * Decorates a List interface with a <b>bound property</b> called
014: * "collection".
015: * <p>
016: * Each modifying method call made on this <code>List</code> is
017: * handled as a change to the "collection" property. This
018: * facility serves to notify subscribers of a change to the collection
019: * but does not allow users the option of vetoing the change. To
020: * gain the ability to veto the change, use a {@link ConstrainedList}
021: * decorater.
022: * </p>
023: * <p>
024: * Registered listeners must implement the {@link
025: * java.beans.PropertyChangeListener} interface. Each change request causes a
026: * {@link CollectionChangeEvent} to be fired <i>after</i> the request
027: * has been executed. The {@link CollectionChangeEvent} provides an
028: * indication of the type of change, the element participating in the
029: * change, and whether or not the <code>List</code> was actually affected by the
030: * change request. As such, receiving a <code>CollectionChangeEvent</code>
031: * is merely an indication that a change was attempted, not that the
032: * List is actually different.
033: * </p>
034: * @see BoundCollection
035: * @see CollectionChangeEvent
036: * @since Commons Events 1.0
037: * @version $Revision$ $Date$
038: * @author Stephen Colebourne, Bryce Nordgren
039: */
040: public class BoundList extends BoundCollection implements List {
041: // Constructors
042: //-----------------------------------------------------------------------
043: /**
044: * Constructor that wraps (not copies.)
045: * @param source the List to decorate, must not be null
046: * @param eventFactory the factory which instantiates
047: * {@link CollectionChangeEvent}s.
048: * @throws IllegalArgumentException if the collection is null
049: * @throws UnsupportedOperationException if the <code>eventFactory</code>
050: * has already been used with another collection decorator.
051: */
052: protected BoundList(final List source,
053: final CollectionChangeEventFactory eventFactory) {
054: super (source, eventFactory);
055: }
056:
057: /**
058: * Constructor that wraps (not copies.)
059: * @param source the List to decorate, must not be null
060: * @throws IllegalArgumentException if the collection is null
061: */
062: protected BoundList(final List source) {
063: super (source);
064: }
065:
066: // Factory methods
067: //-----------------------------------------------------------------------
068: public static BoundList decorate(final List source) {
069: return new BoundList(source);
070: }
071:
072: public static BoundList decorate(final List source,
073: final CollectionChangeEventFactory eventFactory) {
074:
075: return new BoundList(source, eventFactory);
076: }
077:
078: // Utility methods
079: //-----------------------------------------------------------------------
080: /**
081: * Typecast the collection to a List.
082: *
083: * @return the wrapped collection as a List
084: */
085: private List getList() {
086: return (List) getCollection();
087: }
088:
089: // List API (these methods don't change the List)
090: //-----------------------------------------------------------------------
091: public Object get(int index) {
092: return getList().get(index);
093: }
094:
095: public int indexOf(Object object) {
096: return getList().indexOf(object);
097: }
098:
099: public int lastIndexOf(Object object) {
100: return getList().lastIndexOf(object);
101: }
102:
103: // Decorator methods
104: //-----------------------------------------------------------------------
105: /**
106: * <p>
107: * Inserts an object at the specified index (optional operation).
108: * This method is assumed
109: * to succeed if no exceptions are thrown. It is important to note that
110: * it is impossible to document all cases where an exception is
111: * thrown, as this is totally determined by the implementation class
112: * of the decorated List. See the documentation for the decorated
113: * List for details.
114: * </p>
115: * <p>
116: * This method will only fire a <code>CollectionChangeEvent</code>
117: * if the operation succeeds. Therefore, calling this method will
118: * always result in a <code>CollectionChangeEvent</code> or an
119: * exception from the decorated list. An exception prevents the
120: * <code>CollectionChangeEvent</code> from being fired.
121: * </p>
122: *
123: * @param index The index at which to insert the object.
124: * @param object The object to insert in the list.
125: * @throws IndexOutOfBoundsException if the index does not represent a
126: * position in the List.
127: * @throws UnsupportedOperationException if the decorated list does
128: * not support this optional operation.
129: */
130: public void add(int index, Object object) {
131: getList().add(index, object);
132: CollectionChangeEvent evt = eventFactory.createAddIndexed(
133: index, object, true);
134: firePropertyChange(evt);
135: }
136:
137: /**
138: * <p>
139: * Inserts all of the elements in the specified collection into this
140: * list at the specified position (optional operation).
141: * </p>
142: *
143: * <p>
144: * This method will only fire a <code>CollectionChangeEvent</code>
145: * if the decorated list completes the call normally. Therefore,
146: * calling this method will
147: * always result in a <code>CollectionChangeEvent</code> or an
148: * exception from the decorated list. An exception prevents the
149: * <code>CollectionChangeEvent</code> from being fired. Unlike the
150: * {@link #add(int index, Object object)} method, however, an event
151: * may be fired when the collection has not changed. It is therefore
152: * important to check the {@link CollectionChangeEvent.isChanged()}
153: * flag.
154: * </p>
155: * @param index The index at which to insert the collection.
156: * @param coll The collection of objects to insert in the list.
157: *
158: * @throws IndexOutOfBoundsException if the index does not represent a
159: * position in the List.
160: * @throws UnsupportedOperationException if the decorated list does
161: * not support this optional operation.
162: */
163: public boolean addAll(int index, Collection coll) {
164: boolean changed = getList().addAll(index, coll);
165: CollectionChangeEvent evt = eventFactory.createAddAllIndexed(
166: index, coll, changed);
167: firePropertyChange(evt);
168:
169: return changed;
170: }
171:
172: /**
173: * <p>
174: * Removes the element at the specified position in this list
175: * (optional operation). Shifts any subsequent elements to the left
176: * (subtracts one from their indices). Returns the element that was
177: * removed from the list.
178: * </p>
179: * <p>
180: * This method will only fire a <code>CollectionChangeEvent</code>
181: * if the operation succeeds. Therefore, calling this method will
182: * always result in a <code>CollectionChangeEvent</code> or an
183: * exception from the decorated list. An exception prevents the
184: * <code>CollectionChangeEvent</code> from being fired.
185: * </p>
186: *
187: * @param index The index of the element to remove.
188: * @return The object which was removed from the list.
189: * @throws IndexOutOfBoundsException if the index does not represent a
190: * position in the List.
191: * @throws UnsupportedOperationException if the decorated list does
192: * not support this optional operation.
193: */
194: public Object remove(int index) {
195: Object element = getList().remove(index);
196: CollectionChangeEvent evt = eventFactory.createRemoveIndexed(
197: index, element, true);
198: firePropertyChange(evt);
199:
200: return element;
201: }
202:
203: /**
204: * <p>
205: * Replaces the element at the specified position in this list with
206: * the specified element (optional operation).
207: * </p>
208: * <p>
209: * This method will only fire a <code>CollectionChangeEvent</code>
210: * if the operation succeeds. Therefore, calling this method will
211: * always result in a <code>CollectionChangeEvent</code> or an
212: * exception from the decorated list. An exception prevents the
213: * <code>CollectionChangeEvent</code> from being fired.
214: * </p>
215: *
216: * @param index The index of the element to change.
217: * @param object The new value to place at location <code>index</code>.
218: * @return The object formerly at location <code>index</code>.
219: * @throws IndexOutOfBoundsException if the index does not represent a
220: * position in the List.
221: * @throws UnsupportedOperationException if the decorated list does
222: * not support this optional operation.
223: */
224: public Object set(int index, Object object) {
225: Object oldElement = getList().set(index, object);
226: CollectionChangeEvent evt = eventFactory.createSetIndexed(
227: index, oldElement, object, true);
228: firePropertyChange(evt);
229:
230: return oldElement;
231: }
232:
233: public ListIterator listIterator() {
234: return new BoundListIterator(getList().listIterator());
235: }
236:
237: public ListIterator listIterator(int index) {
238: return new BoundListIterator(getList().listIterator(index));
239: }
240:
241: /**
242: * <p>
243: * Returns a view of the portion of this list between fromIndex,
244: * inclusive, and toIndex, exclusive. (If fromIndex and toIndex are equal,
245: * the returned list is empty.) The returned list is backed by this list,
246: * so changes in the returned list are reflected in this list, and
247: * vice-versa. The returned list supports all of the optional list
248: * operations supported by this list.
249: * </p>
250: * <p>
251: * Because changes in the subList are reflected in the list, this
252: * decorator implementation ensures that listeners registered with this
253: * list are notified of changes made through the sublist interface.
254: * Changes caused by the sub list view will create events which have the
255: * sub list as the <code>source</code> property. This is likely to
256: * be important as the view causing the change will impact the
257: * indicies of any indexed events. The indices are consistent with the
258: * List view specified as the source object.
259: * </p>
260: * <p>
261: * Listeners may be added or removed
262: * from the main list before or after the sub list is created. Events
263: * will be fired to listeners registered at the time of the event, not
264: * at the time the sublist was created. Listeners may also be added
265: * separately to the sublist, if desired.
266: * </p>
267: * @param fromIndex first index included in the sublist view
268: * @param toIndex first index <i>not</i> included in the sublist view.
269: * @return The sublist view backed by this list.
270: */
271: public List subList(int fromIndex, int toIndex) {
272: // make the subList view
273: List subList = getList().subList(fromIndex, toIndex);
274:
275: // clone the event factory we're using.
276: CollectionChangeEventFactory factoryCopy = (CollectionChangeEventFactory) (eventFactory
277: .clone());
278:
279: // decorate the sub list view.
280: BoundList boundSubList = BoundList.decorate(subList,
281: factoryCopy);
282:
283: // relay events from sublist to our subscribers
284: boundSubList.addPropertyChangeListener(createEventRepeater());
285:
286: // relay events from main list to sublist subscribers
287: addPropertyChangeListener(boundSubList.createEventRepeater());
288:
289: return boundSubList;
290: }
291:
292: /**
293: * Inner class ListIterator for the BoundList.
294: */
295: protected class BoundListIterator extends
296: AbstractListIteratorDecorator {
297:
298: protected Object last;
299:
300: protected BoundListIterator(ListIterator iterator) {
301: super (iterator);
302: }
303:
304: public Object next() {
305: last = super .next();
306: return last;
307: }
308:
309: public Object previous() {
310: last = iterator.previous();
311: return last;
312: }
313:
314: public void remove() {
315: int index = iterator.previousIndex();
316: iterator.remove();
317: CollectionChangeEvent evt = eventFactory
318: .createRemoveIterated(index, last, true);
319: firePropertyChange(evt);
320: }
321:
322: public void add(Object object) {
323: int index = iterator.nextIndex();
324: iterator.add(object);
325: CollectionChangeEvent evt = eventFactory.createAddIterated(
326: index, object, true);
327: firePropertyChange(evt);
328: }
329:
330: public void set(Object object) {
331: int index = iterator.previousIndex();
332: iterator.set(object);
333: CollectionChangeEvent evt = eventFactory.createSetIterated(
334: index, last, object, true);
335: firePropertyChange(evt);
336: }
337: }
338: }
|