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;
}
}