~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to openide/util/src/org/openide/util/WeakListenerImpl.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.openide.util;
 
43
 
 
44
import java.awt.event.FocusEvent;
 
45
import java.awt.event.FocusListener;
 
46
import java.beans.PropertyChangeEvent;
 
47
import java.beans.PropertyChangeListener;
 
48
import java.beans.PropertyVetoException;
 
49
import java.beans.VetoableChangeListener;
 
50
import java.lang.ref.Reference;
 
51
import java.lang.ref.SoftReference;
 
52
import java.lang.ref.WeakReference;
 
53
import java.lang.reflect.Constructor;
 
54
import java.lang.reflect.InvocationHandler;
 
55
import java.lang.reflect.Method;
 
56
import java.lang.reflect.Modifier;
 
57
import java.lang.reflect.Proxy;
 
58
import java.util.EventListener;
 
59
import java.util.EventObject;
 
60
import java.util.logging.Level;
 
61
import java.util.logging.Logger;
 
62
import java.util.Map;
 
63
import java.util.WeakHashMap;
 
64
import javax.swing.event.ChangeEvent;
 
65
import javax.swing.event.ChangeListener;
 
66
import javax.swing.event.DocumentEvent;
 
67
import javax.swing.event.DocumentListener;
 
68
 
 
69
/**
 
70
 * A listener wrapper that delegates to another listener but hold
 
71
 * only weak reference to it, so it does not prevent it to be finalized.
 
72
 *
 
73
 * @author Jaroslav Tulach
 
74
 */
 
