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 * A null array to be shared by all empty listener lists 041 */ 042 private final static Object[] NULL_ARRAY = new Object[0]; 043 044 /** 045 * The list of ListenerType - Listener pairs. We need to redefine this as it would otherwise be stored by 046 * the standard {@link javax.swing.event.EventListenerList} mechanism. 047 * 048 * PENDING (sz9): Need to replace direct references to listeners by SoftReferences, in order to avoid 049 * cycles and thereby memory leaks. Example: Stocks listen to their catalogs in order to maintain 050 * referential integrity. They also store a pointer to their catalog in order to be able to reference 051 * it. A cycle is created, which means that creating lots of temporary stocks is a potential memory 052 * problem. 053 */ 054 protected transient Object[] aoListeners = NULL_ARRAY; 055 056 /** 057 * The listener that owns this listener helper. 058 * 059 * @serial 060 */ 061 protected HelpableListener m_hlOwner; 062 063 /** 064 * Construct a new ListenerHelper. Using this constructor will only use the improved serialization support, 065 * but not the subscribe/unsubscribe functionality. 066 */ 067 public ListenerHelper() { 068 this(null); 069 } 070 071 /** 072 * Create a new ListenerHelper. 073 * 074 * @param hlOwner the HelpableListener that is associated to this ListenerHelper. 075 */ 076 public ListenerHelper(HelpableListener hlOwner) { 077 super(); 078 079 m_hlOwner = hlOwner; 080 } 081 082 /** 083 * This passes back the event listener list as an array 084 * of ListenerType - listener pairs. Note that for 085 * performance reasons, this implementation passes back 086 * the actual data structure in which the listner data 087 * is stored internally! 088 * This method is guaranteed to pass back a non-null 089 * array, so that no null-checking is required in 090 * fire methods. A zero-length array of Object should 091 * be returned if there are currently no listeners. 092 * 093 * WARNING!!! Absolutely NO modification of 094 * the data contained in this array should be made -- if 095 * any such manipulation is necessary, it should be done 096 * on a copy of the array returned rather than the array 097 * itself. 098 */ 099 public Object[] getListenerList() { 100 return aoListeners; 101 } 102 103 /** 104 * Return the total number of listeners for this listenerlist 105 */ 106 public int getListenerCount() { 107 return aoListeners.length / 2; 108 } 109 110 /** 111 * Return the total number of listeners of the supplied type 112 * for this listenerlist. 113 */ 114 public int getListenerCount(Class t) { 115 int count = 0; 116 Object[] lList = aoListeners; 117 118 for (int i = 0; i < lList.length; i += 2) { 119 if (t == ((Class)lList[i])) { 120 count++; 121 } 122 } 123 124 return count; 125 } 126 127 /** 128 * Add the listener as a listener of the specified type. 129 * 130 * @param t the type of the listener to be added 131 * @param l the listener to be added 132 */ 133 public synchronized void add(Class t, EventListener l) { 134 if (!t.isInstance(l)) { 135 throw new IllegalArgumentException("Listener " + l + " is not of type " + t); 136 } 137 138 if (l == null) { 139 throw new IllegalArgumentException("Listener " + l + " is null"); 140 } 141 142 if (aoListeners == NULL_ARRAY) { 143 // if this is the first listener added, 144 // initialize the lists 145 aoListeners = new Object[] { 146 t, l}; 147 148 // .. and have the owner subscribe. 149 if (m_hlOwner != null) { 150 m_hlOwner.subscribe(); 151 // a last time so that we start with the most accurate model possible 152 m_hlOwner.updateModel(); 153 } 154 } else { 155 // Otherwise copy the array and add the new listener 156 int i = aoListeners.length; 157 Object[] tmp = new Object[i + 2]; 158 System.arraycopy(aoListeners, 0, tmp, 0, i); 159 160 tmp[i] = t; 161 tmp[i + 1] = l; 162 163 aoListeners = tmp; 164 } 165 } 166 167 /** 168 * Remove the listener as a listener of the specified type. 169 * 170 * @param t the type of the listener to be removed 171 * @param l the listener to be removed 172 */ 173 public synchronized void remove(Class t, EventListener l) { 174 if (!t.isInstance(l)) { 175 throw new IllegalArgumentException("Listener " + l + " is not of type " + t); 176 } 177 178 if (l == null) { 179 throw new IllegalArgumentException("Listener " + l + " is null"); 180 } 181 182 // Is l on the list? 183 int index = -1; 184 for (int i = aoListeners.length - 2; i >= 0; i -= 2) { 185 if ((aoListeners[i] == t) && (aoListeners[i + 1].equals(l))) { 186 index = i; 187 break; 188 } 189 } 190 191 // If so, remove it 192 if (index != -1) { 193 Object[] tmp = new Object[aoListeners.length - 2]; 194 // Copy the list up to index 195 System.arraycopy(aoListeners, 0, tmp, 0, index); 196 197 // Copy from two past the index, up to 198 // the end of tmp (which is two elements 199 // shorter than the old list) 200 if (index < tmp.length) { 201 System.arraycopy(aoListeners, index + 2, tmp, index, tmp.length - index); 202 } 203 204 // set the listener array to the new array or null 205 aoListeners = (tmp.length == 0) ? NULL_ARRAY : tmp; 206 207 if (tmp.length == 0) { 208 if (m_hlOwner != null) { 209 m_hlOwner.unsubscribe(); 210 } 211 } 212 } 213 } 214 215 /** 216 * Make sure, the owner's model is up to date. If necessary call 217 * {@link util.HelpableListener#updateModel} in the owner. 218 */ 219 public void needModelUpdate() { 220 if (getListenerCount() == 0) { 221 222 if (m_hlOwner != null) { 223 m_hlOwner.updateModel(); 224 } 225 } 226 } 227 228 /** 229 * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. 230 * 231 * @serialData tuples of (<classname>, <listener>). 232 */ 233 private void writeObject(ObjectOutputStream s) throws IOException { 234 Object[] lList = aoListeners; 235 s.defaultWriteObject(); 236 237 // Save the non-null event listeners: 238 for (int i = 0; i < lList.length; i += 2) { 239 Class t = (Class)lList[i]; 240 EventListener l = (EventListener)lList[i + 1]; 241 242 if ((l != null) && (l instanceof SerializableListener)) { 243 s.writeObject(t.getName()); 244 s.writeObject(l); 245 } 246 } 247 248 s.writeObject(null); 249 } 250 251 /** 252 * A SerializableListenerHelper will store all the listeners that implement {@link SerializableListener}. 253 */ 254 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { 255 256 aoListeners = NULL_ARRAY; 257 258 s.defaultReadObject(); 259 Object listenerTypeOrNull; 260 261 while (null != (listenerTypeOrNull = s.readObject())) { 262 EventListener l = (EventListener)s.readObject(); 263 add(Class.forName((String)listenerTypeOrNull), l); 264 } 265 } 266 267 /** 268 * Return a string representation of the SerializableListenerHelper. 269 */ 270 public String toString() { 271 Object[] lList = aoListeners; 272 String s = "SerializableListenerHelper: "; 273 s += lList.length / 2 + " listeners: "; 274 for (int i = 0; i <= lList.length - 2; i += 2) { 275 s += " type " + ((Class)lList[i]).getName(); 276 s += " listener " + lList[i + 1]; 277 } 278 279 return s; 280 } 281 }