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 }