75
abstract class WeakListenerImpl implements java.util.EventListener {
 
76
 
 
77
    private static final Logger LOG = Logger.getLogger(WeakListenerImpl.class.getName());
 
78
 
 
79
    /** weak reference to listener */
 
80
    private ListenerReference ref;
 
81
 
 
82
    /** class of the listener */
 
83
    Class listenerClass;
 
84
 
 
85
    /** weak reference to source */
 
86
    private Reference<Object> source;
 
87
 
 
88
    /**
 
89
     * @param listenerClass class/interface of the listener
 
90
     * @param l listener to delegate to, <code>l</code> must be an instance of
 
91
     * listenerClass
 
92
     */
 
93
    protected WeakListenerImpl(Class listenerClass, java.util.EventListener l) {
 
94
        this.listenerClass = listenerClass;
 
95
        ref = new ListenerReference(l, this);
 
96
    }
 
97
 
 
98
    /** Setter for the source field. If a WeakReference to an underlying listener is
 
99
     * cleared and enqueued, that is, the original listener is garbage collected,
 
100
     * then the source field is used for deregistration of this WeakListenerImpl, thus making
 
101
     * it eligible for garbage collection if no more references exist.
 
102
     *
 
103
     * This method is particularly useful in cases where the underlying listener was
 
104
     * garbage collected and the event source, on which this listener is listening on,
 
105
     * is quiet, i.e. does not fire any events for long periods. In this case, this listener
 
106
     * is not removed from the event source until an event is fired. If the source field is
 
107
     * set however, WeakListenerImpls that lost their underlying listeners are removed
 
108
     * as soon as the ReferenceQueue notifies the WeakListenerImpl.
 
109
     *
 
110
     * @param source is any Object or <code>null</code>, though only setting an object
 
111
     * that has an appropriate remove*listenerClass*Listener method and on which this listener is listening on,
 
112
     * is useful.
 
113
     */
 
114
    protected final void setSource(Object source) {
 
115
        if (source == null) {
 
116
            this.source = null;
 
117
        } else {
 
118
            this.source = new WeakReference<Object>(source);
 
119
        }
 
120
    }
 
121
 
 
122
    /** Method name to use for removing the listener.
 
123
    * @return name of method of the source object that should be used
 
124
    *   to remove the listener from listening on source of events
 
125
    */
 
126
    protected abstract String removeMethodName();
 
127
 
 
128
    /** Getter for the target listener.
 
129
    * @param ev the event the we want to distribute
 
130
    * @return null if there is no listener because it has been finalized
 
131
    */
 
132
    protected final java.util.EventListener get(java.util.EventObject ev) {
 
133
        Object l = ref.get(); // get the consumer
 
134
 
 
135
        // if the event consumer is gone, unregister us from the event producer
 
136
        if (l == null) {
 
137
            ref.requestCleanUp((ev == null) ? null : ev.getSource());
 
138
        }
 
139
 
 
140
        return (EventListener) l;
 
141
    }
 
142
 
 
143
    Object getImplementator() {
 
144
        return this;
 
145
    }
 
146
 
 
147
    public String toString() {
 
148
        Object listener = ref.get();
 
149
 
 
150
        return getClass().getName() + "[" + ((listener == null) ? "null" : (listener.getClass().getName() + "]"));
 
151
    }
 
152
 
 
153
    public static <T extends EventListener> T create(Class<T> lType, Class<? super T> apiType, T l, Object source) {
 
154
        ProxyListener pl = new ProxyListener(lType, apiType, l);
 
155
        pl.setSource(source);
 
156
 
 
157
        return lType.cast(pl.proxy);
 
158
    }
 
159
 
 
160
    /** Weak property change listener
 
161
    */
 
162
    static class PropertyChange extends WeakListenerImpl implements PropertyChangeListener {
 
163
        /** Constructor.
 
164
        * @param l listener to delegate to
 
165
        */
 
166
        public PropertyChange(PropertyChangeListener l) {
 
167
            super(PropertyChangeListener.class, l);
 
168
        }
 
169
 
 
170
        /** Constructor.
 
171
        * @param clazz required class
 
172
        * @param l listener to delegate to
 
173
        */
 
174
        PropertyChange(Class clazz, PropertyChangeListener l) {
 
175
            super(clazz, l);
 
176
        }
 
177
 
 
178
        /** Tests if the object we reference to still exists and
 
179
        * if so, delegate to it. Otherwise remove from the source
 
180
        * if it has removePropertyChangeListener method.
 
181
        */
 
182
        public void propertyChange(PropertyChangeEvent ev) {
 
183
            PropertyChangeListener l = (PropertyChangeListener) super.get(ev);
 
184
 
 
185
            if (l != null) {
 
186
                l.propertyChange(ev);
 
187
            }
 
188
        }
 
189
 
 
190
        /** Method name to use for removing the listener.
 
191
        * @return name of method of the source object that should be used
 
192
        *   to remove the listener from listening on source of events
 
193
        */
 
194
        protected String removeMethodName() {
 
195
            return "removePropertyChangeListener"; // NOI18N
 
196
        }
 
197
    }
 
198
 
 
199
    /** Weak vetoable change listener
 
200
    */
 
201
    static class VetoableChange extends WeakListenerImpl implements VetoableChangeListener {
 
202
        /** Constructor.
 
203
        * @param l listener to delegate to
 
204
        */
 
205
        public VetoableChange(VetoableChangeListener l) {
 
206
            super(VetoableChangeListener.class, l);
 
207
        }
 
208
 
 
209
        /** Tests if the object we reference to still exists and
 
210
        * if so, delegate to it. Otherwise remove from the source
 
211
        * if it has removePropertyChangeListener method.
 
212
        */
 
213
        public void vetoableChange(PropertyChangeEvent ev)
 
214
        throws PropertyVetoException {
 
215
            VetoableChangeListener l = (VetoableChangeListener) super.get(ev);
 
216
 
 
217
            if (l != null) {
 
218
                l.vetoableChange(ev);
 
219
            }
 
220
        }
 
221
 
 
222
        /** Method name to use for removing the listener.
 
223
        * @return name of method of the source object that should be used
 
224
        *   to remove the listener from listening on source of events
 
225
        */
 
226
        protected String removeMethodName() {
 
227
            return "removeVetoableChangeListener"; // NOI18N
 
228
        }
 
229
    }
 
230
 
 
231
    /** Weak document modifications listener.
 
232
    * This class if final only for performance reasons,
 
233
    * can be happily unfinaled if desired.
 
234
    */
 
235
    static final class Document extends WeakListenerImpl implements DocumentListener {
 
236
        /** Constructor.
 
237
        * @param l listener to delegate to
 
238
        */
 
239
        public Document(final DocumentListener l) {
 
240
            super(DocumentListener.class, l);
 
241
        }
 
242
 
 
243
        /** Gives notification that an attribute or set of attributes changed.
 
244
        * @param ev event describing the action
 
245
        */
 
246
        public void changedUpdate(DocumentEvent ev) {
 
247
            final DocumentListener l = docGet(ev);
 
248
 
 
249
            if (l != null) {
 
250
                l.changedUpdate(ev);
 
251
            }
 
252
        }
 
253
 
 
254
        /** Gives notification that there was an insert into the document.
 
255
        * @param ev event describing the action
 
256
        */
 
257
        public void insertUpdate(DocumentEvent ev) {
 
258
            final DocumentListener l = docGet(ev);
 
259
 
 
260
            if (l != null) {
 
261
                l.insertUpdate(ev);
 
262
            }
 
263
        }
 
264
 
 
265
        /** Gives notification that a portion of the document has been removed.
 
266
        * @param ev event describing the action
 
267
        */
 
268
        public void removeUpdate(DocumentEvent ev) {
 
269
            final DocumentListener l = docGet(ev);
 
270
 
 
271
            if (l != null) {
 
272
                l.removeUpdate(ev);
 
273
            }
 
274
        }
 
275
 
 
276
        /** Method name to use for removing the listener.
 
277
        * @return name of method of the source object that should be used
 
278
        *   to remove the listener from listening on source of events
 
279
        */
 
280
        protected String removeMethodName() {
 
281
            return "removeDocumentListener"; // NOI18N
 
282
        }
 
283
 
 
284
        /** Getter for the target listener.
 
285
        * @param event the event the we want to distribute
 
286
        * @return null if there is no listener because it has been finalized
 
287
        */
 
288
        private DocumentListener docGet(DocumentEvent ev) {
 
289
            DocumentListener l = (DocumentListener) super.ref.get();
 
290
 
 
291
            if (l == null) {
 
292
                super.ref.requestCleanUp(ev.getDocument());
 
293
            }
 
294
 
 
295
            return l;
 
296
        }
 
297
    }
 
298
     // end of Document inner class
 
299
 
 
300
    /** Weak swing change listener.
 
301
    * This class if final only for performance reasons,
 
302
    * can be happily unfinaled if desired.
 
303
    */
 
304
    static final class Change extends WeakListenerImpl implements ChangeListener {
 
305
        /** Constructor.
 
306
        * @param l listener to delegate to
 
307
        */
 
308
        public Change(ChangeListener l) {
 
309
            super(ChangeListener.class, l);
 
310
        }
 
311
 
 
312
        /** Called when new file system is added to the pool.
 
313
        * @param ev event describing the action
 
314
        */
 
315
        public void stateChanged(final ChangeEvent ev) {
 
316
            ChangeListener l = (ChangeListener) super.get(ev);
 
317
 
 
318
            if (l != null) {
 
319
                l.stateChanged(ev);
 
320
            }
 
321
        }
 
322
 
 
323
        /** Method name to use for removing the listener.
 
324
        * @return name of method of the source object that should be used
 
325
        *   to remove the listener from listening on source of events
 
326
        */
 
327
        protected String removeMethodName() {
 
328
            return "removeChangeListener"; // NOI18N
 
329
        }
 
330
    }
 
331
 
 
332
    /** Weak version of focus listener.
 
333
    * This class if final only for performance reasons,
 
334
    * can be happily unfinaled if desired.
 
335
    */
 
336
    static final class Focus extends WeakListenerImpl implements FocusListener {
 
337
        /** Constructor.
 
338
        * @param l listener to delegate to
 
339
        */
 
340
        public Focus(FocusListener l) {
 
341
            super(FocusListener.class, l);
 
342
        }
 
343
 
 
344
        /** Delegates to the original listener.
 
345
        */
 
346
        public void focusGained(FocusEvent ev) {
 
347
            FocusListener l = (FocusListener) super.get(ev);
 
348
 
 
349
            if (l != null) {
 
350
                l.focusGained(ev);
 
351
            }
 
352
        }
 
353
 
 
354
        /** Delegates to the original listener.
 
355
        */
 
356
        public void focusLost(FocusEvent ev) {
 
357
            FocusListener l = (FocusListener) super.get(ev);
 
358
 
 
359
            if (l != null) {
 
360
                l.focusLost(ev);
 
361
            }
 
362
        }
 
363
 
 
364
        /** Method name to use for removing the listener.
 
365
        * @return name of method of the source object that should be used
 
366
        *   to remove the listener from listening on source of events
 
367
        */
 
368
        protected String removeMethodName() {
 
369
            return "removeFocusListener"; // NOI18N
 
370
        }
 
371
    }
 
372
 
 
373
    /** Proxy interface that delegates to listeners.
 
374
    */
 
375
    private static class ProxyListener extends WeakListenerImpl implements InvocationHandler {
 
376
        /** Equals method */
 
377
        private static Method equalsMth;
 
378
 
 
379
        /** Class -> Reference(Constructor) */
 
380
        private static final Map<Class, Reference<Constructor>> constructors = new WeakHashMap<Class, Reference<Constructor>>();
 
381
 
 
382
        /** proxy generated for this listener */
 
383
        public final Object proxy;
 
384
 
 
385
        /** @param listener listener to delegate to
 
386
        */
 
387
        public ProxyListener(Class c, Class api, java.util.EventListener listener) {
 
388
            super(api, listener);
 
389
 
 
390
            try {
 
391
                Reference ref = (Reference) constructors.get(c);
 
392
                Constructor proxyConstructor = (ref == null) ? null : (Constructor) ref.get();
 
393
 
 
394
                if (proxyConstructor == null) {
 
395
                    Class<?> proxyClass = Proxy.getProxyClass(c.getClassLoader(), new Class[] { c });
 
396
                    proxyConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
 
397
                    constructors.put(c, new SoftReference<Constructor>(proxyConstructor));
 
398
                }
 
399
 
 
400
                Object p;
 
401
 
 
402
                try {
 
403
                    p = proxyConstructor.newInstance(new Object[] { this });
 
404
                } catch (java.lang.NoClassDefFoundError err) {
 
405
                    // if for some reason the actual creation of the instance
 
406
                    // from constructor fails, try it once more using regular
 
407
                    // method, see issue 30449
 
408
                    p = Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, this);
 
409
                }
 
410
 
 
411
                proxy = p;
 
412
            } catch (Exception ex) {
 
413
                throw (IllegalStateException) new IllegalStateException(ex.toString()).initCause(ex);
 
414
            }
 
415
        }
 
416
 
 
417
        /** */
 
418
        private static Method getEquals() {
 
419
            if (equalsMth == null) {
 
420
                try {
 
421
                    equalsMth = Object.class.getMethod("equals", new Class[] { Object.class }); // NOI18N
 
422
                } catch (NoSuchMethodException e) {
 
423
                    e.printStackTrace();
 
424
                }
 
425
            }
 
426
 
 
427
            return equalsMth;
 
428
        }
 
429
 
 
430
        public java.lang.Object invoke(Object proxy, Method method, Object[] args)
 
431
        throws Throwable {
 
432
            if (method.getDeclaringClass() == Object.class) {
 
433
                // a method from object => call it on your self
 
434
                if (method == getEquals()) {
 
435
                    boolean ret = equals(args[0]);
 
436
 
 
437
                    return (ret ? Boolean.TRUE : Boolean.FALSE);
 
438
                }
 
439
 
 
440
                return method.invoke(this, args);
 
441
            }
 
442
 
 
443
            // listeners method
 
444
            EventObject ev = ((args != null) && (args[0] instanceof EventObject)) ? (EventObject) args[0] : null;
 
445
 
 
446
            Object listener = super.get(ev);
 
447
 
 
448
            if (listener != null) {
 
449
                return method.invoke(listener, args);
 
450
            } else {
 
451
                return null;
 
452
            }
 
453
        }
 
454
 
 
455
        /** Remove method name is composed from the name of the listener.
 
456
        */
 
457
        protected String removeMethodName() {
 
458
            String name = listenerClass.getName();
 
459
 
 
460
            // strip package name
 
461
            int dot = name.lastIndexOf('.');
 
462
            name = name.substring(dot + 1);
 
463
 
 
464
            // in case of inner interfaces/classes we also strip the outer
 
465
            // class' name
 
466
            int i = name.lastIndexOf('$'); // NOI18N
 
467
 
 
468
            if (i >= 0) {
 
469
                name = name.substring(i + 1);
 
470
            }
 
471
 
 
472
            return "remove".concat(name); // NOI18N
 
473
        }
 
474
 
 
475
        /** To string prints class.
 
476
        */
 
477
        public String toString() {
 
478
            return super.toString() + "[" + listenerClass + "]"; // NOI18N
 
479
        }
 
480
 
 
481
        /** Equal is extended to equal also with proxy object.
 
482
        */
 
483
        public boolean equals(Object obj) {
 
484
            return (proxy == obj) || (this == obj);
 
485
        }
 
486
 
 
487
        Object getImplementator() {
 
488
            return proxy;
 
489
        }
 
490
    }
 
