001    /* EventListenerList.java --
002       Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package javax.swing.event;
039    
040    import java.io.IOException;
041    import java.io.ObjectInputStream;
042    import java.io.ObjectOutputStream;
043    import java.io.Serializable;
044    import java.lang.reflect.Array;
045    import java.util.EventListener;
046    
047    
048    /**
049     * A utility class for keeping track of {@link EventListener}s.
050     *
051     * <p><b>Example for using this class:</b>
052     *
053     * <blockquote><pre> import java.util.EventListener;
054     * import javax.swing.event.EventListenerList;
055     *
056     * class Foo
057     * {
058     *   protected final EventListenerList listeners = new EventListenerList();
059     *   protected BarClosedEvent barClosedEvent = null;
060     *
061     *   public void addBarListener(BarListener l)
062     *   {
063     *     listeners.<a href="#add(java.lang.Class, java.util.EventListener)"
064     *               >add</a>(BarListener.class, l);
065     *   }
066     *
067     *   public void removeBarListener(BarListener l)
068     *   {
069     *     listeners.<a href="#remove(java.lang.Class, java.util.EventListener)"
070     *               >remove</a>(BarListener.class, l);
071     *   }
072     *
073     *   protected void fireBarClosedEvent()
074     *   {
075     *     Object[] l = listeners.<a href="#getListenerList()"
076     *                            >getListenerList()</a>;
077     *
078     *     for (int i = l.length - 2; i >= 0; i -= 2)
079     *       if (l[i] == BarListener.class)
080     *         {
081     *           // Create the event on demand, when it is needed the first time.
082     *           if (barClosedEvent == null)
083     *             barClosedEvent = new BarClosedEvent(this);
084     *
085     *           ((BarClosedListener) l[i + 1]).barClosed(barClosedEvent);
086     *         }
087     *   }
088     * }</pre></blockquote>
089     *
090     * @author Andrew Selkirk (aselkirk@sympatico.ca)
091     * @author Sascha Brawer (brawer@dandelis.ch)
092     */
093    public class EventListenerList
094      implements Serializable
095    {
096      /**
097       * An ID for serializing instances of this class; verified with the
098       * serialver tool of Sun J2SE 1.4.1_01.
099       */
100      static final long serialVersionUID = -5677132037850737084L;
101    
102    
103      /**
104       * An empty array that is shared by all instances of this class that
105       * have no listeners.
106       */
107      private static final Object[] NO_LISTENERS = new Object[0];
108      
109      
110      /**
111       * An array with all currently registered listeners.  The array has
112       * twice as many elements as there are listeners.  For an even
113       * integer <code>i</code>, <code>listenerList[i]</code> indicates
114       * the registered class, and <code>listenerList[i + 1]</code> is the
115       * listener.
116       */
117      protected transient Object[] listenerList = NO_LISTENERS;
118    
119      
120      /**
121       * EventListenerList constructor
122       */
123      public EventListenerList()
124      {
125        // Nothing to do here.
126      }
127    
128    
129      /**
130       * Registers a listener of a specific type.
131       *
132       * @param t the type of the listener.
133       *
134       * @param listener the listener to add, which must be an instance of
135       * <code>t</code>, or of a subclass of <code>t</code>.
136       *
137       * @throws IllegalArgumentException if <code>listener</code> is not
138       * an instance of <code>t</code> (or a subclass thereof).
139       *
140       * @throws NullPointerException if <code>t</code> is <code>null</code>.
141       */
142      public <T extends EventListener> void add(Class<T> t, T listener)
143      {
144        int oldLength;
145        Object[] newList;
146    
147        if (listener == null)
148          return;
149    
150        if (!t.isInstance(listener))
151          throw new IllegalArgumentException();
152    
153        oldLength = listenerList.length;
154        newList = new Object[oldLength + 2];
155        if (oldLength > 0)
156          System.arraycopy(listenerList, 0, newList, 0, oldLength);
157    
158        newList[oldLength] = t;
159        newList[oldLength + 1] = listener;
160        listenerList = newList;
161      }
162    
163    
164      /**
165       * Determines the number of listeners.
166       */
167      public int getListenerCount()
168      {
169        return listenerList.length / 2;
170      }
171    
172    
173      /**
174       * Determines the number of listeners of a particular class.
175       *
176       * @param t the type of listeners to be counted. In order to get
177       * counted, a subscribed listener must be exactly of class
178       * <code>t</code>. Thus, subclasses of <code>t</code> will not be
179       * counted.
180       */
181      public int getListenerCount(Class<?> t)
182      {
183        int result = 0;
184        for (int i = 0; i < listenerList.length; i += 2)
185          if (t == listenerList[i])
186            ++result;
187    
188        return result;
189      }
190    
191    
192      /**
193       * Returns an array containing a sequence of listenerType/listener pairs, one
194       * for each listener.
195       * 
196       * @return An array containing the listener types and references.
197       */
198      public Object[] getListenerList()
199      {
200        // returning the internal storage is a bad idea, but tests show that the
201        // reference implementation does this...
202        return listenerList;
203      }
204    
205    
206      /**
207       * Retrieves the currently subscribed listeners of a particular
208       * type.  For a listener to be returned, it must have been
209       * registered with exactly the type <code>c</code>; subclasses are
210       * not considered equal.
211       *
212       * <p>The returned array can always be cast to <code>c[]</code>.
213       * Since it is a newly allocated copy, the caller may arbitrarily
214       * modify the array.
215       *
216       * @param c the class which was passed to {@link #add}.
217       *
218       * @throws ClassCastException if <code>c</code> does not implement
219       * the {@link EventListener} interface.
220       *
221       * @throws NullPointerException if <code>c</code> is
222       * <code>null</code>.
223       *
224       * @return an array of <code>c</code> whose elements are the
225       * currently subscribed listeners of the specified type.  If there
226       * are no such listeners, an empty array is returned.
227       *
228       * @since 1.3
229       */
230      public <T extends EventListener> T[] getListeners(Class<T> c)
231      {
232        int count, f;
233        EventListener[] result;
234    
235        count = getListenerCount(c);
236        result = (EventListener[]) Array.newInstance(c, count);
237        f = 0;
238        for (int i = listenerList.length - 2; i >= 0; i -= 2)
239          if (listenerList[i] == c)
240            result[f++] = (EventListener) listenerList[i + 1];
241        
242        return (T[]) result;
243      }
244    
245    
246      /**
247       * Removes a listener of a specific type.
248       *
249       * @param t the type of the listener.
250       *
251       * @param listener the listener to remove, which must be an instance
252       * of <code>t</code>, or of a subclass of <code>t</code>.
253       *
254       * @throws IllegalArgumentException if <code>listener</code> is not
255       * an instance of <code>t</code> (or a subclass thereof).
256       *
257       * @throws NullPointerException if <code>t</code> is <code>null</code>.
258       */
259      public <T extends EventListener> void remove(Class<T> t, T listener)
260      {
261        Object[] oldList, newList;
262        int oldLength;
263    
264        if (listener == null)
265          return;
266    
267        if (!t.isInstance(listener))
268          throw new IllegalArgumentException();
269    
270        oldList = listenerList;
271        oldLength = oldList.length;
272        for (int i = 0; i < oldLength; i += 2)
273          if (oldList[i] == t && oldList[i + 1] == listener)
274            {
275              if (oldLength == 2)
276                newList = NO_LISTENERS;
277              else
278                {
279                  newList = new Object[oldLength - 2];
280                  if (i > 0)
281                    System.arraycopy(oldList, 0, newList, 0, i);
282                  if (i < oldLength - 2)
283                    System.arraycopy(oldList, i + 2, newList, i,
284                                     oldLength - 2 - i);
285                }
286              listenerList = newList;
287              return;
288            }
289      }
290    
291    
292      /**
293       * Returns a string representation of this object that may be useful
294       * for debugging purposes.
295       */
296      public String toString()
297      {
298        StringBuffer buf = new StringBuffer("EventListenerList: ");
299        buf.append(listenerList.length / 2);
300        buf.append(" listeners: ");
301        for (int i = 0; i < listenerList.length; i += 2)
302          {
303            buf.append(" type ");
304            buf.append(((Class) listenerList[i]).getName());
305            buf.append(" listener ");
306            buf.append(listenerList[i + 1]);
307          }
308        return buf.toString();
309      }
310    
311      /**
312       * Serializes an instance to an ObjectOutputStream.
313       *
314       * @param out the stream to serialize to
315       *
316       * @throws IOException if something goes wrong
317       */
318      private void writeObject(ObjectOutputStream out)
319        throws IOException
320      {
321        out.defaultWriteObject();
322        for (int i = 0; i < listenerList.length; i += 2)
323          {
324            Class cl = (Class) listenerList[i];
325            EventListener l = (EventListener) listenerList[i + 1];
326            if (l != null && l instanceof Serializable)
327              {
328                out.writeObject(cl.getName());
329                out.writeObject(l);
330              }
331          }
332        // Write end marker.
333        out.writeObject(null);
334      }
335    
336      /**
337       * Deserializes an instance from an ObjectInputStream.
338       *
339       * @param in the input stream
340       *
341       * @throws ClassNotFoundException if a serialized class can't be found
342       * @throws IOException if something goes wrong
343       */
344      private <T extends EventListener> void readObject(ObjectInputStream in)
345        throws ClassNotFoundException, IOException
346      {
347        listenerList = NO_LISTENERS;
348        in.defaultReadObject();
349        Object type;
350        ClassLoader cl = Thread.currentThread().getContextClassLoader();
351        while ((type = in.readObject()) != null)
352          {
353            EventListener l = (EventListener) in.readObject();
354            add(((Class<T>) Class.forName((String) type, true, cl)), (T) l);
355          }
356      }
357    }