Skip to content

Instantly share code, notes, and snippets.

@jakubkulhan
Created March 16, 2013 21:16
Show Gist options
  • Save jakubkulhan/5178365 to your computer and use it in GitHub Desktop.
Save jakubkulhan/5178365 to your computer and use it in GitHub Desktop.
package info.kulhan.evented;
import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.BlockingQueue;
import android.util.SparseArray;
/**
* Inbox for receiving messages
* @author Jakub Kulhan <[email protected]>
*/
public class Inbox {
/**
* Special message ID
*
* When message with this ID is dispatched, dispatch() returns false.
* If dispatched by loop(), loop() stops.
*/
final public static int STOP_DISPATCH = 0;
/**
* Will dispatch immediately on current thread
*/
final static PostponedDispatch defaultPostponedDispatch = new PostponedDispatch() {
@Override
public void postpone(Runnable r) {
r.run();
}
};
/**
* Object that receives messages
*/
private Object receiver;
/**
* Inbox thread
*/
private Thread thread;
/**
* Message queue
*/
private BlockingQueue<Message> queue = new LinkedBlockingQueue<Message>();
/**
* Association between message ID and its dispatcher
*
* Every message ID has only one dispatcher.
*/
private SparseArray<Method> dispatchers = new SparseArray<Method>();
/**
* Initialize inbox
* @param receiver
*/
public Inbox(Object receiver) {
this.receiver = receiver;
for (Method method : receiver.getClass().getMethods()) {
On annotation = method.getAnnotation(On.class);
if (annotation != null) {
if (annotation.value() == STOP_DISPATCH) {
throw new IllegalArgumentException("@On annotation argument cannot be " + STOP_DISPATCH + ".");
}
dispatchers.put(annotation.value(), method);
}
}
}
/**
* Receive message
*
* The message will be dispatched the next time dispatch() is called.
*/
public void receive(int messageID, Object... args) {
try {
queue.put(new Message(messageID, args));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Dispatch message (if possible)
* @return true if dispatch should continue, false otherwise
*/
public boolean dispatch() {
return dispatch(defaultPostponedDispatch);
}
/**
* Dispatch message (if possible)
*
* Dispatch will be made by Runnable's run() method.
*
* @param p
* @return true if dispatch should continue, false otherwise
*/
public boolean dispatch(PostponedDispatch p) {
try {
final Message msg = queue.take();
if (msg.messageID == 0) {
return false;
}
p.postpone(new Runnable() {
@Override
public void run() {
try {
dispatchers.get(msg.messageID).invoke(receiver, msg.args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
return true;
}
/**
* Occupy current thread by message dispatching
*/
public void loop() {
loop(defaultPostponedDispatch);
}
/**
* Occupy current thread by message dispatching
*
* Dispatch will be made by Runnable's run() method.
*
* @param p
*/
public void loop(PostponedDispatch p) {
while (dispatch(p));
}
/**
* Spawn inbox handling thread
*/
public void spawn() {
spawn(defaultPostponedDispatch);
}
/**
* Spawn inbox handling thread with different dispatcher
* @param p
*/
public void spawn(final PostponedDispatch p) {
if (thread == null) {
thread = new Thread(new Runnable() {
@Override
public void run() {
Inbox.this.loop(p);
}
});
thread.start();
}
}
/**
* Calls quit() and then waits for inbox handling thread to join()
*/
public void join() {
if (thread != null) {
try {
quit();
thread.join();
thread = null;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* Stop message dispatch loop
*/
public void quit() {
try {
queue.put(new Message(0, null));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Message will be dispatched by Runnable
*/
public interface PostponedDispatch {
void postpone(Runnable r);
}
/**
* Holder for received message
*/
private class Message {
int messageID;
Object[] args;
Message(int messageID, Object[] args) {
this.messageID = messageID;
this.args = args;
}
}
}
package info.kulhan.evented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies for what message ID method should be dispatched
* @author Jakub Kulhan <[email protected]>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface On {
int value();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment