View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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         //TODO: Implement this.
263         //        return delegate_.removeAll(collection);
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         //TODO: Implement this.
287         //        return delegate_.retainAll(collection);
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&nbsp;?&nbsp;e==null&nbsp;:&nbsp;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         // Is there anything to do?
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         // Is there anything to do?
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         // Is there anything to do?
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