001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 */
020
021package org.biojava.utils;
022
023import java.lang.ref.Reference;
024import java.lang.ref.WeakReference;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.Set;
029
030/**
031 * <p>
032 * A utility class to provide management for informing ChangeListeners of
033 * ChangeEvents.
034 * </p>
035 *
036 * <p>
037 * This is loosely modelled after the standard PropertyChangeEvent objects.
038 * </p>
039 *
040 * <p>
041 * For an object to correctly fire these events, they must follow a broad
042 * outline like this:
043 * <code><pre>
044 * public void mutator(foo arg) throw ChangeVetoException {
045 *   ChangeEvent cevt = new ChangeEvent(this, SOME_EVENT_TYPE, arg);
046 *   synchronized(changeSupport) {
047 *     changeSupport.firePreChangeEvent(cevt);
048 *     // update our state using arg
049 *     // ...
050 *     changeSupport.firePostChangeEvent(cevt);
051 *   }
052 * }
053 * </pre></code>
054 * </p>
055 *
056 * <p>
057 * The methods that delegate adding and removing listeners to a ChangeSupport
058 * must take responsibility for synchronizing on the delegate.
059 * </p>
060 *
061 * @author Matthew Pocock
062 * @author Thomas Down
063 * @author Keith James (docs)
064 * @author Kalle Naslund (tiny bugfix)
065 * @since 1.1
066 */
067
068public class ChangeSupport {
069  private int listenerCount;
070  private int delta;
071  private Set unchanging;
072  private Reference[] listeners;
073  private ChangeType[] types;
074
075  /**
076   * Return true if we have any listeners registered at all.
077   * 
078   * @return true if there are listeners
079   */
080  public boolean hasListeners() {
081      return (listenerCount > 0);
082  }
083  
084  /**
085   * Return true if we have listeners registered for a particular change type.
086   * 
087   * @param ct  the ChangeType to check
088   * @return    true if there are listeners for this type
089   */
090  public boolean hasListeners(ChangeType ct)
091  {
092        for(int i = 0; i < listenerCount ; i++ ) {
093          ChangeType type = (ChangeType) types[i];
094          if(ct.isMatchingType(type)) {
095                return true;
096          }
097        }
098        
099        return false;
100  }
101
102  /**
103   * Generate a new ChangeSupport instance.
104   */
105  public ChangeSupport() {
106    this(5);
107  }
108
109  /**
110   * Generate a new ChangeSupport instance which has room for initialSize
111   * listeners before it needs to grow any resources.
112   *
113   * @param initialSize  the number of listeners that can be added before this
114   *                     needs to grow for the first time
115   */
116  public ChangeSupport(int initialSize) {
117    this(initialSize, 5);
118  }
119
120  /**
121   * Generate a new ChangeSupport instance which has room for initialSize
122   * listeners before it needs to grow any resources, and which will grow by
123   * delta each time.
124   *
125   * @param initialSize  the number of listeners that can be added before this
126   *                     needs to grow for the first time
127   * @param delta  the number of listener slots that this will grow by each time
128   *               it needs to
129   */
130  public ChangeSupport(int initialSize, int delta) {
131    this(Collections.EMPTY_SET, initialSize, delta);
132  }
133
134  public ChangeSupport(Set unchanging) {
135    this(unchanging, 0, 5);
136  }
137
138  /**
139   * Generate a new ChangeSupport instance which has room for initialSize
140   * listeners before it needs to grow any resources, and which will grow by
141   * delta each time.
142   *
143   * @param unchanging Set of ChangeTypes that can never be fired
144   * @param initialSize  the number of listeners that can be added before this
145   *                     needs to grow for the first time
146   * @param delta  the number of listener slots that this will grow by each time
147   *               it needs to
148   */
149  public ChangeSupport(Set unchanging, int initialSize, int delta) {
150    this.listenerCount = 0;
151    this.listeners = new Reference[initialSize];
152    this.types = new ChangeType[initialSize];
153
154    this.delta = delta;
155    this.unchanging = new HashSet(unchanging);
156  }
157  /**
158   * Add a listener that will be informed of all changes.
159   *
160   * @param cl  the ChangeListener to add
161   */
162  public void addChangeListener(ChangeListener cl) {
163    addChangeListener(cl, ChangeType.UNKNOWN);
164  }
165
166  /**
167   * Add a listener that will be informed of changes of a given type (and it's subtypes)
168   *
169   * @param cl  the ChangeListener
170   * @param ct  the ChangeType it is to be informed of
171   */
172  public void addChangeListener(ChangeListener cl, ChangeType ct) {
173    if (ct == null) {
174      throw new NullPointerException("Since 1.2, listeners registered for the null changetype are not meaningful.  Please register a listener for ChangeType.UNKNOWN instead");
175    }
176
177    if(isUnchanging(ct)) {
178      return;
179    }
180
181    synchronized(this) {
182      growIfNecessary();
183      types[listenerCount] = ct;
184      listeners[listenerCount] = new WeakReference(cl);
185      listenerCount++;
186    }
187  }
188
189  /**
190   * Grows the internal resources if by adding one more listener they would be
191   * full.
192   */
193  protected void growIfNecessary() {
194    //try cleaning up first
195    synchronized(this){
196        reapGarbageListeners();
197    }  
198    if(listenerCount == listeners.length) {
199      int newLength = listenerCount + delta;
200      Reference[] newList = new Reference[newLength];
201      ChangeType[] newTypes = new ChangeType[newLength];
202
203      System.arraycopy(listeners, 0, newList, 0, listenerCount);
204      System.arraycopy(types, 0, newTypes, 0, listenerCount);
205
206      listeners = newList;
207      types = newTypes;
208    }
209  }
210
211  /**
212   * Remove a listener that was interested in all types of changes.
213   *
214   * @param cl  a ChangeListener to remove
215   */
216  public void removeChangeListener(ChangeListener cl) {
217    removeChangeListener(cl, ChangeType.UNKNOWN);
218  }
219
220  /**
221   * Remove a listener that was interested in a specific types of changes.
222   *
223   * @param cl  a ChangeListener to remove
224   * @param ct  the ChangeType that it was interested in
225   */
226  public void removeChangeListener(ChangeListener cl, ChangeType ct) {
227    synchronized(this) {
228      for(int i = 0; i < listenerCount; i++) {
229        if( (listeners[i].get() == cl) && (types[i] == ct) ) {
230          listenerCount--;
231          System.arraycopy(listeners, i+1, listeners, i, (listenerCount - i));
232          System.arraycopy(types, i+1, types, i, (listenerCount - i));
233          return;
234        }
235      }
236    }
237  }
238
239    /**
240     * Remove all references to listeners which have been cleared by the
241     * garbage collector.  This method should only be called when the
242     * object is locked.
243     */
244
245    protected void reapGarbageListeners() {
246        int pp = 0;
247        for (int p = 0; p < listenerCount; ++p) {
248            Reference r = listeners[p];
249            if (r.get() != null) {
250                types[pp] = types[p];
251                listeners[pp] = r;
252                pp++;
253            }else{ //if it is null release the reference
254                r = null;
255            }
256        }
257        listenerCount = pp;
258    }
259
260  /**
261   * <p>
262   * Inform the listeners that a change is about to take place using their
263   * firePreChangeEvent methods.
264   * </p>
265   *
266   * <p>
267   * Listeners will be informed if they were interested in all types of event,
268   * or if ce.getType() is equal to the type they are registered for.
269   * </p>
270   *
271   * <p>
272   * This method must be called while the current thread holds the lock on this change support.
273   * </p>
274   * 
275   * @param ce  the ChangeEvent to pass on
276   * @throws ChangeVetoException if any of the listeners veto this change
277   */
278  public void firePreChangeEvent(ChangeEvent ce)
279  throws ChangeVetoException {
280     assert Thread.holdsLock(this)
281            : "firePreChangeEvent must be called in a synchronized block locking the ChangeSupport";
282    boolean needToReap = false;
283
284    ChangeType ct = ce.getType();
285    int listenerCount = this.listenerCount;
286    ChangeType[] types = new ChangeType[listenerCount];
287    System.arraycopy(this.types, 0, types, 0, listenerCount);
288
289    Reference[] listeners = new Reference[listenerCount];
290    System.arraycopy(this.listeners, 0, listeners, 0, listenerCount);
291
292    for(int i = 0; i < listenerCount; i++) {
293      ChangeType lt = types[i];
294      if( ct.isMatchingType(lt)) {
295        ChangeListener cl = (ChangeListener) listeners[i].get();
296        if (cl != null) {
297                synchronized (cl) {
298                        cl.preChange(ce);       
299                        }
300          
301        } else {
302          needToReap = true;
303        }
304      }
305    }
306
307    if (needToReap) {
308      reapGarbageListeners();
309    }
310  }
311
312  /**
313   * <p>
314   * Inform the listeners that a change has taken place using their
315   * firePostChangeEvent methods.
316   * </p>
317   *
318   * <p>
319   * Listeners will be informed if they were interested in all types of event,
320   * or if ce.getType() is equal to the type they are registered for.
321   * </p>
322   *
323   * <p>
324   * This method must be called while the current thread holds the lock on this change support.
325   * </p>
326   *
327   * @param ce  the ChangeEvent to pass on
328   */
329
330  public void firePostChangeEvent(ChangeEvent ce) {
331    assert Thread.holdsLock(this)
332            : "firePostChangeEvent must be called in a synchronized block locking the ChangeSupport";
333    boolean needToReap = false;
334
335    ChangeType ct = ce.getType();
336    int listenerCount = this.listenerCount;
337    ChangeType[] types = new ChangeType[listenerCount];
338    System.arraycopy(this.types, 0, types, 0, listenerCount);
339
340    Reference[] listeners = new Reference[listenerCount];
341    System.arraycopy(this.listeners, 0, listeners, 0, listenerCount);
342
343    for(int i = 0; i < listenerCount; i++) {
344      ChangeType lt = types[i];
345      if( ct.isMatchingType(lt) ) {
346        ChangeListener cl = (ChangeListener) listeners[i].get();
347        if (cl != null) {
348          cl.postChange(ce);
349        } else {
350          needToReap = true;
351        }
352      }
353    }
354
355    if (needToReap) {
356      reapGarbageListeners();
357    }
358  }
359
360  public boolean isUnchanging(ChangeType ct) {
361    if(unchanging == null) {
362      return false;
363    }
364
365    for(Iterator i = ct.matchingTypes(); i.hasNext(); ) {
366      if(unchanging.contains(i.next())) {
367        return true;
368      }
369    }
370
371    return false;
372  }
373
374  public String displayString()
375  {
376    StringBuffer sb = new StringBuffer();
377    sb.append(this.toString());
378    sb.append("\n");
379    for(int i = 0; i < listenerCount; i++) {
380      sb.append("\t");
381      sb.append(listeners[i].get());
382      sb.append("\t");
383      sb.append(types[i]);
384      sb.append("\n");
385    }
386
387    return sb.toString();
388  }
389}