package freenet.node; import freenet.io.comm.AsyncMessageCallback; /** Groups a set of message sends together so that we get a single sent(boolean) * callback after all the messages have been sent, and then later a finished(boolean). * None of the callbacks will be called until after you call arm(). Typical usage: * <pre>Message m1 = ...; * Message m2 = ...; * PeerNode pn = ...; * MultiMessageCallback mcb = new MultiMessageCallback() { * protected void finish(boolean success) { * // Messages have finished. * } * protected void sent(boolean success) { * // Messages have been sent. * } * } * pn.sendAsync(m1, mcb.make(), ctr); * pn.sendAsync(m2, mcb.make(), ctr); * mcb.arm();</pre> */ public abstract class MultiMessageCallback { /** Number of messages that have not yet completed */ private int waiting; /** Number of messages that have not yet been sent */ private int waitingForSend; /** True if arm() has been called. finish(boolean) and sent(boolean) will * only be called after arming the callback. */ private boolean armed; /** True if some messages have failed to send (e.g. disconnected). */ private boolean someFailed; /** This is called when all messages have been acked, or failed */ abstract void finish(boolean success); /** This is called when all messages have been sent (but not acked) or failed to send */ abstract void sent(boolean success); /** Add another message. You should call arm() after you have added all messages. */ public AsyncMessageCallback make() { synchronized(this) { assert(!armed); AsyncMessageCallback cb = new AsyncMessageCallback() { private boolean finished; private boolean sent; @Override public void sent() { boolean success; synchronized(MultiMessageCallback.this) { if(finished || sent) return; sent = true; waitingForSend--; if(waitingForSend > 0) return; if(!armed) return; success = !someFailed; } MultiMessageCallback.this.sent(success); } @Override public void acknowledged() { complete(true); } @Override public void disconnected() { complete(false); } @Override public void fatalError() { complete(false); } private void complete(boolean success) { boolean callSent = false; synchronized(MultiMessageCallback.this) { if(finished) return; if(!sent) { sent = true; waitingForSend--; if(waitingForSend == 0) callSent = true; } if(!success) someFailed = true; finished = true; waiting--; if(!finished()) return; if(someFailed) success = false; } if(callSent) MultiMessageCallback.this.sent(success); finish(success); } }; waiting++; waitingForSend++; return cb; } } /** Enable the callback. The callbacks sent(boolean) and finish(boolean) * will only be called after this method has been called, so you should * use this to indicate that you won't add any more messages. */ public void arm() { boolean success; boolean callSent = false; boolean complete = false; synchronized(this) { armed = true; complete = waiting == 0; if(waitingForSend == 0) callSent = true; success = !someFailed; } if(callSent) sent(success); if(complete) finish(success); } /** @return True if the callbacck has finished (and is armed) */ protected final synchronized boolean finished() { return armed && waiting == 0; } }