KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > util > WeakListenerImpl


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.util;
21
22 import java.awt.event.FocusEvent JavaDoc;
23 import java.awt.event.FocusListener JavaDoc;
24 import java.beans.PropertyChangeEvent JavaDoc;
25 import java.beans.PropertyChangeListener JavaDoc;
26 import java.beans.PropertyVetoException JavaDoc;
27 import java.beans.VetoableChangeListener JavaDoc;
28 import java.lang.ref.Reference JavaDoc;
29 import java.lang.ref.SoftReference JavaDoc;
30 import java.lang.ref.WeakReference JavaDoc;
31 import java.lang.reflect.Constructor JavaDoc;
32 import java.lang.reflect.InvocationHandler JavaDoc;
33 import java.lang.reflect.Method JavaDoc;
34 import java.lang.reflect.Modifier JavaDoc;
35 import java.lang.reflect.Proxy JavaDoc;
36 import java.util.EventListener JavaDoc;
37 import java.util.EventObject JavaDoc;
38 import java.util.logging.Level JavaDoc;
39 import java.util.logging.Logger JavaDoc;
40 import java.util.Map JavaDoc;
41 import java.util.WeakHashMap JavaDoc;
42 import javax.swing.event.ChangeEvent JavaDoc;
43 import javax.swing.event.ChangeListener JavaDoc;
44 import javax.swing.event.DocumentEvent JavaDoc;
45 import javax.swing.event.DocumentListener JavaDoc;
46
47 /**
48  * A listener wrapper that delegates to another listener but hold
49  * only weak reference to it, so it does not prevent it to be finalized.
50  *
51  * @author Jaroslav Tulach
52  */

