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 }