001 package util; 002 003 import java.io.*; 004 import java.util.*; 005 006 /** 007 * Helper class that supports Listeners which themselves can be listened to. 008 * 009 * <p>The main intent is to support garbage collection by avoiding cyclic links between 010 * the listener and its event source. Listeners that use ListenerHelper (and implement 011 * HelpableListener) will be registered with their event source only as long as there 012 * are any listeners registered with them. They will however guarantee up-to-date-ness 013 * indepent of whether a listener is registered with them or not, by polling their 014 * model whenever a query is performed against them and they have no listeners.</p> 015 * 016 * <p>For this mechanism to work properly such listeners must abide by the following 017 * rules:</p> 018 * 019 * <ul> 020 * <li>They must implement HelpableListener.</li> 021 * <li>They must use an instance of ListenerHelper to manage their list of listeners.</li> 022 * <li>At the start of any query method that depends on the listener's own event 023 * source they are to call {@link #needModelUpdate}</li> 024 * </ul> 025 * 026 * <p>As the Swing serialization is buggy, this class will serialize only listeners that implement 027 * {@link SerializableListener}. For this purpose, however, all methods in 028 * {@link javax.swing.event.EventListenerList} need to be overridden.</p> 029 * 030 * @see HelpableListener 031 * @see #needModelUpdate 032 * 033 * @author Steffen Zschaler 034 * @version 2.0 02/06/1999 035 * @since v2.0 036 */ 037 public class ListenerHelper extends javax.swing.event.EventListenerList { 038 039 /** 040 * ID for serialization. 041 */ 042 private static final long serialVersionUID = -7971346159857861210L; 043 044 /** 045 * A null array to be shared by all empty listener lists 046 */ 047 private final static Object[] NULL_ARRAY = new Object[0]; 048 049 /** 050 * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by 051 * the standard {@link javax.swing.event.EventListenerList} mechanism. 052 * 053 * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid 054 * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain 055 * referential integrity. They also store a pointer to their catalog in order to be able to reference 056 * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory 057 * problem. 058 */ 059 protected transient Object[] aoListeners = NULL_ARRAY; 060 061 /** 062 * The listener that owns this listener helper. 063 * 064 * @serial 065 */ 066 protected HelpableListener m_hlOwner; 067 068 /** 069 * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support, 070 * but not the subscribe/unsubscribe functionality. 071 */ 072 public ListenerHelper() { 073 this(null); 074 } 075 076 /** 077 * Create a new ListenerHelper. 078 * 079 * @param hlOwner the HelpableListener that is associated to this ListenerHelper. 080 */ 081 public ListenerHelper(HelpableListener hlOwner) { 082 super(); 083 084 m_hlOwner = hlOwner; 085 } 086 087 /** 088 * This passes back the event listener list as an array 089 * of ListenerType - listener pairs. Note that for 090 * performance reasons, this implementation passes back 091 * the actual data structure in which the listner data 092 * is stored internally! 093 * This method is guaranteed to pass back a non-null 094 * array, so that no null-checking is required in 095 * fire methods. A zero-length array of Object should 096 * be returned if there are currently no listeners. 097 * 098 * WARNING!!! Absolutely NO modification of 099 * the data contained in this array should be made -- if 100 * any such manipulation is necessary, it should be done 101 * on a copy of the array returned rather than the array 102 * itself. 103 */ 104 public Object[] getListenerList() { 105 return aoListeners; 106 } 107 108 /** 109 * Return the total number of listeners for this listenerlist 110 */ 111 public int getListenerCount() { 112 return aoListeners.length / 2; 113 } 114 115 /** 116 * Return the total number of listeners of the supplied type 117 * for this listenerlist. 118 */ 119 public int getListenerCount(Class t) { 120 int count = 0; 121 Object[] lList = aoListeners; 122 123 for (int i = 0; i < lList.length; i += 2) { 124 if (t == ((Class)lList[i])) { 125 count++; 126 } 127 } 128 129 return count; 130 } 131 132 /** 133 * Add the listener as a listener of the specified type. 134 * 135 * @param t the type of the listener to be added 136 * @param l the listener to be added 137 */ 138 public synchronized <T extends EventListener> void add(Class<T> t, T l) { 139 if (!t.isInstance(l)) { 140 throw new IllegalArgumentException("Listener " + l + " is not of type " + t); 141 } 142 143 if (l == null) { 144 throw new IllegalArgumentException("Listener " + l + " is null"); 145 } 146 147 if (aoListeners == NULL_ARRAY) { 148 // if this is the first listener added, 149 // initialize the lists 150 aoListeners = new Object[] { 151 t, l}; 152 153 // .. and have the owner subscribe. 154 if (m_hlOwner != null) { 155 m_hlOwner.subscribe(); 156 // a last time so that we start with the most accurate model possible 157 m_hlOwner.updateModel(); 158 } 159 } else { 160 // Otherwise copy the array and add the new listener 161 int i = aoListeners.length; 162 Object[] tmp = new Object[i + 2]; 163 System.arraycopy(aoListeners, 0, tmp, 0, i); 164 165 tmp[i] = t; 166 tmp[i + 1] = l; 167 168 aoListeners = tmp; 169 } 170 } 171 172 /** 173 * Remove the listener as a listener of the specified type. 174 * 175 * @param t the type of the listener to be removed 176 * @param l the listener to be removed 177 */ 178 public synchronized <T extends EventListener> void remove(Class<T> t, T l) { 179 if (!t.isInstance(l)) { 180 throw new IllegalArgumentException("Listener " + l + " is not of type " + t); 181 } 182 183 if (l == null) { 184 throw new IllegalArgumentException("Listener " + l + " is null"); 185 } 186 187 // Is l on the list? 188 int index = -1; 189 for (int i = aoListeners.length - 2; i >= 0; i -= 2) { 190 if ((aoListeners[i] == t) && (aoListeners[i + 1].equals(l))) { 191 index = i; 192 break; 193 } 194 } 195 196 // If so, remove it 197 if (index != -1) { 198 Object[] tmp = new Object[aoListeners.length - 2]; 199 // Copy the list up to index 200 System.arraycopy(aoListeners, 0, tmp, 0, index); 201 202 // Copy from two past the index, up to 203 // the end of tmp (which is two elements 204 // shorter than the old list) 205 if (index < tmp.length) { 206 System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index); 207 } 208 209 // set the listener array to the new array or null 210 aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp; 211 212 if (tmp.length == 0) { 213 if (m_hlOwner != null) { 214 m_hlOwner.unsubscribe(); 215 } 216 } 217 } 218 } 219 220 /** 221 * Make sure, the owner's model is up to date. If necessary call 222 * {@link util.HelpableListener#updateModel} in the owner. 223 */ 224 public void needModelUpdate() { 225 if (getListenerCount() == 0) { 226 227 if (m_hlOwner != null) { 228 m_hlOwner.updateModel(); 229 } 230 } 231 } 232 233 /** 234 * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. 235 * 236 * @serialData tuples of (<classname>, <listener>). 237 */ 238 private void writeObject(ObjectOutputStream s) throws IOException { 239 Object[] lList = aoListeners; 240 s.defaultWriteObject(); 241 242 // Save the non-null event listeners: 243 for (int i = 0; i < lList.length; i += 2) { 244 Class t = (Class)lList[i]; 245 EventListener l = (EventListener)lList[i + 1]; 246 247 if ((l != null) && (l instanceof SerializableListener)) { 248 s.writeObject(t.getName()); 249 s.writeObject(l); 250 } 251 } 252 253 s.writeObject(null); 254 } 255 256 /** 257 * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. 258 * 259 * <p><b>Attention:</b><br /> 260 * This code is not 100% Java 1.5 compatible. Had to avoid the warning with SuppressWarnings 261 * because there is no clean solution for this in 1.5 yet.</p> 262 */ 263 @SuppressWarnings("unchecked") 264 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 265 aoListeners = NULL_ARRAY; 266 267 s.defaultReadObject(); 268 Object listenerTypeOrNull; 269 270 while (null != (listenerTypeOrNull = s.readObject())) { 271 272 System.out.println(Class.forName((String)listenerTypeOrNull)); 273 EventListener l = (EventListener) s.readObject(); 274 add((Class<EventListener>) Class.forName((String)listenerTypeOrNull), l); 275 } 276 } 277 278 /** 279 * Return a string representation of the SerializableListenerHelper. 280 */ 281 public String toString() { 282 Object[] lList = aoListeners; 283 String s = "SerializableListenerHelper: "; 284 s += lList.length / 2 + " listeners: "; 285 for (int i = 0; i <= lList.length - 2; i += 2) { 286 s += " type " + ((Class)lList[i]).getName(); 287 s += " listener " + lList[i + 1]; 288 } 289 290 return s; 291 } 292 }