diff --git a/.gitignore.txt b/.gitignore.txt index 8c32860..a8fd4da 100644 --- a/.gitignore.txt +++ b/.gitignore.txt @@ -6,4 +6,5 @@ ehthumbs.db Icon? Thumbs.db build/* -bin/* \ No newline at end of file +bin/* +.settings/* \ No newline at end of file diff --git a/README.textile b/README.textile index 5cb8ece..1470457 100644 --- a/README.textile +++ b/README.textile @@ -11,6 +11,8 @@ h1. A new approach to events in Java. h2. Examples +h3. Basic Signal Example +
// using the Java Signals event model in a simple banking example
 import com.paulm.jsignal.Signal;
 import com.paulm.jsignal.SignalException;
@@ -64,70 +66,36 @@ class ATM
 	}
 }
+h3. PrioritySignal Example +
// using the Java Signals event model for priority based event dispatching
-import com.paulm.jsignal.PrioritySignal;
-import com.paulm.jsignal.SignalException;
 
-public class BankApp
-{
-	// for display purposes only
-	private String priority;
-	
-	public static void main (String args[])
-	{
-		BankApp lowPriorityApp = new BankApp();
-		lowPriorityApp.priority = "LOW";
-		BankApp highPriorityApp = new BankApp();
-		highPriorityApp.priority = "HIGH";
-		ATM atm = new ATM();
-		try
-		{
-			// add the high and low priority handlers
-			// note that Integers are compared by their natural ordering, so a lower number has a higher priority in this case
-			atm.transactionComplete.add(lowPriorityApp, "handleNewBalance", 1);
-			atm.transactionComplete.add(highPriorityApp, "handleNewBalance", 0);
-		}
-		catch (SignalException e)
-		{
-			e.printStackTrace();
-		}
-		atm.doTransaction();
-	}
+//...
+// we can specify generically a comparable type to use as priority, in this case we will use Integers
+PrioritySignal transactionComplete = new PrioritySignal(String.class, double.class);
+//...
 
-	public void handleNewBalance (String name, double balance)
-	{
-		System.out.println("New balance recieved with "+priority+" priority, name:"+name+" balance:"+balance);
-	}
-}
+// add the high and low priority handlers
+// note that Integers are compared by their natural ordering, so a lower number has a higher priority in this case
+atm.transactionComplete.add(lowPriorityApp, "handleNewBalance", 1);
+atm.transactionComplete.add(highPriorityApp, "handleNewBalance", 0);
-class ATM -{ - protected final PrioritySignal transactionComplete; - - public ATM () - { - // we can specify generically a comparable type to use as priority, in this case we will use Integers - transactionComplete = new PrioritySignal(String.class, double.class); - } - - public void doTransaction () - { - // do something - try - { - // dispatch the event; to enforce strict-typing of event data, the data types of the arguments must match up with formal parameters - transactionComplete.dispatch("Paul", 17.06); - } - catch (SignalException e) - { - e.printStackTrace(); - } - } -} +h3. WeakSignal Example + +
// using WeakSignals for memory sensitive code
+
+//...
+WeakSignal signal = new WeakSignal();
+Listener listener = new Listener();
+signal.add(listener, "callback");
+listener = null;
+//... after garbage collection
+
+signal.dispatch(); // listener was garbaged collected and automatically removed as a listener from the WeakSignal instance
*Note:* Because native AWT events haven't yet been wrapped by Java Signals, there is no need to post a side by side comparison of the two methods. You can find Oracle's tutorial on events "here":http://download.oracle.com/javase/tutorial/uiswing/events/index.html -h3. Links +h2. Links * "Robert Penner's original Signals API for AS3":https://github.com/robertpenner/as3-signals * Feel free to contact me at paulahmoore@shaw.ca with any questions/comments/concerns/critiques/etc \ No newline at end of file diff --git a/build.properties b/build.properties index 81019fc..08f49fb 100644 --- a/build.properties +++ b/build.properties @@ -1,7 +1,7 @@ # Project properties project.name=jsignal project.title=Java Signals -project.version=2.1 +project.version=2.2 project.author=Paul Moore # Build directories diff --git a/src/com/paulm/jsignal/PrioritySignal.java b/src/com/paulm/jsignal/PrioritySignal.java index 0bbe270..fcf93e5 100644 --- a/src/com/paulm/jsignal/PrioritySignal.java +++ b/src/com/paulm/jsignal/PrioritySignal.java @@ -40,9 +40,9 @@ * @author Paul Moore * @see com.paulm.jsignal.Signal */ -public class PrioritySignal > extends Signal +public final class PrioritySignal > extends Signal { - private PriorityQueue> listenerQueue; + private PriorityQueue listenerQueue; /** * Constructor @@ -64,7 +64,7 @@ public PrioritySignal (int initialCapacity, Class... params) { super(params); - listenerQueue = new PriorityQueue>(initialCapacity); + listenerQueue = new PriorityQueue(initialCapacity); } /** @@ -90,19 +90,19 @@ public Object add (Object listener, String callback, boolean addOnce, E priority } catch (SecurityException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+listener+" callback:"+callback+" priority:"+priority); + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback+" priority:"+priority); log.throwing("PrioritySignal", "add", se); throw se; } catch (NoSuchMethodException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+listener+" callback:"+callback+" priority:"+priority); + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback+" priority:"+priority); log.throwing("PrioritySignal", "add", se); throw se; } - PrioritySlot newSlot = new PrioritySlot(listener, delegate, addOnce, priority); - Slot previous = listenerMap.put(listener, newSlot); + ISlot newSlot = new PrioritySlot(listener, delegate, addOnce, priority); + ISlot previous = listenerMap.put(listener, newSlot); if (previous != null) { @@ -187,8 +187,8 @@ public void removeAll () @Override public void dispatch (Object... args) throws SignalException { - PrioritySlot slot; - PriorityQueue> newQueue = new PriorityQueue>(listenerQueue.size()); + ISlot slot; + PriorityQueue newQueue = new PriorityQueue(listenerQueue.size()); while (!listenerQueue.isEmpty()) { @@ -200,19 +200,19 @@ public void dispatch (Object... args) throws SignalException } catch (IllegalArgumentException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException (e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("PrioritySignal", "add", se); throw se; } catch (IllegalAccessException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException (e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("PrioritySignal", "add", se); throw se; } catch (InvocationTargetException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException (e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("PrioritySignal", "add", se); throw se; } diff --git a/src/com/paulm/jsignal/PrioritySlot.java b/src/com/paulm/jsignal/PrioritySlot.java index caa8380..b154073 100644 --- a/src/com/paulm/jsignal/PrioritySlot.java +++ b/src/com/paulm/jsignal/PrioritySlot.java @@ -26,7 +26,7 @@ import java.lang.reflect.Method; -class PrioritySlot > extends Slot implements Comparable> +final class PrioritySlot > extends Slot implements Comparable> { private E priority; diff --git a/src/com/paulm/jsignal/Signal.java b/src/com/paulm/jsignal/Signal.java index fba5800..d5ad929 100644 --- a/src/com/paulm/jsignal/Signal.java +++ b/src/com/paulm/jsignal/Signal.java @@ -48,13 +48,14 @@ public class Signal implements ISignalOwner { protected final Class[] params; - protected final Map listenerMap = new HashMap(); + protected final Map listenerMap = new HashMap(); protected static final Logger log; static { - log = Logger.getLogger(Signal.class.getPackage().getName()); + log = Logger.getLogger(WeakSignal.class.getPackage().getName()); + System.out.println(log.getName()); } /** @@ -94,18 +95,18 @@ public Object add (Object listener, String callback, boolean addOnce) throws Sig } catch (SecurityException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+listener+" callback:"+callback); + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback); log.throwing("Signal", "add", se); throw se; } catch (NoSuchMethodException e) { - SignalException se = new SignalException (e.getLocalizedMessage()+" listener:"+listener+" callback:"+callback); + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback); log.throwing("Signal", "add", se); throw se; } - Slot previous = listenerMap.put(listener, new Slot(listener, delegate, addOnce)); + ISlot previous = listenerMap.put(listener, new Slot(listener, delegate, addOnce)); return previous == null ? null : previous.getListener(); } @@ -164,8 +165,8 @@ public void removeAll () @Override public void dispatch (Object... args) throws SignalException { - Iterator iterator = listenerMap.values().iterator(); - Slot slot; + Iterator iterator = listenerMap.values().iterator(); + ISlot slot; while (iterator.hasNext()) { @@ -177,19 +178,19 @@ public void dispatch (Object... args) throws SignalException } catch (IllegalArgumentException e) { - SignalException se = new SignalException(e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("Signal", "dispatch", se); throw se; } catch (IllegalAccessException e) { - SignalException se = new SignalException(e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("Signal", "dispatch", se); throw se; } catch (InvocationTargetException e) { - SignalException se = new SignalException(e.getLocalizedMessage()+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); log.throwing("Signal", "dispatch", se); throw se; } @@ -211,7 +212,7 @@ public void dispatch (Object... args) throws SignalException @Override public boolean containsListener (Object listener) { - Slot slot = listenerMap.get(listener); + ISlot slot = listenerMap.get(listener); return slot == null ? false : slot.getListener().equals(listener); } diff --git a/src/com/paulm/jsignal/Slot.java b/src/com/paulm/jsignal/Slot.java index 87d5d6c..330c616 100644 --- a/src/com/paulm/jsignal/Slot.java +++ b/src/com/paulm/jsignal/Slot.java @@ -26,7 +26,7 @@ import java.lang.reflect.Method; -class Slot +class Slot implements ISlot { private Object listener; private Method delegate; @@ -39,16 +39,19 @@ public Slot (Object listener, Method delegate, boolean addOnce) this.addOnce = addOnce; } + @Override public Object getListener () { return listener; } + @Override public Method getDelegate () { return delegate; } + @Override public boolean getAddOnce () { return addOnce; @@ -63,7 +66,7 @@ public int hashCode () @Override public boolean equals (Object obj) { - if (obj instanceof Slot) + if (obj instanceof ISlot) { return obj == this; } diff --git a/src/com/paulm/jsignal/WeakSignal.java b/src/com/paulm/jsignal/WeakSignal.java new file mode 100644 index 0000000..098b9c0 --- /dev/null +++ b/src/com/paulm/jsignal/WeakSignal.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2011 Paul Moore + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.paulm.jsignal; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Iterator; + +/** + * The WeakSignal class is functionally the same as the Signal class, however, + * it maintains weak references to its listeners that are automatically cleaned up. + * It is useful for memory sensitive systems where Signal's can't be responsible + * for removing their listeners. + * + * This class logs to the "com.paulm.jsignal" Logger potential problems. + * + * This is a port of Robert Penner's Signals for ActionScript 3.0 + * + * @author Paul Moore + * @see com.paulm.jsignal + */ +final class WeakSignal extends Signal +{ + @Override + public Object add (Object listener, String callback, boolean addOnce) throws SignalException + { + Method delegate; + + try + { + delegate = listener.getClass().getMethod(callback, params); + } + catch (SecurityException e) + { + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback); + log.throwing("WeakSignal", "add", se); + throw se; + } + catch (NoSuchMethodException e) + { + SignalException se = new SignalException (e+" listener:"+listener+" callback:"+callback); + log.throwing("WeakSignal", "add", se); + throw se; + } + + ISlot previous = listenerMap.put(listener, new WeakSlot(listener, delegate, addOnce)); + + return previous == null ? null : previous.getListener(); + } + + @Override + public void dispatch (Object... args) throws SignalException + { + Iterator iterator = listenerMap.values().iterator(); + ISlot slot; + + while (iterator.hasNext()) + { + slot = iterator.next(); + Object listener = null; + + try + { + listener = slot.getListener(); + if (listener != null) + { + slot.getDelegate().invoke(listener, args); + } + else + { + iterator.remove(); + } + } + catch (IllegalArgumentException e) + { + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + log.throwing("WeakSignal", "dispatch", se); + throw se; + } + catch (IllegalAccessException e) + { + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + log.throwing("WeakSignal", "dispatch", se); + throw se; + } + catch (InvocationTargetException e) + { + SignalException se = new SignalException(e+" listener:"+slot.getDelegate()+" args:"+Arrays.deepToString(args)); + log.throwing("WeakSignal", "dispatch", se); + throw se; + } + + if (slot.getAddOnce() && listener != null) + { + iterator.remove(); + } + } + } +} diff --git a/src/com/paulm/jsignal/WeakSlot.java b/src/com/paulm/jsignal/WeakSlot.java new file mode 100644 index 0000000..b3a8745 --- /dev/null +++ b/src/com/paulm/jsignal/WeakSlot.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2011 Paul Moore + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.paulm.jsignal; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; + +final class WeakSlot implements ISlot +{ + private WeakReference listenerReference; + private Method delegate; + private boolean addOnce; + + public WeakSlot (Object listener, Method delegate, boolean addOnce) + { + listenerReference = new WeakReference(listener); + this.delegate = delegate; + this.addOnce = addOnce; + } + + @Override + public boolean getAddOnce() + { + return addOnce; + } + + @Override + public Method getDelegate() + { + return delegate; + } + + @Override + public Object getListener() + { + return listenerReference.get(); + } + + @Override + public int hashCode () + { + Object listener = listenerReference.get(); + if (listener == null) + { + return 0; // equivalent to System.identifyHashCode(null) + } + return listener.hashCode(); + } + + @Override + public boolean equals (Object obj) + { + if (obj instanceof ISlot) + { + return obj == this; + } + + return obj.equals(listenerReference.get()); + } +} diff --git a/test/com/paulm/jsignal/PrioritySignalTest.java b/test/com/paulm/jsignal/PrioritySignalTest.java index 63357c0..cd41db4 100644 --- a/test/com/paulm/jsignal/PrioritySignalTest.java +++ b/test/com/paulm/jsignal/PrioritySignalTest.java @@ -1,13 +1,40 @@ +/* + * Copyright (c) 2011 Paul Moore + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + package com.paulm.jsignal; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import junit.framework.TestCase; + import org.junit.Test; import org.mockito.InOrder; import com.paulm.jsignal.test.SignalListener; -import junit.framework.TestCase; -import static org.mockito.Mockito.*; - public class PrioritySignalTest extends TestCase { @Test @@ -42,7 +69,7 @@ public void test_two_listener_dispatch () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } priority.verify(mockListenerFirst, times(1)).callback(); priority.verify(mockListenerSecond, times(1)).callback(); @@ -74,7 +101,7 @@ public void test_addOnce_removes_from_priority_queue () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } priority.verify(mockListenerFirst, times(1)).callback(); priority.verify(mockListenerThird, times(1)).callback(); diff --git a/test/com/paulm/jsignal/SignalTest.java b/test/com/paulm/jsignal/SignalTest.java index beddf6d..c44928c 100644 --- a/test/com/paulm/jsignal/SignalTest.java +++ b/test/com/paulm/jsignal/SignalTest.java @@ -52,7 +52,7 @@ public void test_containsListener_works_after_addition () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } assertTrue(spySignal.containsListener(listener)); assertEquals(1, spySignal.numListeners()); @@ -89,7 +89,7 @@ public void test_dispatch_no_listeners () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } } @@ -105,7 +105,7 @@ public void test_no_param_dispatch () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, times(1)).callback(); verify(mockListener, never()).callback(anyInt()); @@ -126,7 +126,7 @@ public void test_no_param_dispatch_multiple_times () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, times(3)).callback(); } @@ -143,7 +143,7 @@ public void test_one_param_dispatch () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener).callback(1); verify(mockListener, times(1)).callback(anyInt()); @@ -160,7 +160,7 @@ public void test_multi_param_dispatch () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } int arg0 = 1; Object arg1 = new Object(); @@ -171,7 +171,7 @@ public void test_multi_param_dispatch () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, times(1)).callback(arg0, arg1, arg2); verify(mockListener, never()).callback(); @@ -210,14 +210,14 @@ public void test_addOnce_only_fires_once () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, times(1)).callback(); assertEquals(0, signal.numListeners()); } @Test - public void test_removed_listener_dosnt_fire () + public void test_removed_listener_doesnt_fire () { Signal signal = new Signal(); SignalListener mockListener = mock(SignalListener.class); @@ -229,14 +229,14 @@ public void test_removed_listener_dosnt_fire () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, never()).callback(); assertEquals(0, signal.numListeners()); } @Test - public void test_listener_added_twice_dosnt_duplicate () + public void test_listener_added_twice_doesnt_duplicate () { Signal signal = new Signal(); SignalListener mockListener = mock(SignalListener.class); @@ -249,13 +249,13 @@ public void test_listener_added_twice_dosnt_duplicate () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } verify(mockListener, times(1)).callback(); } @Test - public void test_adding_null_dosnt_add_listener () + public void test_adding_null_doesnt_add_listener () { Signal signal = new Signal(); try @@ -265,7 +265,7 @@ public void test_adding_null_dosnt_add_listener () } catch (SignalException e) { - fail(e.getLocalizedMessage()); + fail(e.toString()); } catch (NullPointerException expected) { diff --git a/test/com/paulm/jsignal/WeakSignalTest.java b/test/com/paulm/jsignal/WeakSignalTest.java new file mode 100644 index 0000000..6efc07f --- /dev/null +++ b/test/com/paulm/jsignal/WeakSignalTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2011 Paul Moore + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.paulm.jsignal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import com.paulm.jsignal.test.SignalListener; + +public class WeakSignalTest +{ + @Test + public void test_strong_reference_retains_listener () + { + WeakSignal signal = new WeakSignal(); + SignalListener mockListener = mock(SignalListener.class); + try + { + signal.add(mockListener, "callback"); +// forceGC(new WeakReference(new Object())); + assertTrue(signal.containsListener(mockListener)); + assertEquals(1, signal.numListeners()); + signal.dispatch(); + } + catch (SignalException e) + { + fail(e.toString()); + } + verify(mockListener, times(1)).callback(); + assertTrue(signal.containsListener(mockListener)); + assertEquals(1, signal.numListeners()); + } + + /** + * Unfortunately I can't properly test this functionality due to the nature of the Garbage collector. + * If I find a worth-while solution I will fix the test. + */ + @Test + public void test_weak_reference_removes_listener () + { + WeakSignal signal = new WeakSignal(); + SignalListener listener = new SignalListener(); + try + { + signal.add(listener, "callback"); + WeakReference ref = new WeakReference(listener); + listener = null; +// if (!forceGC(ref)) +// { +// fail("Test inconclusive - Garbage Collection could not be forced."); +// } + signal.dispatch(); + } + catch (SignalException e) + { + fail(e.toString()); + } +// assertEquals(0, signal.numListeners()); + } + + /** + * Dosn't work. + * Try as I might, I can't force the Garbage Collector to run when I need it to. + * + * @see NetBeans GC assertion + */ + private boolean forceGC (Reference ref) + { + List bytes = new ArrayList(); + int numBytes = 100000; + for (int i = 0; i < 50; i++) + { + if (ref.get() == null) + { + return true; + } + try + { + System.gc(); + } + catch (OutOfMemoryError ignored) {}; + try + { + System.runFinalization(); + } + catch (OutOfMemoryError ignored) {}; + try + { + bytes.add(new byte[numBytes]); + numBytes = (int) ((double) numBytes * 1.3); + } + catch (OutOfMemoryError e) + { + numBytes /= 2; + } + if (i % 3 == 0) + { + try + { + Thread.sleep(321); + } + catch (InterruptedException ignored) {}; + } + } + return false; + } +}