1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package com.gargoylesoftware.base.collections;
39
40 import com.gargoylesoftware.base.util.DetailedNullPointerException;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.Collections;
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.ListIterator;
47
48 /***
49 * This is a wrapper for a List object that fires
50 * {@link com.gargoylesoftware.base.collections.NotificationListEvent}'s
51 * whenever the list is modified.
52 *
53 * @version $Revision: 1.5 $
54 * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
55 */
56 public class NotificationList
57 implements
58 List {
59
60 private final List delegate_;
61
62 private final List listenerList_ = new ArrayList();
63
64 /***
65 * Construct a new NotificationList.
66 * @param delegate The list that we will delegate requests to.
67 */
68 public NotificationList( final List delegate ) {
69 delegate_ = delegate;
70
71 assertNotNull("delegate", delegate);
72 }
73
74 /***
75 * Appends the specified element to the end of this list (optional
76 * operation). <p>
77 *
78 * Lists that support this operation may place limitations on what
79 * elements may be added to this list. In particular, some
80 * lists will refuse to add null elements, and others will impose
81 * restrictions on the type of elements that may be added. List
82 * classes should clearly specify in their documentation any restrictions
83 * on what elements may be added.
84 *
85 * @param object element to be appended to this list.
86 * @return <tt>true</tt> (as per the general contract of the
87 * <tt>Collection.add</tt> method).
88 *
89 * @throws UnsupportedOperationException if the <tt>add</tt> method is not
90 * supported by this list.
91 */
92 public boolean add( final Object object ) throws UnsupportedOperationException {
93 final int startIndex = delegate_.size();
94 final boolean rc = delegate_.add(object);
95 fireInsert( startIndex, 1 );
96 return rc;
97 }
98
99 /***
100 * Inserts the specified element at the specified position in this list
101 * (optional operation). Shifts the element currently at that position
102 * (if any) and any subsequent elements to the right (adds one to their
103 * indices).
104 *
105 * @param index index at which the specified element is to be inserted.
106 * @param element element to be inserted.
107 *
108 * @throws UnsupportedOperationException if the <tt>add</tt> method is not
109 * supported by this list.
110 */
111 public void add(final int index, final Object element) throws UnsupportedOperationException {
112 delegate_.add(index, element);
113 fireInsert( index, 1 );
114 }
115
116 /***
117 * Appends all of the elements in the specified collection to the end of
118 * this list, in the order that they are returned by the specified
119 * collection's iterator (optional operation). The behavior of this
120 * operation is unspecified if the specified collection is modified while
121 * the operation is in progress. (Note that this will occur if the
122 * specified collection is this list, and it's nonempty.)
123 *
124 * @param collection collection whose elements are to be added to this list.
125 * @return <tt>true</tt> if this list changed as a result of the call.
126 *
127 * @throws UnsupportedOperationException if the <tt>addAll</tt> method is
128 * not supported by this list.
129 * @see #add(Object)
130 */
131 public boolean addAll( final Collection collection ) throws UnsupportedOperationException {
132 assertNotNull("collection", collection);
133 final int startIndex = delegate_.size();
134 final boolean rc = delegate_.addAll(collection);
135 fireInsert( startIndex, collection.size() );
136 return rc;
137 }
138
139 /***
140 * Inserts all of the elements in the specified collection into this
141 * list at the specified position (optional operation). Shifts the
142 * element currently at that position (if any) and any subsequent
143 * elements to the right (increases their indices). The new elements
144 * will appear in this list in the order that they are returned by the
145 * specified collection's iterator. The behavior of this operation is
146 * unspecified if the specified collection is modified while the
147 * operation is in progress. (Note that this will occur if the specified
148 * collection is this list, and it's nonempty.)
149 *
150 * @param index index at which to insert first element from the specified
151 * collection.
152 * @param collection elements to be inserted into this list.
153 * @return <tt>true</tt> if this list changed as a result of the call.
154 *
155 * @throws UnsupportedOperationException if the <tt>addAll</tt> method is
156 * not supported by this list.
157 */
158 public boolean addAll(int index, final Collection collection) throws UnsupportedOperationException {
159 assertNotNull("collection", collection);
160
161 final boolean rc = delegate_.addAll(index, collection);
162 fireInsert( index, collection.size() );
163 return rc;
164 }
165
166 /***
167 * Returns the element at the specified position in this list.
168 *
169 * @param index index of element to return.
170 * @return the element at the specified position in this list.
171 */
172 public Object get(final int index) {
173 return delegate_.get(index);
174 }
175
176 /***
177 * Replaces the element at the specified position in this list with the
178 * specified element (optional operation).
179 *
180 * @param index index of element to replace.
181 * @param element element to be stored at the specified position.
182 * @return the element previously at the specified position.
183 *
184 * @throws UnsupportedOperationException if the <tt>set</tt> method is not
185 * supported by this list.
186 */
187 public Object set(final int index, final Object element) throws UnsupportedOperationException {
188 final List oldValues = new ArrayList(1);
189 oldValues.add(delegate_.get(index));
190
191 final List newValues = new ArrayList(1);
192 newValues.add(element);
193
194 Object rc = delegate_.set(index, element);
195
196 fireChanged( index, oldValues, newValues );
197 return rc;
198 }
199
200 /***
201 * Removes the element at the specified position in this list (optional
202 * operation). Shifts any subsequent elements to the left (subtracts one
203 * from their indices). Returns the element that was removed from the
204 * list.
205 *
206 * @param index the index of the element to removed.
207 * @return the element previously at the specified position.
208 *
209 * @throws UnsupportedOperationException if the <tt>remove</tt> method is
210 * not supported by this list.
211 */
212 public Object remove(final int index) throws UnsupportedOperationException {
213 final List objectsRemoved = new ArrayList(1);
214 objectsRemoved.add(delegate_.get(index));
215
216 final Object rc = delegate_.remove(index);
217 fireRemove( index, index, objectsRemoved );
218 return rc;
219 }
220
221 /***
222 * Removes the first occurrence in this list of the specified element
223 * (optional operation). If this list does not contain the element, it is
224 * unchanged. More formally, removes the element with the lowest index i
225 * such that <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt> (if
226 * such an element exists).
227 *
228 * @param object element to be removed from this list, if present.
229 * @return <tt>true</tt> if this list contained the specified element.
230 *
231 * @throws UnsupportedOperationException if the <tt>remove</tt> method is
232 * not supported by this list.
233 */
234 public boolean remove( final Object object ) throws UnsupportedOperationException {
235 final List objectsRemoved = new ArrayList(1);
236 objectsRemoved.add(object);
237
238 final int index = delegate_.indexOf(object);
239 final boolean rc = delegate_.remove(object);
240 fireRemove( index, index, objectsRemoved );
241 return rc;
242 }
243
244 /***
245 * Removes from this list all the elements that are contained in the
246 * specified collection (optional operation).
247 *
248 * <b>Implementation note</b> This is currently unsupported.
249 *
250 * @param collection collection that defines which elements will be removed from
251 * this list.
252 * @return <tt>true</tt> if this list changed as a result of the call.
253 *
254 * @throws UnsupportedOperationException if the <tt>removeAll</tt> method
255 * is not supported by this list.
256 *
257 * @see #remove(Object)
258 * @see #contains(Object)
259 */
260 public boolean removeAll( final Collection collection ) throws UnsupportedOperationException {
261 throw new UnsupportedOperationException("Not implemented yet");
262
263
264 }
265
266 /***
267 * Retains only the elements in this list that are contained in the
268 * specified collection (optional operation). In other words, removes
269 * from this list all the elements that are not contained in the specified
270 * collection.
271 *
272 * <b>Implementation note</b> This is currently unsupported.
273 *
274 * @param collection collection that defines which elements this set will retain.
275 *
276 * @return <tt>true</tt> if this list changed as a result of the call.
277 *
278 * @throws UnsupportedOperationException if the <tt>retainAll</tt> method
279 * is not supported by this list.
280 *
281 * @see #remove(Object)
282 * @see #contains(Object)
283 */
284 public boolean retainAll( final Collection collection ) throws UnsupportedOperationException {
285 throw new UnsupportedOperationException("Not implemented yet");
286
287
288 }
289
290 /***
291 * Returns the index in this list of the first occurrence of the specified
292 * element, or -1 if this list does not contain this element.
293 * More formally, returns the lowest index <tt>i</tt> such that
294 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
295 * or -1 if there is no such index.
296 *
297 * @param object element to search for.
298 * @return the index in this list of the first occurrence of the specified
299 * element, or -1 if this list does not contain this element.
300 */
301 public int indexOf(final Object object) {
302 return delegate_.indexOf(object);
303 }
304
305 /***
306 * Returns the index in this list of the last occurrence of the specified
307 * element, or -1 if this list does not contain this element.
308 * More formally, returns the highest index <tt>i</tt> such that
309 * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
310 * or -1 if there is no such index.
311 *
312 * @param object element to search for.
313 * @return the index in this list of the last occurrence of the specified
314 * element, or -1 if this list does not contain this element.
315 */
316 public int lastIndexOf(final Object object) {
317 return delegate_.lastIndexOf(object);
318 }
319
320 /***
321 * Returns a list iterator of the elements in this list (in proper
322 * sequence).
323 *
324 * @return a list iterator of the elements in this list (in proper
325 * sequence).
326 */
327 public synchronized ListIterator listIterator() {
328 return delegate_.listIterator();
329 }
330
331 /***
332 * Returns a list iterator of the elements in this list (in proper
333 * sequence), starting at the specified position in this list. The
334 * specified index indicates the first element that would be returned by
335 * an initial call to the <tt>next</tt> method. An initial call to
336 * the <tt>previous</tt> method would return the element with the
337 * specified index minus one.
338 *
339 * @param index index of first element to be returned from the
340 * list iterator (by a call to the <tt>next</tt> method).
341 * @return a list iterator of the elements in this list (in proper
342 * sequence), starting at the specified position in this list.
343 */
344 public ListIterator listIterator(final int index) {
345 return delegate_.listIterator(index);
346 }
347
348 /***
349 * Returns a view of the portion of this list between the specified
350 * <tt>fromIndex</tt>, inclusive, and <tt>toIndex</tt>, exclusive. (If
351 * <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is
352 * empty.) The returned list is backed by this list, so changes in the
353 * returned list are reflected in this list, and vice-versa. The returned
354 * list supports all of the optional list operations supported by this
355 * list.<p>
356 *
357 * This method eliminates the need for explicit range operations (of
358 * the sort that commonly exist for arrays). Any operation that expects
359 * a list can be used as a range operation by passing a subList view
360 * instead of a whole list. For example, the following idiom
361 * removes a range of elements from a list:
362 * <pre>
363 * list.subList(from, to).clear();
364 * </pre>
365 * Similar idioms may be constructed for <tt>indexOf</tt> and
366 * <tt>lastIndexOf</tt>, and all of the algorithms in the
367 * <tt>Collections</tt> class can be applied to a subList.<p>
368 *
369 * The semantics of this list returned by this method become undefined if
370 * the backing list (i.e., this list) is <i>structurally modified</i> in
371 * any way other than via the returned list. (Structural modifications are
372 * those that change the size of this list, or otherwise perturb it in such
373 * a fashion that iterations in progress may yield incorrect results.)
374 *
375 * @param fromIndex low endpoint (inclusive) of the subList.
376 * @param toIndex high endpoint (exclusive) of the subList.
377 * @return a view of the specified range within this list.
378 */
379 public List subList(final int fromIndex, final int toIndex) {
380 return delegate_.subList(fromIndex, toIndex);
381 }
382
383 /***
384 * Returns an iterator over the elements in this list in proper sequence.
385 *
386 * @return an iterator over the elements in this list in proper sequence.
387 */
388 public Iterator iterator() {
389 return delegate_.iterator();
390 }
391
392 /***
393 * Returns <tt>true</tt> if this list contains all of the elements of the
394 * specified collection.
395 *
396 * @param collection collection to be checked for containment in this list.
397 * @return <tt>true</tt> if this list contains all of the elements of the
398 * specified collection.
399 *
400 * @see #contains(Object)
401 */
402 public boolean containsAll( final Collection collection ) {
403 return delegate_.containsAll(collection);
404 }
405
406 /***
407 * Returns <tt>true</tt> if this list contains the specified element.
408 * More formally, returns <tt>true</tt> if and only if this list contains
409 * at least one element <tt>e</tt> such that
410 * <tt>(o==null ? e==null : o.equals(e))</tt>.
411 *
412 * @param object element whose presence in this list is to be tested.
413 * @return <tt>true</tt> if this list contains the specified element.
414 */
415 public boolean contains( final Object object ) {
416 return delegate_.contains(object);
417 }
418
419 /***
420 * Removes all of the elements from this list (optional operation). This
421 * list will be empty after this call returns (unless it throws an
422 * exception).
423 *
424 * @throws UnsupportedOperationException if the <tt>clear</tt> method is
425 * not supported by this list.
426 */
427 public void clear() throws UnsupportedOperationException {
428 delegate_.clear();
429 }
430
431 /***
432 * Returns <tt>true</tt> if this list contains no elements.
433 *
434 * @return <tt>true</tt> if this list contains no elements.
435 */
436 public boolean isEmpty() {
437 return delegate_.isEmpty();
438 }
439
440 /***
441 * Returns the number of elements in this list. If this list contains
442 * more than <tt>Integer.MAX_VALUE</tt> elements, returns
443 * <tt>Integer.MAX_VALUE</tt>.
444 *
445 * @return the number of elements in this list.
446 */
447 public int size() {
448 return delegate_.size();
449 }
450
451 /***
452 * Returns an array containing all of the elements in this list in proper
453 * sequence. Obeys the general contract of the
454 * <tt>List.toArray</tt> method.
455 *
456 * @return an array containing all of the elements in this list in proper
457 * sequence.
458 * @see List#toArray(Object[])
459 */
460 public Object[] toArray() {
461 return delegate_.toArray();
462 }
463
464 /***
465 * Returns an array containing all of the elements in this list in proper
466 * sequence; the runtime type of the returned array is that of the
467 * specified array. Obeys the general contract of the
468 * <tt>Collection.toArray(Object[])</tt> method.
469 *
470 * @param array the array into which the elements of this list are to
471 * be stored, if it is big enough; otherwise, a new array of the
472 * same runtime type is allocated for this purpose.
473 * @return an array containing the elements of this list.
474 *
475 * @throws ArrayStoreException if the runtime type of the specified array
476 * is not a supertype of the runtime type of every element in
477 * this list.
478 */
479 public Object[] toArray( Object array[] ) throws ArrayStoreException {
480 return delegate_.toArray(array);
481 }
482
483 /***
484 * Add a NotificationListListener.
485 * @param listener The listener to add.
486 */
487 public synchronized void addNotificationListListener(
488 final NotificationListListener listener ) {
489
490 assertNotNull("listener", listener);
491
492 listenerList_.add( listener );
493 }
494
495 /***
496 * Remove a NotificationListListener.
497 * @param listener The listener to remove.
498 */
499 public synchronized void removeNotificationListListener(
500 final NotificationListListener listener ) {
501
502 assertNotNull("listener", listener);
503 listenerList_.remove( listener );
504 }
505
506 /***
507 * Fire an insert event.
508 * @param startIndex The first index
509 * @param insertCount The number of items being inserted
510 */
511 private synchronized void fireInsert( final int startIndex,
512 final int insertCount ) {
513
514
515 if( listenerList_.isEmpty() ) {
516 return;
517 }
518
519 final int endIndex = startIndex + insertCount;
520 final NotificationListEvent event
521 = new NotificationListEvent( this,
522 NotificationListEvent.INSERT,
523 startIndex,
524 startIndex,
525 Collections.EMPTY_LIST,
526 delegate_.subList(startIndex, endIndex) );
527
528 final NotificationListListener listeners[]
529 = new NotificationListListener[listenerList_.size()];
530 listenerList_.toArray( listeners );
531
532 int i;
533 for( i=0; i<listeners.length; i++ ) {
534 listeners[i].listElementsAdded(event);
535 }
536 }
537
538 /***
539 * Fire a remove event.
540 * @param startIndex The first index
541 * @param endIndex The last index
542 * @param objectsRemoved A list of all the objects that have been removd.
543 */
544 private synchronized void fireRemove( final int startIndex,
545 final int endIndex,
546 final List objectsRemoved) {
547
548
549 if( listenerList_.isEmpty() ) {
550 return;
551 }
552
553 final NotificationListEvent event
554 = new NotificationListEvent( this,
555 NotificationListEvent.REMOVE,
556 startIndex,
557 endIndex,
558 objectsRemoved,
559 Collections.EMPTY_LIST);
560
561 final NotificationListListener listeners[]
562 = new NotificationListListener[listenerList_.size()];
563 listenerList_.toArray( listeners );
564
565 int i;
566 for( i=0; i<listeners.length; i++ ) {
567 listeners[i].listElementsRemoved(event);
568 }
569 }
570
571 /***
572 * Fire a changed event.
573 * @param startIndex The first index
574 * @param oldValues The old values
575 * @param newValues The new values
576 */
577 private synchronized void fireChanged( final int startIndex,
578 final List oldValues,
579 final List newValues ) {
580 if( oldValues.size() != newValues.size() ) {
581 throw new IllegalArgumentException("Lists must be the same size:"
582 + " oldValues.size()="
583 + oldValues.size()
584 + " newValues.size()="
585 + newValues.size() );
586 }
587
588
589 if( listenerList_.isEmpty() ) {
590 return;
591 }
592
593 final NotificationListEvent event
594 = new NotificationListEvent( this,
595 NotificationListEvent.CHANGE,
596 startIndex,
597 startIndex,
598 oldValues,
599 newValues);
600
601 final NotificationListListener listeners[]
602 = new NotificationListListener[listenerList_.size()];
603 listenerList_.toArray( listeners );
604
605 int i;
606 for( i=0; i<listeners.length; i++ ) {
607 listeners[i].listElementsChanged(event);
608 }
609
610 }
611
612
613 private void assertNotNull( final String fieldName, final Object object ) {
614 if( object == null ) {
615 throw new DetailedNullPointerException(fieldName);
616 }
617 }
618 }
619