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