# HG changeset patch # User Jaroslav Tulach # Date 1300643567 -3600 # Node ID 35da2d439e3d90c890384b0c0126281f97775f32 # Parent c20d1d8ef2ca2ed2c5ca7c7f4308ee6238cb8d11 Calculator and various ways to deliver changes in its counter to listeners diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/nbproject/project.properties --- a/samples/openfixed/nbproject/project.properties Sun Mar 20 08:12:26 2011 +0100 +++ b/samples/openfixed/nbproject/project.properties Sun Mar 20 18:52:47 2011 +0100 @@ -25,6 +25,7 @@ dist.jar=${dist.dir}/openfixed.jar dist.javadoc.dir=${dist.dir}/javadoc excludes= +file.reference.junit-4.4.jar=../libs/dist/junit-4.4.jar includes=** jar.compress=false javac.classpath= @@ -37,7 +38,8 @@ javac.target=1.6 javac.test.classpath=\ ${javac.classpath}:\ - ${build.classes.dir} + ${build.classes.dir}:\ + ${file.reference.junit-4.4.jar} javac.test.processorpath=\ ${javac.test.classpath} javadoc.additionalparam= diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/AsyncEventSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/AsyncEventSupport.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,50 @@ +package org.apidesign.openfixed; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * + * @author Jaroslav Tulach + */ +final class AsyncEventSupport implements EventSupport { + private final List listeners = new CopyOnWriteArrayList(); + private static final Executor EXEC = Executors.newSingleThreadExecutor(); + + AsyncEventSupport() { + } + + @Override + public void fireModificationEvent(ModificationEvent ev) { + EXEC.execute(new Deliverable(ev, listeners.toArray(new ModificationListener[0]))); + } + + @Override + public void add(ModificationListener l) { + listeners.add(l); + } + + @Override + public void remove(ModificationListener l) { + listeners.remove(l); + } + + private static class Deliverable implements Runnable { + final ModificationEvent ev; + final ModificationListener[] listeners; + + public Deliverable(ModificationEvent ev, ModificationListener[] listeners) { + this.ev = ev; + this.listeners = listeners; + } + + @Override + public void run() { + for (ModificationListener l : listeners) { + l.modification(ev); + } + } + } +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/Calculator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/Calculator.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,49 @@ +package org.apidesign.openfixed; + +/** Sample bean using the {@link ModificationListener} + * to add numbers. + * + * @author Jaroslav Tulach + */ +public final class Calculator { + private final EventSupport listeners; + private int sum; + + private Calculator(EventSupport listeners) { + this.listeners = listeners; + } + + public static Calculator create() { + return new Calculator(new TrivialEventSupport()); + } + + public static Calculator createAsynch() { + return new Calculator(new AsyncEventSupport()); + } + + /** @since 2.0 */ + public static Calculator createPending() { + return new Calculator(new PendingEventSupport()); + } + + /** @since 3.0 */ + public static Calculator createBatch() { + return new Calculator(new PostEventSupport()); + } + + public synchronized void add(int add) { + sum += add; + listeners.fireModificationEvent(new ModificationEvent(this, add)); + } + + public synchronized int getSum() { + return sum; + } + + public void addModificationListener(ModificationListener l) { + listeners.add(l); + } + public void removeModificationListener(ModificationListener l) { + listeners.remove(l); + } +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/EventSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/EventSupport.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,19 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.apidesign.openfixed; + +/** + * + * @author Jaroslav Tulach + */ +interface EventSupport { + + public void fireModificationEvent(ModificationEvent ev); + + public void add(ModificationListener l); + + public void remove(ModificationListener l); + +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/Growable.java --- a/samples/openfixed/src/org/apidesign/openfixed/Growable.java Sun Mar 20 08:12:26 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -package org.apidesign.openfixed; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** Sample bean using the {@link GrowingListener}. - * - * @author Jaroslav Tulach - */ -public final class Growable { - private List listeners = new CopyOnWriteArrayList(); - - public void addGrowingListener(GrowingListener l) { - listeners.add(l); - } - public void removeGrowingListener(GrowingListener l) { - listeners.remove(l); - } -} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/GrowingEvent.java --- a/samples/openfixed/src/org/apidesign/openfixed/GrowingEvent.java Sun Mar 20 08:12:26 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -package org.apidesign.openfixed; - -import java.util.EventObject; - -// BEGIN: openfixed.event -public final class GrowingEvent extends EventObject { - public GrowingEvent(Object source) { - super(source); - } -// FINISH: openfixed.event - -// BEGIN: openfixed.addgetter - private int index; - /** @since 2.0 */ - public GrowingEvent(Object source, int index) { - super(source); - this.index = index; - } - - /** @since 2.0 */ - public int getIndex() { - return index; - } -// END: openfixed.addgetter -} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/GrowingListener.java --- a/samples/openfixed/src/org/apidesign/openfixed/GrowingListener.java Sun Mar 20 08:12:26 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -package org.apidesign.openfixed; - -import java.util.EventListener; - -// BEGIN: openfixed.listener -public interface GrowingListener extends EventListener { - public void response(GrowingEvent ev); -} -// FINISH: openfixed.listener diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/ModificationEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/ModificationEvent.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,35 @@ +package org.apidesign.openfixed; + +import java.util.Collection; +import java.util.EventObject; + +// BEGIN: openfixed.event +public final class ModificationEvent extends EventObject { + private final int delta; + ModificationEvent(Object source, int delta) { + super(source); + this.delta = delta; + } + + public int getChange() { + return delta; + } + +// FINISH: openfixed.event + +// BEGIN: openfixed.addgetter + int pending; + /** @since 2.0 */ + public int getPending() { + return pending; + } +// END: openfixed.addgetter + +// BEGIN: openfixed.mount + Collection posts; + /** @since 3.0 */ + public void postProcess(PostModificationListener p) { + posts.add(p); + } +// END: openfixed.mount +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/ModificationListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/ModificationListener.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,9 @@ +package org.apidesign.openfixed; + +import java.util.EventListener; + +// BEGIN: openfixed.listener +public interface ModificationListener extends EventListener { + public void modification(ModificationEvent ev); +} +// FINISH: openfixed.listener diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/PendingEventSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/PendingEventSupport.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,67 @@ +package org.apidesign.openfixed; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * + * @author Jaroslav Tulach + */ +final class PendingEventSupport implements EventSupport, Runnable { + private final List listeners = new CopyOnWriteArrayList(); + private final List deliverables = new CopyOnWriteArrayList(); + private static final Executor EXEC = Executors.newSingleThreadExecutor(); + + PendingEventSupport() { + } + + @Override + public void fireModificationEvent(ModificationEvent ev) { + synchronized (deliverables) { + final Deliverable d = new Deliverable(ev, listeners.toArray(new ModificationListener[0])); + deliverables.add(d); + EXEC.execute(this); + } + } + + @Override + public void add(ModificationListener l) { + listeners.add(l); + } + + @Override + public void remove(ModificationListener l) { + listeners.remove(l); + } + + @Override + public void run() { + Deliverable[] pending; + synchronized (deliverables) { + if (deliverables.isEmpty()) { + return; + } + pending = deliverables.toArray(new Deliverable[0]); + deliverables.clear(); + } + int pendingCount = pending.length; + for (Deliverable d : pending) { + d.ev.pending = --pendingCount; + for (ModificationListener l : d.listeners) { + l.modification(d.ev); + } + } + } + + private static class Deliverable { + final ModificationEvent ev; + final ModificationListener[] listeners; + + public Deliverable(ModificationEvent ev, ModificationListener[] listeners) { + this.ev = ev; + this.listeners = listeners; + } + } +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/PostEventSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/PostEventSupport.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,78 @@ +package org.apidesign.openfixed; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * + * @author Jaroslav Tulach + */ +final class PostEventSupport implements EventSupport, Runnable { + private final List listeners = new CopyOnWriteArrayList(); + private final List deliverables = new CopyOnWriteArrayList(); + private static final Executor EXEC = Executors.newSingleThreadExecutor(); + + PostEventSupport() { + } + + @Override + public void fireModificationEvent(ModificationEvent ev) { + synchronized (deliverables) { + final Deliverable d = new Deliverable(ev, listeners.toArray(new ModificationListener[0])); + deliverables.add(d); + EXEC.execute(this); + } + } + + @Override + public void add(ModificationListener l) { + listeners.add(l); + } + + @Override + public void remove(ModificationListener l) { + listeners.remove(l); + } + + @Override + public void run() { + Deliverable[] pending; + synchronized (deliverables) { + if (deliverables.isEmpty()) { + return; + } + pending = deliverables.toArray(new Deliverable[0]); + deliverables.clear(); + } + Calculator calc = null; + Set notify = new HashSet(); + int pendingCount = pending.length; + for (Deliverable d : pending) { + calc = (Calculator)d.ev.getSource(); + d.ev.pending = --pendingCount; + d.ev.posts = notify; + for (ModificationListener l : d.listeners) { + l.modification(d.ev); + } + d.ev.posts = null; + } + + for (PostModificationListener pml : notify) { + pml.postProcess(new PostModificationEvent(calc)); + } + } + + private static class Deliverable { + final ModificationEvent ev; + final ModificationListener[] listeners; + + public Deliverable(ModificationEvent ev, ModificationListener[] listeners) { + this.ev = ev; + this.listeners = listeners; + } + } +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/PostModificationEvent.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/PostModificationEvent.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,14 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.apidesign.openfixed; + +import java.util.EventObject; +// BEGIN: openfixed.postevent +public final class PostModificationEvent extends EventObject { + PostModificationEvent(Calculator calc) { + super(calc); + } +} +// END: openfixed.postevent diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/PostModificationListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/PostModificationListener.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,9 @@ +package org.apidesign.openfixed; + +import java.util.EventListener; + +// BEGIN: openfixed.postprocessor +public interface PostModificationListener extends EventListener { + public void postProcess(PostModificationEvent ev); +} +// END: openfixed.postprocessor diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/src/org/apidesign/openfixed/TrivialEventSupport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/src/org/apidesign/openfixed/TrivialEventSupport.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,37 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.apidesign.openfixed; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * + * @author Jaroslav Tulach + */ +final class TrivialEventSupport implements EventSupport { + List listener = new CopyOnWriteArrayList(); + + TrivialEventSupport() { + } + + @Override + public void fireModificationEvent(ModificationEvent ev) { + for (ModificationListener l : listener) { + l.modification(ev); + } + } + + @Override + public void add(ModificationListener l) { + listener.add(l); + } + + @Override + public void remove(ModificationListener l) { + listener.remove(l); + } + +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/test/org/apidesign/openfixed/AsyncTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/test/org/apidesign/openfixed/AsyncTest.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,18 @@ +package org.apidesign.openfixed; + +/** Test the Calculator.createAsynch() behavior. + * + * @author Jaroslav Tulach + */ +public final class AsyncTest extends CalculatorBase { + + public AsyncTest(String testName) { + super(testName); + } + + @Override + protected Calculator create() { + return Calculator.createAsynch(); + } + +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/test/org/apidesign/openfixed/BasicTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/test/org/apidesign/openfixed/BasicTest.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,16 @@ +package org.apidesign.openfixed; + +/** Test the Calculator.create() behavior. + * + * @author Jaroslav Tulach + */ +public final class BasicTest extends CalculatorBase { + public BasicTest(String testName) { + super(testName); + } + + @Override + protected Calculator create() { + return Calculator.create(); + } +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/test/org/apidesign/openfixed/CalculatorBase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/test/org/apidesign/openfixed/CalculatorBase.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,64 @@ +package org.apidesign.openfixed; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +public abstract class CalculatorBase extends TestCase { + + public CalculatorBase(String testName) { + super(testName); + } + + protected abstract Calculator create(); + + public void testSumAndListeners() throws Exception { + Calculator a = create(); + MockListener l = new MockListener(); + a.addModificationListener(l); + a.add(5); + a.add(10); + a.add(20); + int ch = allChanges(l.assertEvents("Three changes", 3)); + assertEquals("35 was the change", 35, ch); + assertEquals("Current value", 35, a.getSum()); + a.add(-5); + int ch2 = allChanges(l.assertEvents("One change", 1)); + assertEquals("minus five was the change", -5, ch2); + assertEquals("Final value", 30, a.getSum()); + } + + private static int allChanges(List events) { + int changes = 0; + for (ModificationEvent me : events) { + changes += me.getChange(); + } + return changes; + } + + public static class MockListener implements ModificationListener { + private List events; + + @Override + public synchronized void modification(ModificationEvent ev) { + if (events == null) { + events = new ArrayList(); + } + events.add(ev); + } + + public synchronized List assertEvents(String msg, int cnt) + throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (events != null && events.size() >= cnt) { + break; + } + wait(1000); + } + assertEquals(msg + ":\n" + events, cnt, events.size()); + List res = events; + events = null; + return res; + } + } // end of ModificationListener +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/test/org/apidesign/openfixed/PendingTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/test/org/apidesign/openfixed/PendingTest.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,60 @@ +package org.apidesign.openfixed; + +import java.util.List; +import java.util.concurrent.CountDownLatch; + +/** Test the Calculator.createPending() behavior. + * + * @author Jaroslav Tulach + */ +public class PendingTest extends CalculatorBase { + + public PendingTest(String testName) { + super(testName); + } + + @Override + protected Calculator create() { + return Calculator.createPending(); + } + + public void testPendingEvents() throws Exception { + BlockingListener bl = new BlockingListener(); + + Calculator calc = create(); + calc.addModificationListener(bl); + + calc.add(10); + bl.first.await(); + + calc.add(1); + calc.add(2); + calc.add(3); + + bl.cdl.countDown(); + + List events = bl.assertEvents("Four changes together", 4); + + assertEquals("No pending events for first event", 0, events.get(0).getPending()); + assertEquals("Group of three, two remaining", 2, events.get(1).getPending()); + assertEquals("Group of three, one remaining", 1, events.get(2).getPending()); + assertEquals("Group of three, last one", 0, events.get(3).getPending()); + } + + static class BlockingListener extends MockListener { + CountDownLatch first = new CountDownLatch(1); + CountDownLatch cdl = new CountDownLatch(1); + + @Override + public synchronized void modification(ModificationEvent ev) { + try { + first.countDown(); + cdl.await(); + } catch (InterruptedException ex) { + throw new IllegalStateException(ex); + } + super.modification(ev); + } + } + +} diff -r c20d1d8ef2ca -r 35da2d439e3d samples/openfixed/test/org/apidesign/openfixed/PostTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/openfixed/test/org/apidesign/openfixed/PostTest.java Sun Mar 20 18:52:47 2011 +0100 @@ -0,0 +1,62 @@ +package org.apidesign.openfixed; + +import java.util.concurrent.CountDownLatch; + +/** Test the Calculator.createPending() behavior. + * + * @author Jaroslav Tulach + */ +public final class PostTest extends PendingTest { + + public PostTest(String testName) { + super(testName); + } + + @Override + protected Calculator create() { + return Calculator.createBatch(); + } + + public void testPostModificationEvents() throws Exception { + class PostListener extends BlockingListener implements PostModificationListener { + int cnt; + + @Override + public synchronized void modification(ModificationEvent ev) { + // registers for callback when batch processing is over: + ev.postProcess(this); + super.modification(ev); + } + + @Override + public synchronized void postProcess(PostModificationEvent ev) { + cnt++; + } + + public synchronized void assertPostProcess(String msg, int expected) throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (cnt >= expected) { + break; + } + wait(1000); + } + assertEquals(msg, expected, cnt); + cnt = 0; + } + } + PostListener bl = new PostListener(); + + Calculator calc = create(); + calc.addModificationListener(bl); + + calc.add(10); + bl.first.await(); + + calc.add(1); + calc.add(2); + calc.add(3); + + bl.cdl.countDown(); + bl.assertPostProcess("Two postprocessings (one for 10), then for the rest", 2); + } +}