53 abstract class WeakListenerImpl implements java.util.EventListener JavaDoc {
54     /** weak reference to listener */
55     private ListenerReference ref;
56
57     /** class of the listener */
58     Class JavaDoc listenerClass;
59
60     /** weak reference to source */
61     private Reference JavaDoc<Object JavaDoc> source;
62
63     /**
64      * @param listenerClass class/interface of the listener
65      * @param l listener to delegate to, <code>l</code> must be an instance of
66      * listenerClass
67      */

68     protected WeakListenerImpl(Class JavaDoc listenerClass, java.util.EventListener JavaDoc l) {
69         this.listenerClass = listenerClass;
70         ref = new ListenerReference(l, this);
71     }
72
73     /** Setter for the source field. If a WeakReference to an underlying listener is
74      * cleared and enqueued, that is, the original listener is garbage collected,
75      * then the source field is used for deregistration of this WeakListenerImpl, thus making
76      * it eligible for garbage collection if no more references exist.
77      *
78      * This method is particularly useful in cases where the underlying listener was
79      * garbage collected and the event source, on which this listener is listening on,
80      * is quiet, i.e. does not fire any events for long periods. In this case, this listener
81      * is not removed from the event source until an event is fired. If the source field is
82      * set however, WeakListenerImpls that lost their underlying listeners are removed
83      * as soon as the ReferenceQueue notifies the WeakListenerImpl.
84      *
85      * @param source is any Object or <code>null</code>, though only setting an object
86      * that has an appropriate remove*listenerClass*Listener method and on which this listener is listening on,
87      * is useful.
88      */

89     protected final void setSource(Object JavaDoc source) {
90         if (source == null) {
91             this.source = null;
92         } else {
93             this.source = new WeakReference JavaDoc<Object JavaDoc>(source);
94         }
95     }
96
97     /** Method name to use for removing the listener.
98     * @return name of method of the source object that should be used
99     * to remove the listener from listening on source of events
100     */

101     protected abstract String JavaDoc removeMethodName();
102
103     /** Getter for the target listener.
104     * @param ev the event the we want to distribute
105     * @return null if there is no listener because it has been finalized
106     */

107     protected final java.util.EventListener JavaDoc get(java.util.EventObject JavaDoc ev) {
108         Object JavaDoc l = ref.get(); // get the consumer
109

110         // if the event consumer is gone, unregister us from the event producer
111
if (l == null) {
112             ref.requestCleanUp((ev == null) ? null : ev.getSource());
113         }
114
115         return (EventListener JavaDoc) l;
116     }
117
118     Object JavaDoc getImplementator() {
119         return this;
120     }
121
122     public String JavaDoc toString() {
123         Object JavaDoc listener = ref.get();
124
125         return getClass().getName() + "[" + ((listener == null) ? "null" : (listener.getClass().getName() + "]"));
126     }
127
128     public static <T extends EventListener JavaDoc> T create(Class JavaDoc<T> lType, Class JavaDoc<? super T> apiType, T l, Object JavaDoc source) {
129         ProxyListener pl = new ProxyListener(lType, apiType, l);
130         pl.setSource(source);
131
132         return lType.cast(pl.proxy);
133     }
134
135     /** Weak property change listener
136     */

137     static class PropertyChange extends WeakListenerImpl implements PropertyChangeListener JavaDoc {
138         /** Constructor.
139         * @param l listener to delegate to
140         */

141         public PropertyChange(PropertyChangeListener JavaDoc l) {
142             super(PropertyChangeListener JavaDoc.class, l);
143         }
144
145         /** Constructor.
146         * @param clazz required class
147         * @param l listener to delegate to
148         */

149         PropertyChange(Class JavaDoc clazz, PropertyChangeListener JavaDoc l) {
150             super(clazz, l);
151         }
152
153         /** Tests if the object we reference to still exists and
154         * if so, delegate to it. Otherwise remove from the source
155         * if it has removePropertyChangeListener method.
156         */

157         public void propertyChange(PropertyChangeEvent JavaDoc ev) {
158             PropertyChangeListener JavaDoc l = (PropertyChangeListener JavaDoc) super.get(ev);
159
160             if (l != null) {
161                 l.propertyChange(ev);
162             }
163         }
164
165         /** Method name to use for removing the listener.
166         * @return name of method of the source object that should be used
167         * to remove the listener from listening on source of events
168         */

169         protected String JavaDoc removeMethodName() {
170             return "removePropertyChangeListener"; // NOI18N
171
}
172     }
173
174     /** Weak vetoable change listener
175     */

176     static class VetoableChange extends WeakListenerImpl implements VetoableChangeListener JavaDoc {
177         /** Constructor.
178         * @param l listener to delegate to
179         */

180         public VetoableChange(VetoableChangeListener JavaDoc l) {
181             super(VetoableChangeListener JavaDoc.class, l);
182         }
183
184         /** Tests if the object we reference to still exists and
185         * if so, delegate to it. Otherwise remove from the source
186         * if it has removePropertyChangeListener method.
187         */

188         public void vetoableChange(PropertyChangeEvent JavaDoc ev)
189         throws PropertyVetoException JavaDoc {
190             VetoableChangeListener JavaDoc l = (VetoableChangeListener JavaDoc) super.get(ev);
191
192             if (l != null) {
193                 l.vetoableChange(ev);
194             }
195         }
196
197         /** Method name to use for removing the listener.
198         * @return name of method of the source object that should be used
199         * to remove the listener from listening on source of events
200         */

201         protected String JavaDoc removeMethodName() {
202             return "removeVetoableChangeListener"; // NOI18N
203
}
204     }
205
206     /** Weak document modifications listener.
207     * This class if final only for performance reasons,
208     * can be happily unfinaled if desired.
209     */

210     static final class Document extends WeakListenerImpl implements DocumentListener JavaDoc {
211         /** Constructor.
212         * @param l listener to delegate to
213         */

214         public Document(final DocumentListener JavaDoc l) {
215             super(DocumentListener JavaDoc.class, l);
216         }
217
218         /** Gives notification that an attribute or set of attributes changed.
219         * @param ev event describing the action
220         */

221         public void changedUpdate(DocumentEvent JavaDoc ev) {
222             final DocumentListener JavaDoc l = docGet(ev);
223
224             if (l != null) {
225                 l.changedUpdate(ev);
226             }
227         }
228
229         /** Gives notification that there was an insert into the document.
230         * @param ev event describing the action
231         */

232         public void insertUpdate(DocumentEvent JavaDoc ev) {
233             final DocumentListener JavaDoc l = docGet(ev);
234
235             if (l != null) {
236                 l.insertUpdate(ev);
237             }
238         }
239
240         /** Gives notification that a portion of the document has been removed.
241         * @param ev event describing the action
242         */

243         public void removeUpdate(DocumentEvent JavaDoc ev) {
244             final DocumentListener JavaDoc l = docGet(ev);
245
246             if (l != null) {
247                 l.removeUpdate(ev);
248             }
249         }
250
251         /** Method name to use for removing the listener.
252         * @return name of method of the source object that should be used
253         * to remove the listener from listening on source of events
254         */

255         protected String JavaDoc removeMethodName() {
256             return "removeDocumentListener"; // NOI18N
257
}
258
259         /** Getter for the target listener.
260         * @param event the event the we want to distribute
261         * @return null if there is no listener because it has been finalized
262         */

263         private DocumentListener JavaDoc docGet(DocumentEvent JavaDoc ev) {
264             DocumentListener JavaDoc l = (DocumentListener JavaDoc) super.ref.get();
265
266             if (l == null) {
267                 super.ref.requestCleanUp(ev.getDocument());
268             }
269
270             return l;
271         }
272     }
273      // end of Document inner class
274

275     /** Weak swing change listener.
276     * This class if final only for performance reasons,
277     * can be happily unfinaled if desired.
278     */

279     static final class Change extends WeakListenerImpl implements ChangeListener JavaDoc {
280         /** Constructor.
281         * @param l listener to delegate to
282         */

283         public Change(ChangeListener JavaDoc l) {
284             super(ChangeListener JavaDoc.class, l);
285         }
286
287         /** Called when new file system is added to the pool.
288         * @param ev event describing the action
289         */

290         public void stateChanged(final ChangeEvent JavaDoc ev) {
291             ChangeListener JavaDoc l = (ChangeListener JavaDoc) super.get(ev);
292
293             if (l != null) {
294                 l.stateChanged(ev);
295             }
296         }
297
298         /** Method name to use for removing the listener.
299         * @return name of method of the source object that should be used
300         * to remove the listener from listening on source of events
301         */

302         protected String JavaDoc removeMethodName() {
303             return "removeChangeListener"; // NOI18N
304
}
305     }
306
307     /** Weak version of focus listener.
308     * This class if final only for performance reasons,
309     * can be happily unfinaled if desired.
310     */

311     static final class Focus extends WeakListenerImpl implements FocusListener JavaDoc {
312         /** Constructor.
313         * @param l listener to delegate to
314         */

315         public Focus(FocusListener JavaDoc l) {
316             super(FocusListener JavaDoc.class, l);
317         }
318
319         /** Delegates to the original listener.
320         */

321         public void focusGained(FocusEvent JavaDoc ev) {
322             FocusListener JavaDoc l = (FocusListener JavaDoc) super.get(ev);
323
324             if (l != null) {
325                 l.focusGained(ev);
326             }
327         }
328
329         /** Delegates to the original listener.
330         */

331         public void focusLost(FocusEvent JavaDoc ev) {
332             FocusListener JavaDoc l = (FocusListener JavaDoc) super.get(ev);
333
334             if (l != null) {
335                 l.focusLost(ev);
336             }
337         }
338
339         /** Method name to use for removing the listener.
340         * @return name of method of the source object that should be used
341         * to remove the listener from listening on source of events
342         */

343         protected String JavaDoc removeMethodName() {
344             return "removeFocusListener"; // NOI18N
345
}
346     }
347
348     /** Proxy interface that delegates to listeners.
349     */

350     private static class ProxyListener extends WeakListenerImpl implements InvocationHandler JavaDoc {
351         /** Equals method */
352         private static Method JavaDoc equalsMth;
353
354         /** Class -> Reference(Constructor) */
355         private static final Map JavaDoc<Class JavaDoc, Reference JavaDoc<Constructor JavaDoc>> constructors = new WeakHashMap JavaDoc<Class JavaDoc, Reference JavaDoc<Constructor JavaDoc>>();
356
357         /** proxy generated for this listener */
358         public final Object JavaDoc proxy;
359
360         /** @param listener listener to delegate to
361         */

362         public ProxyListener(Class JavaDoc c, Class JavaDoc api, java.util.EventListener JavaDoc listener) {
363             super(api, listener);
364
365             try {
366                 Reference JavaDoc ref = (Reference JavaDoc) constructors.get(c);
367                 Constructor JavaDoc proxyConstructor = (ref == null) ? null : (Constructor JavaDoc) ref.get();
368
369                 if (proxyConstructor == null) {
370                     Class JavaDoc<?> proxyClass = Proxy.getProxyClass(c.getClassLoader(), new Class JavaDoc[] { c });
371                     proxyConstructor = proxyClass.getConstructor(new Class JavaDoc[] { InvocationHandler JavaDoc.class });
372                     constructors.put(c, new SoftReference JavaDoc<Constructor JavaDoc>(proxyConstructor));
373                 }
374
375                 Object JavaDoc p;
376
377                 try {
378                     p = proxyConstructor.newInstance(new Object JavaDoc[] { this });
379                 } catch (java.lang.NoClassDefFoundError JavaDoc err) {
380                     // if for some reason the actual creation of the instance
381
// from constructor fails, try it once more using regular
382
// method, see issue 30449
383
p = Proxy.newProxyInstance(c.getClassLoader(), new Class JavaDoc[] { c }, this);
384                 }
385
386                 proxy = p;
387             } catch (Exception JavaDoc ex) {
388                 throw (IllegalStateException JavaDoc) new IllegalStateException JavaDoc(ex.toString()).initCause(ex);
389             }
390         }
391
392         /** */
393         private static Method JavaDoc getEquals() {
394             if (equalsMth == null) {
395                 try {
396                     equalsMth = Object JavaDoc.class.getMethod("equals", new Class JavaDoc[] { Object JavaDoc.class }); // NOI18N
397
} catch (NoSuchMethodException JavaDoc e) {
398                     e.printStackTrace();
399                 }
400             }
401
402             return equalsMth;
403         }
404
405         public java.lang.Object JavaDoc invoke(Object JavaDoc proxy, Method JavaDoc method, Object JavaDoc[] args)
406         throws Throwable JavaDoc {
407             if (method.getDeclaringClass() == Object JavaDoc.class) {
408                 // a method from object => call it on your self
409
if (method == getEquals()) {
410                     boolean ret = equals(args[0]);
411
412                     return (ret ? Boolean.TRUE : Boolean.FALSE);
413                 }
414
415                 return method.invoke(this, args);
416             }
417
418             // listeners method
419
EventObject JavaDoc ev = ((args != null) && (args[0] instanceof EventObject JavaDoc)) ? (EventObject JavaDoc) args[0] : null;
420
421             Object JavaDoc listener = super.get(ev);
422
423             if (listener != null) {
424                 return method.invoke(listener, args);
425             } else {
426                 return null;
427             }
428         }
429
430         /** Remove method name is composed from the name of the listener.
431         */

432         protected String JavaDoc removeMethodName() {
433             String JavaDoc name = listenerClass.getName();
434
435             // strip package name
436
int dot = name.lastIndexOf('.');
437             name = name.substring(dot + 1);
438
439             // in case of inner interfaces/classes we also strip the outer
440
// class' name
441
int i = name.lastIndexOf('$'); // NOI18N
442

443             if (i >= 0) {
444                 name = name.substring(i + 1);
445             }
446
447             return "remove".concat(name); // NOI18N
448
}
449
450         /** To string prints class.
451         */

452         public String JavaDoc toString() {
453             return super.toString() + "[" + listenerClass + "]"; // NOI18N
454
}
455
456         /** Equal is extended to equal also with proxy object.
457         */

458         public boolean equals(Object JavaDoc obj) {
459             return (proxy == obj) || (this == obj);
460         }
461
462         Object JavaDoc getImplementator() {
463             return proxy;
464         }
465     }
466
467     /** Reference that also holds ref to WeakListenerImpl.
468     */

469     private static final class ListenerReference extends WeakReference JavaDoc<Object JavaDoc> implements Runnable JavaDoc {
470         private static Class JavaDoc lastClass;
471         private static String JavaDoc lastMethodName;
472         private static Method JavaDoc lastRemove;
473         private static Object JavaDoc LOCK = new Object JavaDoc();
474         WeakListenerImpl weakListener;
475
476         public ListenerReference(Object JavaDoc ref, WeakListenerImpl weakListener) {
477             super(ref, Utilities.activeReferenceQueue());
478             this.weakListener = weakListener;
479         }
480
481         /** Requestes cleanup of the listener with a provided source.
482          * @param source source of the cleanup
483          */

484         public synchronized void requestCleanUp(Object JavaDoc source) {
485             if (weakListener == null) {
486                 // already being handled
487
return;
488             }
489
490             if (weakListener.source != source) {
491                 // plan new cleanup into the activeReferenceQueue with this listener and
492
// provided source
493
weakListener.source = new WeakReference JavaDoc<Object JavaDoc> (source) {
494                             ListenerReference doNotGCRef = new ListenerReference(new Object JavaDoc(), weakListener);
495                         };
496             }
497         }
498
499         public void run() {
500             // prepare array for passing arguments to getMethod/invoke
501
Object JavaDoc[] params = new Object JavaDoc[1];
502             Class JavaDoc[] types = new Class JavaDoc[1];
503             Object JavaDoc src = null; // On whom we're listening
504
Method JavaDoc remove = null;
505
506             WeakListenerImpl ref;
507
508             synchronized (this) {
509                 ref = weakListener;
510
511                 if ((ref.source == null) || ((src = ref.source.get()) == null)) {
512                     return;
513                 }
514
515                 // we are going to clean up the listener
516
weakListener = null;
517             }
518
519             Class JavaDoc methodClass;
520             if (src instanceof Class JavaDoc) {
521                 // Handle static listener methods sanely.
522
methodClass = (Class JavaDoc) src;
523             } else {
524                 methodClass = src.getClass();
525             }
526             String JavaDoc methodName = ref.removeMethodName();
527
528             synchronized (LOCK) {
529                 if ((lastClass == methodClass) && (lastMethodName == methodName) && (lastRemove != null)) {
530                     remove = lastRemove;
531                 }
532             }
533
534             // get the remove method or use the last one
535
if (remove == null) {
536                 types[0] = ref.listenerClass;
537                 remove = getRemoveMethod(methodClass, methodName, types[0]);
538
539                 if (remove == null) {
540                     Logger.getAnonymousLogger().warning(
541                         "Can't remove " + ref.listenerClass.getName() + //NOI18N
542
" using method " + methodName + //NOI18N
543
" from " + src
544                     ); //NOI18N
545

546                     return;
547                 } else {
548                     synchronized (LOCK) {
549                         lastClass = methodClass;
550                         lastMethodName = methodName;
551                         lastRemove = remove;
552                     }
553                 }
554             }
555
556             params[0] = ref.getImplementator(); // Whom to unregister
557

558             try {
559                 remove.invoke(src, params);
560             } catch (Exception JavaDoc ex) { // from invoke(), should not happen
561
Logger.getAnonymousLogger().warning(
562                     "Problem encountered while calling " + methodClass + "." + methodName + "(...) on " + src
563                 ); // NOI18N
564
Logger.getAnonymousLogger().log(Level.WARNING, null, ex);
565             }
566         }
567
568         /* can return null */
569         private Method JavaDoc getRemoveMethod(Class JavaDoc<?> methodClass, String JavaDoc methodName, Class JavaDoc listenerClass) {
570             final Class JavaDoc<?>[] clarray = new Class JavaDoc<?>[] { listenerClass };
571             Method JavaDoc m = null;
572
573             try {
574                 m = methodClass.getMethod(methodName, clarray);
575             } catch (NoSuchMethodException JavaDoc e) {
576                 do {
577                     try {
578                         m = methodClass.getDeclaredMethod(methodName, clarray);
579                     } catch (NoSuchMethodException JavaDoc ex) {
580                     }
581
582                     methodClass = methodClass.getSuperclass();
583                 } while ((m == null) && (methodClass != Object JavaDoc.class));
584             }
585
586             if (
587                 (m != null) &&
588                     (!Modifier.isPublic(m.getModifiers()) || !Modifier.isPublic(m.getDeclaringClass().getModifiers()))
589             ) {
590                 m.setAccessible(true);
591             }
592
593             return m;
594         }
595     }
596 }
597
Popular Tags