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}