2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
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.
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.
42
package org.openide.util;
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;
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;
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.
73
* @author Jaroslav Tulach
75
abstract class WeakListenerImpl implements java.util.EventListener {
77
private static final Logger LOG = Logger.getLogger(WeakListenerImpl.class.getName());
79
/** weak reference to listener */
80
private ListenerReference ref;
82
/** class of the listener */
85
/** weak reference to source */
86
private Reference<Object> source;
89
* @param listenerClass class/interface of the listener
90
* @param l listener to delegate to, <code>l</code> must be an instance of
93
protected WeakListenerImpl(Class listenerClass, java.util.EventListener l) {
94
this.listenerClass = listenerClass;
95
ref = new ListenerReference(l, this);
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.
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.
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,
114
protected final void setSource(Object source) {
115
if (source == null) {
118
this.source = new WeakReference<Object>(source);
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
126
protected abstract String removeMethodName();
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
132
protected final java.util.EventListener get(java.util.EventObject ev) {
133
Object l = ref.get(); // get the consumer
135
// if the event consumer is gone, unregister us from the event producer
137
ref.requestCleanUp((ev == null) ? null : ev.getSource());
140
return (EventListener) l;
143
Object getImplementator() {
147
public String toString() {
148
Object listener = ref.get();
150
return getClass().getName() + "[" + ((listener == null) ? "null" : (listener.getClass().getName() + "]"));
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);
157
return lType.cast(pl.proxy);
160
/** Weak property change listener
162
static class PropertyChange extends WeakListenerImpl implements PropertyChangeListener {
164
* @param l listener to delegate to
166
public PropertyChange(PropertyChangeListener l) {
167
super(PropertyChangeListener.class, l);
171
* @param clazz required class
172
* @param l listener to delegate to
174
PropertyChange(Class clazz, PropertyChangeListener l) {
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.
182
public void propertyChange(PropertyChangeEvent ev) {
183
PropertyChangeListener l = (PropertyChangeListener) super.get(ev);
186
l.propertyChange(ev);
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
194
protected String removeMethodName() {
195
return "removePropertyChangeListener"; // NOI18N
199
/** Weak vetoable change listener
201
static class VetoableChange extends WeakListenerImpl implements VetoableChangeListener {
203
* @param l listener to delegate to
205
public VetoableChange(VetoableChangeListener l) {
206
super(VetoableChangeListener.class, l);
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.
213
public void vetoableChange(PropertyChangeEvent ev)
214
throws PropertyVetoException {
215
VetoableChangeListener l = (VetoableChangeListener) super.get(ev);
218
l.vetoableChange(ev);
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
226
protected String removeMethodName() {
227
return "removeVetoableChangeListener"; // NOI18N
231
/** Weak document modifications listener.
232
* This class if final only for performance reasons,
233
* can be happily unfinaled if desired.
235
static final class Document extends WeakListenerImpl implements DocumentListener {
237
* @param l listener to delegate to
239
public Document(final DocumentListener l) {
240
super(DocumentListener.class, l);
243
/** Gives notification that an attribute or set of attributes changed.
244
* @param ev event describing the action
246
public void changedUpdate(DocumentEvent ev) {
247
final DocumentListener l = docGet(ev);
254
/** Gives notification that there was an insert into the document.
255
* @param ev event describing the action
257
public void insertUpdate(DocumentEvent ev) {
258
final DocumentListener l = docGet(ev);
265
/** Gives notification that a portion of the document has been removed.
266
* @param ev event describing the action
268
public void removeUpdate(DocumentEvent ev) {
269
final DocumentListener l = docGet(ev);
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
280
protected String removeMethodName() {
281
return "removeDocumentListener"; // NOI18N
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
288
private DocumentListener docGet(DocumentEvent ev) {
289
DocumentListener l = (DocumentListener) super.ref.get();
292
super.ref.requestCleanUp(ev.getDocument());
298
// end of Document inner class
300
/** Weak swing change listener.
301
* This class if final only for performance reasons,
302
* can be happily unfinaled if desired.
304
static final class Change extends WeakListenerImpl implements ChangeListener {
306
* @param l listener to delegate to
308
public Change(ChangeListener l) {
309
super(ChangeListener.class, l);
312
/** Called when new file system is added to the pool.
313
* @param ev event describing the action
315
public void stateChanged(final ChangeEvent ev) {
316
ChangeListener l = (ChangeListener) super.get(ev);
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
327
protected String removeMethodName() {
328
return "removeChangeListener"; // NOI18N
332
/** Weak version of focus listener.
333
* This class if final only for performance reasons,
334
* can be happily unfinaled if desired.
336
static final class Focus extends WeakListenerImpl implements FocusListener {
338
* @param l listener to delegate to
340
public Focus(FocusListener l) {
341
super(FocusListener.class, l);
344
/** Delegates to the original listener.
346
public void focusGained(FocusEvent ev) {
347
FocusListener l = (FocusListener) super.get(ev);
354
/** Delegates to the original listener.
356
public void focusLost(FocusEvent ev) {
357
FocusListener l = (FocusListener) super.get(ev);
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
368
protected String removeMethodName() {
369
return "removeFocusListener"; // NOI18N
373
/** Proxy interface that delegates to listeners.
375
private static class ProxyListener extends WeakListenerImpl implements InvocationHandler {
377
private static Method equalsMth;
379
/** Class -> Reference(Constructor) */
380
private static final Map<Class, Reference<Constructor>> constructors = new WeakHashMap<Class, Reference<Constructor>>();
382
/** proxy generated for this listener */
383
public final Object proxy;
385
/** @param listener listener to delegate to
387
public ProxyListener(Class c, Class api, java.util.EventListener listener) {
388
super(api, listener);
391
Reference ref = (Reference) constructors.get(c);
392
Constructor proxyConstructor = (ref == null) ? null : (Constructor) ref.get();
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));
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);
412
} catch (Exception ex) {
413
throw (IllegalStateException) new IllegalStateException(ex.toString()).initCause(ex);
418
private static Method getEquals() {
419
if (equalsMth == null) {
421
equalsMth = Object.class.getMethod("equals", new Class[] { Object.class }); // NOI18N
422
} catch (NoSuchMethodException e) {
430
public java.lang.Object invoke(Object proxy, Method method, Object[] args)
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]);
437
return (ret ? Boolean.TRUE : Boolean.FALSE);
440
return method.invoke(this, args);
444
EventObject ev = ((args != null) && (args[0] instanceof EventObject)) ? (EventObject) args[0] : null;
446
Object listener = super.get(ev);
448
if (listener != null) {
449
return method.invoke(listener, args);
455
/** Remove method name is composed from the name of the listener.
457
protected String removeMethodName() {
458
String name = listenerClass.getName();
460
// strip package name
461
int dot = name.lastIndexOf('.');
462
name = name.substring(dot + 1);
464
// in case of inner interfaces/classes we also strip the outer
466
int i = name.lastIndexOf('$'); // NOI18N
469
name = name.substring(i + 1);
472
return "remove".concat(name); // NOI18N
475
/** To string prints class.
477
public String toString() {
478
return super.toString() + "[" + listenerClass + "]"; // NOI18N
481
/** Equal is extended to equal also with proxy object.
483
public boolean equals(Object obj) {
484
return (proxy == obj) || (this == obj);
487
Object getImplementator() {
492
/** Reference that also holds ref to WeakListenerImpl.
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;
501
public ListenerReference(Object ref, WeakListenerImpl weakListener) {
502
super(ref, Utilities.activeReferenceQueue());
503
this.weakListener = weakListener;
506
/** Requestes cleanup of the listener with a provided source.
507
* @param source source of the cleanup
509
public synchronized void requestCleanUp(Object source) {
510
if (weakListener == null) {
511
// already being handled
515
if (weakListener.source != source) {
516
// plan new cleanup into the activeReferenceQueue with this listener and
518
weakListener.source = new WeakReference<Object> (source) {
519
ListenerReference doNotGCRef = new ListenerReference(new Object(), weakListener);
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;
531
WeakListenerImpl ref;
533
synchronized (this) {
536
if ((ref.source == null) || ((src = ref.source.get()) == null)) {
540
// we are going to clean up the listener
545
if (src instanceof Class) {
546
// Handle static listener methods sanely.
547
methodClass = (Class) src;
549
methodClass = src.getClass();
551
String methodName = ref.removeMethodName();
553
synchronized (LOCK) {
554
if ((lastClass == methodClass) && (lastMethodName == methodName) && (lastRemove != null)) {
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]);
564
if (remove == null) {
566
"Can't remove " + ref.listenerClass.getName() + //NOI18N
567
" using method " + methodName + //NOI18N
573
synchronized (LOCK) {
574
lastClass = methodClass;
575
lastMethodName = methodName;
581
params[0] = ref.getImplementator(); // Whom to unregister
584
remove.invoke(src, params);
585
} catch (Exception ex) { // from invoke(), should not happen
587
"Problem encountered while calling " + methodClass + "." + methodName + "(...) on " + src
589
LOG.log(Level.WARNING, null, ex);
593
/* can return null */
594
private Method getRemoveMethod(Class<?> methodClass, String methodName, Class listenerClass) {
595
final Class<?>[] clarray = new Class<?>[] { listenerClass };
599
m = methodClass.getMethod(methodName, clarray);
600
} catch (NoSuchMethodException e) {
603
m = methodClass.getDeclaredMethod(methodName, clarray);
604
} catch (NoSuchMethodException ex) {
607
methodClass = methodClass.getSuperclass();
608
} while ((m == null) && (methodClass != Object.class));
613
(!Modifier.isPublic(m.getModifiers()) || !Modifier.isPublic(m.getDeclaringClass().getModifiers()))
615
m.setAccessible(true);