491
 
 
492
    /** Reference that also holds ref to WeakListenerImpl.
 
493
    */
 
494
    private static final class ListenerReference extends WeakReference<Object> implements Runnable {
 
495
        private static Class lastClass;
 
496
        private static String lastMethodName;
 
497
        private static Method lastRemove;
 
498
        private static Object LOCK = new Object();
 
499
        WeakListenerImpl weakListener;
 
500
 
 
501
        public ListenerReference(Object ref, WeakListenerImpl weakListener) {
 
502
            super(ref, Utilities.activeReferenceQueue());
 
503
            this.weakListener = weakListener;
 
504
        }
 
505
 
 
506
        /** Requestes cleanup of the listener with a provided source.
 
507
         * @param source source of the cleanup
 
508
         */
 
509
        public synchronized void requestCleanUp(Object source) {
 
510
            if (weakListener == null) {
 
511
                // already being handled
 
512
                return;
 
513
            }
 
514
 
 
515
            if (weakListener.source != source) {
 
516
                // plan new cleanup into the activeReferenceQueue with this listener and 
 
517
                // provided source
 
518
                weakListener.source = new WeakReference<Object> (source) {
 
519
                            ListenerReference doNotGCRef = new ListenerReference(new Object(), weakListener);
 
520
                        };
 
521
            }
 
522
        }
 
523
 
 
524
        public void run() {
 
525
            // prepare array for passing arguments to getMethod/invoke
 
526
            Object[] params = new Object[1];
 
527
            Class[] types = new Class[1];
 
528
            Object src = null; // On whom we're listening
 
529
            Method remove = null;
 
530
 
 
531
            WeakListenerImpl ref;
 
532
 
 
533
            synchronized (this) {
 
534
                ref = weakListener;
 
535
 
 
536
                if ((ref.source == null) || ((src = ref.source.get()) == null)) {
 
537
                    return;
 
538
                }
 
539
 
 
540
                // we are going to clean up the listener
 
541
                weakListener = null;
 
542
            }
 
543
 
 
544
            Class methodClass;
 
545
            if (src instanceof Class) {
 
546
                // Handle static listener methods sanely.
 
547
                methodClass = (Class) src;
 
548
            } else {
 
549
                methodClass = src.getClass();
 
550
            }
 
551
            String methodName = ref.removeMethodName();
 
552
 
 
553
            synchronized (LOCK) {
 
554
                if ((lastClass == methodClass) && (lastMethodName == methodName) && (lastRemove != null)) {
 
555
                    remove = lastRemove;
 
556
                }
 
557
            }
 
558
 
 
559
            // get the remove method or use the last one
 
560
            if (remove == null) {
 
561
                types[0] = ref.listenerClass;
 
562
                remove = getRemoveMethod(methodClass, methodName, types[0]);
 
563
 
 
564
                if (remove == null) {
 
565
                    LOG.warning(
 
566
                        "Can't remove " + ref.listenerClass.getName() + //NOI18N
 
567
                        " using method " + methodName + //NOI18N
 
568
                        " from " + src
 
569
                    ); //NOI18N
 
570
 
 
571
                    return;
 
572
                } else {
 
573
                    synchronized (LOCK) {
 
574
                        lastClass = methodClass;
 
575
                        lastMethodName = methodName;
 
576
                        lastRemove = remove;
 
577
                    }
 
578
                }
 
579
            }
 
580
 
 
581
            params[0] = ref.getImplementator(); // Whom to unregister
 
582
 
 
583
            try {
 
584
                remove.invoke(src, params);
 
585
            } catch (Exception ex) { // from invoke(), should not happen
 
586
                LOG.warning(
 
587
                    "Problem encountered while calling " + methodClass + "." + methodName + "(...) on " + src
 
588
                ); // NOI18N
 
589
                LOG.log(Level.WARNING, null, ex);
 
590
            }
 
591
        }
 
592
 
 
593
        /* can return null */
 
594
        private Method getRemoveMethod(Class<?> methodClass, String methodName, Class listenerClass) {
 
595
            final Class<?>[] clarray = new Class<?>[] { listenerClass };
 
596
            Method m = null;
 
597
 
 
598
            try {
 
599
                m = methodClass.getMethod(methodName, clarray);
 
600
            } catch (NoSuchMethodException e) {
 
601
                do {
 
602
                    try {
 
603
                        m = methodClass.getDeclaredMethod(methodName, clarray);
 
604
                    } catch (NoSuchMethodException ex) {
 
605
                    }
 
606
 
 
607
                    methodClass = methodClass.getSuperclass();
 
608
                } while ((m == null) && (methodClass != Object.class));
 
609
            }
 
610
 
 
611
            if (
 
612
                (m != null) &&
 
613
                    (!Modifier.isPublic(m.getModifiers()) || !Modifier.isPublic(m.getDeclaringClass().getModifiers()))
 
614
            ) {
 
615
                m.setAccessible(true);
 
616
            }
 
617
 
 
618
            return m;
 
619
        }
 
620
    }
 
621
}