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.beans.PropertyChangeListener;
045: import java.lang.ref.Reference;
046: import java.lang.ref.WeakReference;
047: import java.util.ArrayList;
048: import java.util.Arrays;
049: import java.util.List;
050: import java.util.logging.Level;
051: import javax.swing.event.ChangeEvent;
052: import javax.swing.event.ChangeListener;
053: import org.netbeans.junit.MockServices;
054: import org.netbeans.junit.NbTestCase;
055: import org.openide.ErrorManager;
056:
057: public class WeakListenersTest extends NbTestCase {
058:
059: private static Thread activeQueueThread;
060:
061: private ErrorManager log;
062:
063: public WeakListenersTest(String testName) {
064: super (testName);
065: }
066:
067: protected int timeOut() {
068: return 5000;
069: }
070:
071: protected Level logLevel() {
072: return Level.ALL;
073: }
074:
075: protected void setUp() throws Exception {
076: MockServices.setServices(ErrManager.class);
077: log = ErrorManager.getDefault()
078: .getInstance("TEST-" + getName());
079:
080: if (activeQueueThread == null) {
081: class WR extends WeakReference<Object> implements Runnable {
082: public WR(Object o) {
083: super (o, Utilities.activeReferenceQueue());
084: }
085:
086: public synchronized void run() {
087: activeQueueThread = Thread.currentThread();
088: notifyAll();
089: }
090: }
091:
092: Object obj = new Object();
093: WR wr = new WR(obj);
094: synchronized (wr) {
095: obj = null;
096: assertGC("Has to be cleared", wr);
097: // and has to execute run method
098: while (activeQueueThread == null) {
099: wr.wait();
100: }
101: }
102: }
103: }
104:
105: protected void runTest() throws Throwable {
106: assertNotNull("ErrManager has to be in lookup",
107: org.openide.util.Lookup.getDefault().lookup(
108: ErrManager.class));
109: ErrManager.messages.setLength(0);
110:
111: try {
112: super .runTest();
113: } catch (Throwable ex) {
114: throw new junit.framework.AssertionFailedError(ex
115: .getMessage()
116: + "\n" + ErrManager.messages.toString())
117: .initCause(ex);
118: }
119: }
120:
121: public void testOneCanCallHashCodeOrOnWeakListener() {
122: Listener l = new Listener();
123: Object weak = WeakListeners.create(
124: PropertyChangeListener.class, l, null);
125: weak.hashCode();
126: }
127:
128: /** Useful for next test */
129: interface X extends java.util.EventListener {
130: public void invoke();
131: }
132:
133: /** Useful for next test */
134: class XImpl implements X {
135: public int cnt;
136:
137: public void invoke() {
138: cnt++;
139: }
140: }
141:
142: public void testCallingMethodsWithNoArgumentWorks() {
143: XImpl l = new XImpl();
144: log.log("XImpl created: " + l);
145: X weak = (X) WeakListeners.create(X.class, l, null);
146: log.log("weak created: " + weak);
147: weak.invoke();
148: log.log("invoked");
149: assertEquals("One invocation", 1, l.cnt);
150: }
151:
152: public void testReleaseOfListenerWithNullSource() throws Exception {
153: doTestReleaseOfListener(false);
154: }
155:
156: public void testReleaseOfListenerWithSource() throws Exception {
157: doTestReleaseOfListener(true);
158: }
159:
160: private void doTestReleaseOfListener(final boolean source)
161: throws Exception {
162: Listener l = new Listener();
163:
164: class MyButton extends javax.swing.JButton {
165: private Thread removedBy;
166: private int cnt;
167:
168: public synchronized void removePropertyChangeListener(
169: PropertyChangeListener l) {
170: // notify prior
171: log.log("removePropertyChangeListener: " + source
172: + " cnt: " + cnt);
173: if (source && cnt == 0) {
174: notifyAll();
175: try {
176: // wait for 1
177: log.log("wait for 1");
178: wait();
179: log.log("wait for 1 over");
180: } catch (InterruptedException ex) {
181: fail("Not happen");
182: }
183: }
184: log.log("Super removePropertyChangeListener");
185: super .removePropertyChangeListener(l);
186: log.log("Super over removePropertyChangeListener");
187: removedBy = Thread.currentThread();
188: cnt++;
189: notifyAll();
190: }
191:
192: public synchronized void waitListener() throws Exception {
193: int cnt = 0;
194: while (removedBy == null) {
195: log.log("waitListener, wait 500");
196: wait(500);
197: log.log("waitListener 500 Over");
198: if (cnt++ == 5) {
199: fail("Time out: removePropertyChangeListener was not called at all");
200: } else {
201: log.log("Forced gc");
202: System.gc();
203: System.runFinalization();
204: log.log("after force runFinalization");
205: }
206: }
207: }
208: }
209:
210: MyButton button = new MyButton();
211: log.log("Button is here");
212: java.beans.PropertyChangeListener weakL = WeakListeners
213: .propertyChange(l, source ? button : null);
214: log.log("WeakListeners created: " + weakL);
215: button.addPropertyChangeListener(weakL);
216: log.log("WeakListeners attached");
217: assertTrue("Weak listener is there",
218: Arrays.asList(button.getPropertyChangeListeners())
219: .indexOf(weakL) >= 0);
220:
221: button.setText("Ahoj");
222: log.log("setText changed to ahoj");
223: assertEquals("Listener called once", 1, l.cnt);
224:
225: Reference<?> ref = new WeakReference<Object>(l);
226: log.log("Clearing listener");
227: l = null;
228:
229: synchronized (button) {
230: log.log("Before assertGC");
231: assertGC("Can disappear", ref);
232: log.log("assertGC ok");
233:
234: if (source) {
235: log.log("before wait");
236: button.wait();
237: log.log("after wait");
238: // this should not remove the listener twice
239: button.setText("Hoj");
240: log.log("after setText - > hoj");
241: // go on (wait 1)
242: button.notify();
243: log.log("before wait listener");
244:
245: button.waitListener();
246: log.log("after waitListener");
247: } else {
248: // trigger the even firing so weak listener knows from
249: // where to unregister
250: log.log("before setText -> Hoj");
251: button.setText("Hoj");
252: log.log("after setText -> Hoj");
253: }
254:
255: log.log("before 2 waitListener");
256: button.waitListener();
257: log.log("after 2 waitListener");
258: Thread.sleep(500);
259: log.log("Thread.sleep over");
260: }
261:
262: assertEquals("Weak listener has been removed", -1, Arrays
263: .asList(button.getPropertyChangeListeners()).indexOf(
264: weakL));
265: assertEquals("Button released from a thread",
266: activeQueueThread, button.removedBy);
267: assertEquals("Unregister called just once", 1, button.cnt);
268:
269: // and because it is not here, it can be GCed
270: Reference<?> weakRef = new WeakReference<Object>(weakL);
271: weakL = null;
272: log.log("Doing assertGC at the end");
273: assertGC("Weak listener can go away as well", weakRef);
274: }
275:
276: public void testSourceCanBeGarbageCollected() {
277: javax.swing.JButton b = new javax.swing.JButton();
278: Listener l = new Listener();
279:
280: b.addPropertyChangeListener(WeakListeners.propertyChange(l, b));
281:
282: Reference<?> ref = new WeakReference<Object>(b);
283: b = null;
284:
285: assertGC("Source can be GC", ref);
286: }
287:
288: public void testNamingListenerBehaviour() throws Exception {
289: Listener l = new Listener();
290: ImplEventContext c = new ImplEventContext();
291: javax.naming.event.NamingListener weakL = (javax.naming.event.NamingListener) WeakListeners
292: .create(javax.naming.event.ObjectChangeListener.class,
293: javax.naming.event.NamingListener.class, l, c);
294:
295: c.addNamingListener("",
296: javax.naming.event.EventContext.OBJECT_SCOPE, weakL);
297: assertEquals("Weak listener is there", weakL, c.listener);
298:
299: Reference<?> ref = new WeakReference<Object>(l);
300: l = null;
301:
302: synchronized (c) {
303: assertGC("Can disappear", ref);
304: c.waitListener();
305: }
306: assertNull("Listener removed", c.listener);
307: }
308:
309: public void testExceptionIllegalState() {
310: Listener l = new Listener();
311: try {
312: WeakListeners.create((Class) PropertyChangeListener.class,
313: (Class) javax.naming.event.NamingListener.class, l,
314: null);
315: fail("This shall not be allowed as NamingListener is not superclass of PropertyChangeListener");
316: } catch (IllegalArgumentException ex) {
317: // ok
318: }
319:
320: try {
321: WeakListeners.create((Class) Object.class, l, null);
322: fail("Not interface, it should fail");
323: } catch (IllegalArgumentException ex) {
324: // ok
325: }
326:
327: try {
328: WeakListeners.create((Class) Object.class,
329: (Class) Object.class, l, null);
330: fail("Not interface, it should fail");
331: } catch (IllegalArgumentException ex) {
332: // ok
333: }
334:
335: try {
336: WeakListeners.create(PropertyChangeListener.class,
337: Object.class, l, null);
338: fail("Not interface, it should fail");
339: } catch (IllegalArgumentException ex) {
340: // ok
341: }
342: }
343:
344: public void testHowBigIsWeakListener() throws Exception {
345: Listener l = new Listener();
346: javax.swing.JButton button = new javax.swing.JButton();
347: ImplEventContext c = new ImplEventContext();
348:
349: Object[] ignore = new Object[] { l, button, c,
350: Utilities.activeReferenceQueue() };
351:
352: PropertyChangeListener pcl = WeakListeners.propertyChange(l,
353: button);
354: assertSize("Not too big (plus 32 from ReferenceQueue)",
355: java.util.Collections.singleton(pcl), 112, ignore);
356:
357: Object ocl = WeakListeners.create(
358: javax.naming.event.ObjectChangeListener.class,
359: javax.naming.event.NamingListener.class, l, c);
360: assertSize("A bit bigger (plus 32 from ReferenceQueue)",
361: java.util.Collections.singleton(ocl), 128, ignore);
362:
363: Object nl = WeakListeners.create(
364: javax.naming.event.NamingListener.class, l, c);
365: assertSize("The same (plus 32 from ReferenceQueue)",
366: java.util.Collections.singleton(nl), 128, ignore);
367:
368: }
369:
370: public void testPrivateRemoveMethod() throws Exception {
371: PropChBean bean = new PropChBean();
372: Listener listener = new Listener();
373: PCL weakL = (PCL) WeakListeners.create(PCL.class, listener,
374: bean);
375: Reference<?> ref = new WeakReference<Object>(listener);
376:
377: bean.addPCL(weakL);
378:
379: bean.listeners.firePropertyChange(null, null, null);
380: assertEquals("One call to the listener", 1, listener.cnt);
381: listener.cnt = 0;
382:
383: listener = null;
384: assertGC("Listener wasn't GCed", ref);
385:
386: ref = new WeakReference<Object>(weakL);
387: weakL = null;
388: assertGC("WeakListener wasn't GCed", ref);
389:
390: // this shall enforce the removal of the listener
391: bean.listeners.firePropertyChange(null, null, null);
392:
393: assertEquals("No listeners", 0, bean.listeners
394: .getPropertyChangeListeners().length);
395: }
396:
397: public void testStaticRemoveMethod() throws Exception {
398: ChangeListener l = new ChangeListener() {
399: public void stateChanged(ChangeEvent e) {
400: }
401: };
402: Singleton.addChangeListener(WeakListeners.change(l,
403: Singleton.class));
404: assertEquals(1, Singleton.listeners.size());
405: Reference<?> r = new WeakReference<Object>(l);
406: l = null;
407: assertGC("could collect listener", r);
408: assertEquals("called remove method", 0, Singleton.listeners
409: .size());
410: }
411:
412: public static class Singleton {
413: public static List<ChangeListener> listeners = new ArrayList<ChangeListener>();
414:
415: public static void addChangeListener(ChangeListener l) {
416: listeners.add(l);
417: }
418:
419: public static void removeChangeListener(ChangeListener l) {
420: listeners.remove(l);
421: }
422: }
423:
424: private static final class Listener implements PCL,
425: java.beans.PropertyChangeListener,
426: javax.naming.event.ObjectChangeListener {
427: public int cnt;
428:
429: public void propertyChange(java.beans.PropertyChangeEvent ev) {
430: cnt++;
431: }
432:
433: public void namingExceptionThrown(
434: javax.naming.event.NamingExceptionEvent evt) {
435: cnt++;
436: }
437:
438: public void objectChanged(javax.naming.event.NamingEvent evt) {
439: cnt++;
440: }
441: } // end of Listener
442:
443: private static final class ImplEventContext extends
444: javax.naming.InitialContext implements
445: javax.naming.event.EventContext {
446: public javax.naming.event.NamingListener listener;
447:
448: public ImplEventContext() throws Exception {
449: }
450:
451: public void addNamingListener(javax.naming.Name target,
452: int scope, javax.naming.event.NamingListener l)
453: throws javax.naming.NamingException {
454: assertNull(listener);
455: listener = l;
456: }
457:
458: public void addNamingListener(String target, int scope,
459: javax.naming.event.NamingListener l)
460: throws javax.naming.NamingException {
461: assertNull(listener);
462: listener = l;
463: }
464:
465: public synchronized void removeNamingListener(
466: javax.naming.event.NamingListener l)
467: throws javax.naming.NamingException {
468: assertEquals("Removing the same listener", listener, l);
469: listener = null;
470: notifyAll();
471: }
472:
473: public boolean targetMustExist()
474: throws javax.naming.NamingException {
475: return false;
476: }
477:
478: public synchronized void waitListener() throws Exception {
479: int cnt = 0;
480: while (listener != null) {
481: wait(500);
482: if (cnt++ == 5) {
483: fail("Time out: removeNamingListener was not called at all");
484: } else {
485: System.gc();
486: System.runFinalization();
487: }
488: }
489: }
490:
491: }
492:
493: private static class PropChBean {
494: private java.beans.PropertyChangeSupport listeners = new java.beans.PropertyChangeSupport(
495: this );
496:
497: private void addPCL(PCL l) {
498: listeners.addPropertyChangeListener(l);
499: }
500:
501: private void removePCL(PCL l) {
502: listeners.removePropertyChangeListener(l);
503: }
504: } // End of PropChBean class
505:
506: // just a marker, its name will be used to construct the name of add/remove methods, e.g. addPCL, removePCL
507: private static interface PCL extends PropertyChangeListener {
508: } // End of PrivatePropL class
509:
510: //
511: // Manager to delegate to
512: //
513: public static final class ErrManager extends
514: org.openide.ErrorManager {
515: public static final StringBuffer messages = new StringBuffer();
516:
517: private String prefix;
518:
519: public ErrManager() {
520: this (null);
521: }
522:
523: public ErrManager(String prefix) {
524: this .prefix = prefix;
525: }
526:
527: public static ErrManager get() {
528: return (ErrManager) org.openide.util.Lookup.getDefault()
529: .lookup(ErrManager.class);
530: }
531:
532: public Throwable annotate(Throwable t, int severity,
533: String message, String localizedMessage,
534: Throwable stackTrace, java.util.Date date) {
535: return t;
536: }
537:
538: public Throwable attachAnnotations(Throwable t,
539: org.openide.ErrorManager.Annotation[] arr) {
540: return t;
541: }
542:
543: public org.openide.ErrorManager.Annotation[] findAnnotations(
544: Throwable t) {
545: return null;
546: }
547:
548: public org.openide.ErrorManager getInstance(String name) {
549: if (name.startsWith("org.openide.util.RequestProcessor")
550: || name.startsWith("TEST")) {
551: return new ErrManager('[' + name + ']');
552: } else {
553: // either new non-logging or myself if I am non-logging
554: return new ErrManager();
555: }
556: }
557:
558: public void log(int severity, String s) {
559: lastSeverity = severity;
560: lastText = s;
561: if (this != get()) {
562: messages.append(prefix);
563: messages.append(s);
564: messages.append('\n');
565: }
566: }
567:
568: public void notify(int severity, Throwable t) {
569: lastThrowable = t;
570: lastSeverity = severity;
571: }
572:
573: private static int lastSeverity;
574: private static Throwable lastThrowable;
575: private static String lastText;
576:
577: public static void assertNotify(int sev, Throwable t) {
578: assertEquals("Severity is same", sev, lastSeverity);
579: assertSame("Throwable is the same", t, lastThrowable);
580: lastThrowable = null;
581: lastSeverity = -1;
582: }
583:
584: public static void assertLog(int sev, String t) {
585: assertEquals("Severity is same", sev, lastSeverity);
586: assertEquals("Text is the same", t, lastText);
587: lastText = null;
588: lastSeverity = -1;
589: }
590:
591: } // end of ErrManager
592:
593: }
|