1
0
mirror of https://github.com/fabianonline/telegram_backup.git synced 2025-07-14 01:56:26 +00:00

First commit: Just a collection of library sources from Github. Compiles, but doesn't work.

This commit is contained in:
2016-06-29 10:59:33 +02:00
commit 53d2b1674f
371 changed files with 13715 additions and 0 deletions

View File

@ -0,0 +1,297 @@
package com.droidkit.actors;
import com.droidkit.actors.mailbox.Mailbox;
import com.droidkit.actors.messages.DeadLetter;
import com.droidkit.actors.messages.NamedMessage;
import com.droidkit.actors.tasks.*;
import com.droidkit.actors.tasks.messages.TaskError;
import com.droidkit.actors.tasks.messages.TaskResult;
import com.droidkit.actors.tasks.messages.TaskTimeout;
import java.lang.reflect.Array;
import java.util.UUID;
/**
* Actor object
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class Actor {
private UUID uuid;
private String path;
private ActorContext context;
private Mailbox mailbox;
private ActorAskImpl askPattern;
public Actor() {
}
/**
* <p>INTERNAL API</p>
* Initialization of actor
*
* @param uuid uuid of actor
* @param path path of actor
* @param context context of actor
* @param mailbox mailbox of actor
*/
public final void initActor(UUID uuid, String path, ActorContext context, Mailbox mailbox) {
this.uuid = uuid;
this.path = path;
this.context = context;
this.mailbox = mailbox;
this.askPattern = new ActorAskImpl(self());
}
/**
* Actor System
*
* @return Actor System
*/
public final ActorSystem system() {
return context.getSystem();
}
/**
* Self actor reference
*
* @return self reference
*/
public final ActorRef self() {
return context.getSelf();
}
/**
* Actor context
*
* @return context
*/
protected final ActorContext context() {
return context;
}
/**
* Sender of last received message
*
* @return sender's ActorRef
*/
public final ActorRef sender() {
return context.sender();
}
/**
* Actor UUID
*
* @return uuid
*/
protected final UUID getUuid() {
return uuid;
}
/**
* Actor path
*
* @return path
*/
protected final String getPath() {
return path;
}
/**
* Actor mailbox
*
* @return mailbox
*/
public final Mailbox getMailbox() {
return mailbox;
}
/**
* Called before first message receiving
*/
public void preStart() {
}
public final void onReceiveGlobal(Object message) {
if (message instanceof DeadLetter) {
if (askPattern.onDeadLetter((DeadLetter) message)) {
return;
}
} else if (message instanceof TaskResult) {
if (askPattern.onTaskResult((TaskResult) message)) {
return;
}
} else if (message instanceof TaskTimeout) {
if (askPattern.onTaskTimeout((TaskTimeout) message)) {
return;
}
} else if (message instanceof TaskError) {
if (askPattern.onTaskError((TaskError) message)) {
return;
}
}
onReceive(message);
}
/**
* Receiving of message
*
* @param message message
*/
public void onReceive(Object message) {
}
/**
* Called after actor shutdown
*/
public void postStop() {
}
/**
* Reply message to sender of last message
*
* @param message reply message
*/
public void reply(Object message) {
if (context.sender() != null) {
context.sender().send(message, self());
}
}
public AskFuture combine(AskFuture... futures) {
return askPattern.combine(futures);
}
public AskFuture combine(AskCallback<Object[]> callback, AskFuture... futures) {
AskFuture future = combine(futures);
future.addListener(callback);
return future;
}
public <T> AskFuture combine(final String name, final Class<T> clazz, AskFuture... futures) {
return combine(new AskCallback<Object[]>() {
@Override
public void onResult(Object[] result) {
T[] res = (T[]) Array.newInstance(clazz, result.length);
for (int i = 0; i < result.length; i++) {
res[i] = (T) result[i];
}
self().send(new NamedMessage(name, res));
}
@Override
public void onError(Throwable throwable) {
self().send(new NamedMessage(name, throwable));
}
}, futures);
}
public <T> AskFuture combine(final String name, AskFuture... futures) {
return combine(new AskCallback<Object[]>() {
@Override
public void onResult(Object[] result) {
self().send(new NamedMessage(name, result));
}
@Override
public void onError(Throwable throwable) {
self().send(new NamedMessage(name, throwable));
}
}, futures);
}
/**
* Ask TaskActor for result
*
* @param selection ActorSelection of task
* @return Future
*/
public AskFuture ask(ActorSelection selection) {
return askPattern.ask(system().actorOf(selection), 0, null);
}
/**
* Ask TaskActor for result
*
* @param selection ActorSelection of task
* @param timeout timeout of task
* @return Future
*/
public AskFuture ask(ActorSelection selection, long timeout) {
return askPattern.ask(system().actorOf(selection), timeout, null);
}
/**
* Ask TaskActor for result
*
* @param selection ActorSelection of task
* @param callback callback for ask
* @return Future
*/
public AskFuture ask(ActorSelection selection, AskCallback callback) {
return askPattern.ask(system().actorOf(selection), 0, callback);
}
/**
* Ask TaskActor for result
*
* @param selection ActorSelection of task
* @param timeout timeout of task
* @param callback callback for ask
* @return Future
*/
public AskFuture ask(ActorSelection selection, long timeout, AskCallback callback) {
return askPattern.ask(system().actorOf(selection), timeout, callback);
}
/**
* Ask TaskActor for result
*
* @param ref ActorRef of task
* @return Future
*/
public AskFuture ask(ActorRef ref) {
return askPattern.ask(ref, 0, null);
}
/**
* Ask TaskActor for result
*
* @param ref ActorRef of task
* @param timeout timeout of task
* @return Future
*/
public AskFuture ask(ActorRef ref, long timeout) {
return askPattern.ask(ref, timeout, null);
}
/**
* Ask TaskActor for result
*
* @param ref ActorRef of task
* @param callback callback for ask
* @return Future
*/
public AskFuture ask(ActorRef ref, AskCallback callback) {
return askPattern.ask(ref, 0, callback);
}
/**
* Ask TaskActor for result
*
* @param ref ActorRef of task
* @param timeout timeout of task
* @param callback callback for ask
* @return Future
*/
public AskFuture ask(ActorRef ref, long timeout, AskCallback callback) {
return askPattern.ask(ref, timeout, callback);
}
}

View File

@ -0,0 +1,59 @@
package com.droidkit.actors;
/**
* Context of actor
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorContext {
private final ActorScope actorScope;
/**
* <p>INTERNAL API</p>
* Creating of actor context
*
* @param scope actor scope
*/
public ActorContext(ActorScope scope) {
this.actorScope = scope;
}
/**
* Actor Reference
*
* @return reference
*/
public ActorRef getSelf() {
return actorScope.getActorRef();
}
/**
* Actor system
*
* @return Actor system
*/
public ActorSystem getSystem() {
return actorScope.getActorSystem();
}
/**
* Sender of last received message
*
* @return sender's ActorRef
*/
public ActorRef sender() {
return actorScope.getSender();
}
/**
* Stopping actor
*/
public void stopSelf() {
try {
actorScope.shutdownActor();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,15 @@
package com.droidkit.actors;
/**
* Object for manual actors creating
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public interface ActorCreator<T extends Actor> {
/**
* Create actor
*
* @return Actor
*/
public T create();
}

View File

@ -0,0 +1,121 @@
package com.droidkit.actors;
import com.droidkit.actors.mailbox.AbsActorDispatcher;
import java.util.UUID;
/**
* Reference to Actor that allows to send messages to real Actor
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorRef {
private ActorSystem system;
private AbsActorDispatcher dispatcher;
private UUID uuid;
private String path;
public UUID getUuid() {
return uuid;
}
public String getPath() {
return path;
}
/**
* <p>INTERNAL API</p>
* Creating actor reference
*
* @param system actor system
* @param dispatcher dispatcher of actor
* @param path path of actor
* @param uuid uuid of actor
*/
public ActorRef(ActorSystem system, AbsActorDispatcher dispatcher, UUID uuid, String path) {
this.system = system;
this.dispatcher = dispatcher;
this.uuid = uuid;
this.path = path;
}
/**
* Send message with empty sender
*
* @param message message
*/
public void send(Object message) {
send(message, null);
}
/**
* Send message with specified sender
*
* @param message message
* @param sender sender
*/
public void send(Object message, ActorRef sender) {
send(message, 0, sender);
}
/**
* Send message with empty sender and delay
*
* @param message message
* @param delay delay
*/
public void send(Object message, long delay) {
send(message, delay, null);
}
/**
* Send message
*
* @param message message
* @param delay delay
* @param sender sender
*/
public void send(Object message, long delay, ActorRef sender) {
dispatcher.sendMessage(path, message, ActorTime.currentTime() + delay, sender);
}
/**
* Send message once
*
* @param message message
*/
public void sendOnce(Object message) {
send(message, null);
}
/**
* Send message once
*
* @param message message
* @param sender sender
*/
public void sendOnce(Object message, ActorRef sender) {
sendOnce(message, 0, sender);
}
/**
* Send message once
*
* @param message message
* @param delay delay
*/
public void sendOnce(Object message, long delay) {
sendOnce(message, delay, null);
}
/**
* Send message once
*
* @param message message
* @param delay delay
* @param sender sender
*/
public void sendOnce(Object message, long delay, ActorRef sender) {
dispatcher.sendMessageOnce(path, message, ActorTime.currentTime() + delay, sender);
}
}

View File

@ -0,0 +1,128 @@
package com.droidkit.actors;
import com.droidkit.actors.mailbox.AbsActorDispatcher;
import com.droidkit.actors.mailbox.Mailbox;
import java.util.UUID;
/**
* <p>INTERNAL API</p>
* Actor Scope contains states of actor, UUID, Path, Props and Actor (if created).
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorScope {
public static final int STATE_STARTING = 0;
public static final int STATE_RUNNING = 1;
public static final int STATE_SHUTDOWN = 2;
private final UUID uuid;
private final String path;
private final Props props;
private final ActorRef actorRef;
private final Mailbox mailbox;
private final AbsActorDispatcher dispatcher;
private final ActorSystem actorSystem;
private int state;
private Actor actor;
private ActorRef sender;
public ActorScope(ActorSystem actorSystem, Mailbox mailbox, ActorRef actorRef, AbsActorDispatcher dispatcher, UUID uuid, String path, Props props) {
this.actorSystem = actorSystem;
this.mailbox = mailbox;
this.actorRef = actorRef;
this.dispatcher = dispatcher;
this.uuid = uuid;
this.path = path;
this.props = props;
this.state = STATE_STARTING;
}
public AbsActorDispatcher getDispatcher() {
return dispatcher;
}
public int getState() {
return state;
}
public UUID getUuid() {
return uuid;
}
public String getPath() {
return path;
}
public Props getProps() {
return props;
}
public ActorRef getActorRef() {
return actorRef;
}
public Mailbox getMailbox() {
return mailbox;
}
public Actor getActor() {
return actor;
}
public ActorSystem getActorSystem() {
return actorSystem;
}
public ActorRef getSender() {
return sender;
}
public void setSender(ActorRef sender) {
this.sender = sender;
}
/**
* Create actor
*
* @throws Exception
*/
public void createActor() throws Exception {
if (state == STATE_STARTING) {
actor = props.create();
CurrentActor.setCurrentActor(actor);
actor.initActor(getUuid(), getPath(), new ActorContext(this), getMailbox());
actor.preStart();
} else if (state == STATE_RUNNING) {
throw new RuntimeException("Actor already created");
} else if (state == STATE_SHUTDOWN) {
throw new RuntimeException("Actor shutdown");
} else {
throw new RuntimeException("Unknown ActorScope state");
}
}
/**
* Shutdown actor
*
* @throws Exception
*/
public void shutdownActor() throws Exception {
if (state == STATE_STARTING || state == STATE_RUNNING ||
state == STATE_SHUTDOWN) {
actorSystem.removeActor(this);
dispatcher.disconnectScope(this);
actor.postStop();
actor = null;
} else {
throw new RuntimeException("Unknown ActorScope state");
}
}
}

View File

@ -0,0 +1,24 @@
package com.droidkit.actors;
/**
* Actor selection: group and path of actor
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorSelection {
private final Props props;
private final String path;
public ActorSelection(Props props, String path) {
this.props = props;
this.path = path;
}
public Props getProps() {
return props;
}
public String getPath() {
return path;
}
}

View File

@ -0,0 +1,129 @@
package com.droidkit.actors;
import com.droidkit.actors.mailbox.AbsActorDispatcher;
import com.droidkit.actors.mailbox.ActorDispatcher;
import java.util.HashMap;
/**
* Entry point for Actor Model, creates all actors and dispatchers
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorSystem {
private static final ActorSystem mainSystem = new ActorSystem();
/**
* Main actor system
*
* @return ActorSystem
*/
public static ActorSystem system() {
return mainSystem;
}
private static final String DEFAULT_DISPATCHER = "default";
private final HashMap<String, AbsActorDispatcher> dispatchers = new HashMap<String, AbsActorDispatcher>();
private final HashMap<String, ActorScope> actors = new HashMap<String, ActorScope>();
/**
* Creating new actor system
*/
public ActorSystem() {
addDispatcher(DEFAULT_DISPATCHER);
}
/**
* Adding dispatcher with threads count = {@code Runtime.getRuntime().availableProcessors()}
*
* @param dispatcherId dispatcher id
*/
public void addDispatcher(String dispatcherId) {
addDispatcher(dispatcherId, new ActorDispatcher(this, Runtime.getRuntime().availableProcessors()));
}
/**
* Registering custom dispatcher
*
* @param dispatcherId dispatcher id
* @param dispatcher dispatcher object
*/
public void addDispatcher(String dispatcherId, AbsActorDispatcher dispatcher) {
synchronized (dispatchers) {
if (dispatchers.containsKey(dispatcherId)) {
return;
}
dispatchers.put(dispatcherId, dispatcher);
}
}
public <T extends Actor> ActorRef actorOf(ActorSelection selection) {
return actorOf(selection.getProps(), selection.getPath());
}
/**
* Creating or getting existing actor from actor class
*
* @param actor Actor Class
* @param path Actor Path
* @param <T> Actor Class
* @return ActorRef
*/
public <T extends Actor> ActorRef actorOf(Class<T> actor, String path) {
return actorOf(Props.create(actor), path);
}
/**
* Creating or getting existing actor from actor props
*
* @param props Actor Props
* @param path Actor Path
* @return ActorRef
*/
public ActorRef actorOf(Props props, String path) {
// TODO: Remove lock
synchronized (actors) {
// Searching for already created actor
ActorScope scope = actors.get(path);
// If already created - return ActorRef
if (scope != null) {
return scope.getActorRef();
}
// Finding dispatcher for actor
String dispatcherId = props.getDispatcher() == null ? DEFAULT_DISPATCHER : props.getDispatcher();
AbsActorDispatcher mailboxesDispatcher;
synchronized (dispatchers) {
if (!dispatchers.containsKey(dispatcherId)) {
throw new RuntimeException("Unknown dispatcherId '" + dispatcherId + "'");
}
mailboxesDispatcher = dispatchers.get(dispatcherId);
}
// Creating actor scope
scope = mailboxesDispatcher.createScope(path, props);
// Saving actor in collection
actors.put(path, scope);
return scope.getActorRef();
}
}
/**
* WARRING! Call only during processing message in actor!
*
* @param scope Actor Scope
*/
void removeActor(ActorScope scope) {
synchronized (actors) {
if (actors.get(scope.getPath()) == scope) {
actors.remove(scope.getPath());
}
}
}
}

View File

@ -0,0 +1,27 @@
package com.droidkit.actors;
/**
* Time used by actor system, uses System.nanoTime() inside
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorTime {
private static long lastTime = 0;
private static final Object timeLock = new Object();
/**
* Getting current actor system time
*
* @return actor system time
*/
public static long currentTime() {
long res = System.nanoTime() / 1000000;
synchronized (timeLock) {
if (lastTime < res) {
lastTime = res;
}
return lastTime;
}
}
}

View File

@ -0,0 +1,19 @@
package com.droidkit.actors;
/**
* <p>INTERNAL API!</p>
* Keeps current actor for thread. Will be used for better implementations of patterns.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class CurrentActor {
private static ThreadLocal<Actor> currentActor = new ThreadLocal<Actor>();
public static void setCurrentActor(Actor actor) {
currentActor.set(actor);
}
public static Actor getCurrentActor() {
return currentActor.get();
}
}

View File

@ -0,0 +1,88 @@
package com.droidkit.actors;
/**
* <p>Props is a configuration class to specify options for the creation of actors, think of it as an immutable and
* thus freely shareable recipe for creating an actor including associated dispatcher information.</p>
* For more information you may read about <a href="http://doc.akka.io/docs/akka/2.3.5/java/untyped-actors.html">Akka Props</a>.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public final class Props<T extends Actor> {
private static final int TYPE_DEFAULT = 1;
private static final int TYPE_CREATOR = 2;
private final Class<T> aClass;
private final Object[] args;
private final int type;
private final ActorCreator<T> creator;
private final String dispatcher;
private Props(Class<T> aClass, Object[] args, int type, String dispatcher, ActorCreator<T> creator) {
this.aClass = aClass;
this.args = args;
this.type = type;
this.creator = creator;
this.dispatcher = dispatcher;
}
/**
* Creating actor from Props
*
* @return Actor
* @throws Exception
*/
public T create() throws Exception {
if (type == TYPE_DEFAULT) {
if (args == null || args.length == 0) {
return aClass.newInstance();
}
} else if (type == TYPE_CREATOR) {
return creator.create();
}
throw new RuntimeException("Unsupported create method");
}
/**
* Getting dispatcher id if available
*
* @return
*/
public String getDispatcher() {
return dispatcher;
}
/**
* Changing dispatcher
*
* @param dispatcher dispatcher id
* @return this
*/
public Props<T> changeDispatcher(String dispatcher) {
return new Props<T>(aClass, args, type, dispatcher, creator);
}
/**
* Create props from class
*
* @param tClass Actor class
* @param <T> Actor class
* @return Props object
*/
public static <T extends Actor> Props<T> create(Class<T> tClass) {
return new Props(tClass, null, TYPE_DEFAULT, null, null);
}
/**
* Create props from Actor creator
*
* @param clazz Actor class
* @param creator Actor creator class
* @param <T> Actor class
* @return Props object
*/
public static <T extends Actor> Props<T> create(Class<T> clazz, ActorCreator<T> creator) {
return new Props<T>(clazz, null, TYPE_CREATOR, null, creator);
}
}

View File

@ -0,0 +1,137 @@
package com.droidkit.actors;
import com.droidkit.actors.messages.NamedMessage;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
/**
* ReflectedActor is Actor that uses java reflection for processing of messages
* For each message developer must create method named "onReceive" with one argument
* with type of message
* For special message {@link com.droidkit.actors.messages.NamedMessage} you can create method
* named like {@code onDownloadReceive}. First letter in {@code Download} will be lowed and ReflectedActor
* will use this as {@code download} for name of message.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ReflectedActor extends Actor {
private ArrayList<Event> events = new ArrayList<Event>();
private ArrayList<NamedEvent> namedEvents = new ArrayList<NamedEvent>();
@Override
public final void preStart() {
Method[] methods = getClass().getDeclaredMethods();
for (Method m : methods) {
if (m.getName().startsWith("onReceive") && m.getParameterTypes().length == 1) {
if (m.getName().equals("onReceive") && m.getParameterTypes()[0] == Object.class) {
continue;
}
events.add(new Event(m.getParameterTypes()[0], m));
continue;
}
if (m.getName().startsWith("on") && m.getName().endsWith("Receive")) {
String methodName = m.getName();
String name = methodName.substring("on".length(), methodName.length() - "Receive".length());
if (name.length() > 0) {
name = name.substring(0, 1).toLowerCase() + name.substring(1);
namedEvents.add(new NamedEvent(name, m.getParameterTypes()[0], m));
continue;
}
}
}
preStartImpl();
}
/**
* Replacement for preStart
*/
public void preStartImpl() {
}
@Override
public void onReceive(Object message) {
if (message instanceof NamedMessage) {
NamedMessage named = (NamedMessage) message;
for (NamedEvent event : namedEvents) {
if (event.name.equals(named.getName())) {
if (event.check(named.getMessage())) {
try {
event.method.invoke(this, named.getMessage());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
for (Event event : events) {
if (event.check(message)) {
try {
event.method.invoke(this, message);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return;
}
}
}
class NamedEvent {
private String name;
private Class arg;
private Method method;
NamedEvent(String name, Class arg, Method method) {
this.name = name;
this.arg = arg;
this.method = method;
}
public String getName() {
return name;
}
public Class getArg() {
return arg;
}
public Method getMethod() {
return method;
}
public boolean check(Object obj) {
if (arg.isAssignableFrom(obj.getClass())) {
return true;
}
return false;
}
}
class Event {
private Class arg;
private Method method;
Event(Class arg, Method method) {
this.arg = arg;
this.method = method;
}
public boolean check(Object obj) {
if (arg.isAssignableFrom(obj.getClass())) {
return true;
}
return false;
}
}
}

View File

@ -0,0 +1,84 @@
package com.droidkit.actors.dispatch;
/**
* Queue for dispatching messages for {@link ThreadPoolDispatcher}.
* Implementation MUST BE thread-safe.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public abstract class AbstractDispatchQueue<T> {
/**
* Value used for result of waitDelay when dispatcher need to wait forever
*/
protected static final long FOREVER = Long.MAX_VALUE;
private QueueListener listener;
/**
* Fetch message for dispatching and removing it from dispatch queue
*
* @param time current time from ActorTime
* @return message or null if there is no message for processing
*/
public abstract T dispatch(long time);
/**
* Expected delay for nearest message.
* You might provide most accurate value as you can,
* this will minimize unnecessary thread work.
* For example, if you will return zero here then thread will
* loop continuously and consume processor time.
*
* @param time current time from ActorTime
* @return delay in ms
*/
public abstract long waitDelay(long time);
/**
* Implementation of adding message to queue
*
* @param message
* @param atTime
*/
protected abstract void putToQueueImpl(T message, long atTime);
/**
* Adding message to queue
*
* @param message message
* @param atTime time (use {@link com.droidkit.actors.ActorTime#currentTime()} for currentTime)
*/
public final void putToQueue(T message, long atTime) {
putToQueueImpl(message, atTime);
notifyQueueChanged();
}
/**
* Notification about queue change.
*/
protected void notifyQueueChanged() {
QueueListener lListener = listener;
if (lListener != null) {
lListener.onQueueChanged();
}
}
/**
* Getting of current queue listener
*
* @return queue listener
*/
public QueueListener getListener() {
return listener;
}
/**
* Setting queue listener
*
* @param listener queue listener
*/
public void setListener(QueueListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,47 @@
package com.droidkit.actors.dispatch;
/**
* Abstract thread dispatcher for messages
*/
public abstract class AbstractDispatcher<T, Q extends AbstractDispatchQueue<T>> {
final private Q queue;
final Dispatch<T> dispatch;
protected AbstractDispatcher(Q queue, Dispatch<T> dispatch) {
this.queue = queue;
this.dispatch = dispatch;
this.queue.setListener(new QueueListener() {
@Override
public void onQueueChanged() {
notifyDispatcher();
}
});
}
/**
* Queue used for dispatching
*
* @return queue
*/
public Q getQueue() {
return queue;
}
/**
* Actual execution of action
*
* @param message action
*/
protected void dispatchMessage(T message) {
if (dispatch != null) {
dispatch.dispatchMessage(message);
}
}
/**
* Notification about queue change
*/
protected void notifyDispatcher() {
}
}

View File

@ -0,0 +1,8 @@
package com.droidkit.actors.dispatch;
/**
* Used as callback for message processing
*/
public interface Dispatch<T> {
void dispatchMessage(T message);
}

View File

@ -0,0 +1,10 @@
package com.droidkit.actors.dispatch;
/**
* Listener for monitoring queue changes in dispatchers
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public interface QueueListener {
public void onQueueChanged();
}

View File

@ -0,0 +1,70 @@
package com.droidkit.actors.dispatch;
import static com.droidkit.actors.ActorTime.currentTime;
/**
* RunnableDispatcher is used for executing various Runnable in background
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class RunnableDispatcher extends ThreadPoolDispatcher<Runnable, SimpleDispatchQueue<Runnable>> {
/**
* Creating of dispatcher with one thread
*/
public RunnableDispatcher() {
this(1);
}
/**
* Creating of dispatcher with {@code threadsCount} threads
*
* @param threadsCount number of threads
*/
public RunnableDispatcher(int threadsCount) {
super(threadsCount, new SimpleDispatchQueue<Runnable>());
}
/**
* Creating of dispatcher with {@code threadsCount} threads and {@code priority}
*
* @param threadsCount number of threads
* @param priority priority of threads
*/
public RunnableDispatcher(int threadsCount, int priority) {
super(threadsCount, priority, new SimpleDispatchQueue<Runnable>());
}
@Override
protected void dispatchMessage(Runnable object) {
object.run();
}
/**
* Post action to queue
*
* @param action action
*/
public void postAction(Runnable action) {
postAction(action, 0);
}
/**
* Post action to queue with delay
*
* @param action action
* @param delay delay
*/
public void postAction(Runnable action, long delay) {
getQueue().putToQueue(action, currentTime() + delay);
}
/**
* Removing action from queue
*
* @param action action
*/
public void removeAction(Runnable action) {
getQueue().removeFromQueue(action);
}
}

View File

@ -0,0 +1,116 @@
package com.droidkit.actors.dispatch;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
/**
* Simple queue implementation for dispatchers
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class SimpleDispatchQueue<T> extends AbstractDispatchQueue<T> {
protected final TreeMap<Long, Message> messages = new TreeMap<Long, Message>();
protected final ArrayList<Message> freeMessages = new ArrayList<Message>();
/**
* Removing message from queue
*
* @param t message
*/
public void removeFromQueue(T t) {
synchronized (messages) {
for (Map.Entry<Long, Message> messageEntry : messages.entrySet()) {
if (messageEntry.getValue().equals(t)) {
Message message = messages.remove(messageEntry.getKey());
recycle(message);
notifyQueueChanged();
return;
}
}
}
}
@Override
public T dispatch(long time) {
synchronized (messages) {
if (messages.size() > 0) {
long firstKey = messages.firstKey();
if (firstKey < time) {
Message message = messages.remove(firstKey);
T res = message.action;
recycle(message);
return res;
}
}
}
return null;
}
@Override
public long waitDelay(long time) {
synchronized (messages) {
if (messages.size() > 0) {
long firstKey = messages.firstKey();
if (firstKey < time) {
return 0;
} else {
return time - firstKey;
}
}
}
return FOREVER;
}
@Override
public void putToQueueImpl(T action, long atTime) {
Message message = obtainMessage();
message.setMessage(action, atTime);
synchronized (messages) {
while (messages.containsKey(atTime)) {
atTime++;
}
messages.put(atTime, message);
}
}
/**
* Getting new message object for writing to queue
*
* @return Message object
*/
protected Message obtainMessage() {
synchronized (freeMessages) {
if (freeMessages.size() > 0) {
return freeMessages.remove(0);
}
}
return new Message();
}
/**
* Saving message object to free cache
*
* @param message Message object
*/
protected void recycle(Message message) {
synchronized (freeMessages) {
freeMessages.add(message);
}
}
/**
* Holder for messages
*/
protected class Message {
public long destTime;
public T action;
public void setMessage(T action, long destTime) {
this.action = action;
this.destTime = destTime;
}
}
}

View File

@ -0,0 +1,129 @@
package com.droidkit.actors.dispatch;
import static com.droidkit.actors.ActorTime.currentTime;
/**
* ThreadPoolDispatcher is used for dispatching messages on it's own threads.
* Class is completely thread-safe.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ThreadPoolDispatcher<T, Q extends AbstractDispatchQueue<T>> extends AbstractDispatcher<T, Q> {
private final Thread[] threads;
private boolean isClosed = false;
/**
* Dispatcher constructor. Create threads with NORM_PRIORITY.
*
* @param count thread count
* @param queue queue for messages
* (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information)
* @param dispatch Dispatch for message processing
*/
public ThreadPoolDispatcher(int count, Q queue, Dispatch<T> dispatch) {
this(count, Thread.NORM_PRIORITY, queue, dispatch);
}
/**
* Dispatcher constructor. Create threads with NORM_PRIORITY.
* Should override dispatchMessage for message processing.
*
* @param count thread count
* @param queue queue for messages
* (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information)
*/
public ThreadPoolDispatcher(int count, Q queue) {
this(count, Thread.NORM_PRIORITY, queue, null);
}
/**
* Dispatcher constructor. Create threads with NORM_PRIORITY.
* Should override dispatchMessage for message processing.
*
* @param count thread count
* @param priority thread priority
* @param queue queue for messages
* (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information)
*/
public ThreadPoolDispatcher(int count, int priority, Q queue) {
this(count, priority, queue, null);
}
/**
* Dispatcher constructor
*
* @param count thread count
* @param priority thread priority
* @param queue queue for messages
* (see {@link com.droidkit.actors.dispatch.AbstractDispatchQueue} for more information)
* @param dispatch Dispatch for message processing
*/
public ThreadPoolDispatcher(int count, int priority, final Q queue, Dispatch<T> dispatch) {
super(queue, dispatch);
this.threads = new Thread[count];
for (int i = 0; i < count; i++) {
this.threads[i] = new DispatcherThread();
this.threads[i].setPriority(priority);
this.threads[i].start();
}
}
/**
* Closing of dispatcher no one actions will be executed after calling this method.
*/
public void close() {
isClosed = true;
notifyDispatcher();
}
/**
* Notification about queue change
*/
@Override
protected void notifyDispatcher() {
if (threads != null) {
synchronized (threads) {
threads.notifyAll();
}
}
}
/**
* Thread class for dispatching
*/
private class DispatcherThread extends Thread {
@Override
public void run() {
while (!isClosed) {
T action = getQueue().dispatch(currentTime());
if (action == null) {
synchronized (threads) {
try {
long delay = getQueue().waitDelay(currentTime());
if (delay > 0) {
threads.wait(delay);
}
continue;
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
try {
dispatchMessage(action);
} catch (Throwable t) {
// Possibly danger situation, but i hope this will not corrupt JVM
// For example: on Android we could always continue execution after OutOfMemoryError
// Anyway, better to catch all errors manually in dispatchMessage
t.printStackTrace();
}
}
}
}
}

View File

@ -0,0 +1,131 @@
package com.droidkit.actors.mailbox;
import com.droidkit.actors.*;
import com.droidkit.actors.dispatch.AbstractDispatcher;
import com.droidkit.actors.messages.DeadLetter;
import com.droidkit.actors.messages.PoisonPill;
import com.droidkit.actors.messages.StartActor;
import java.util.HashMap;
import java.util.UUID;
/**
* Abstract Actor Dispatcher, used for dispatching messages for actors
*/
public abstract class AbsActorDispatcher {
private final HashMap<Mailbox, ActorScope> mailboxes = new HashMap<Mailbox, ActorScope>();
private final HashMap<String, ActorScope> scopes = new HashMap<String, ActorScope>();
private final HashMap<String, Props> actorProps = new HashMap<String, Props>();
private final ActorSystem actorSystem;
private AbstractDispatcher<Envelope, MailboxesQueue> dispatcher;
public AbsActorDispatcher(ActorSystem actorSystem) {
this.actorSystem = actorSystem;
}
protected void initDispatcher(AbstractDispatcher<Envelope, MailboxesQueue> dispatcher) {
if (this.dispatcher != null) {
throw new RuntimeException("Double dispatcher init");
}
this.dispatcher = dispatcher;
}
public final ActorScope createScope(String path, Props props) {
// TODO: add path check
Mailbox mailbox = new Mailbox(dispatcher.getQueue());
UUID uuid = UUID.randomUUID();
ActorRef ref = new ActorRef(actorSystem, this, uuid, path);
ActorScope scope = new ActorScope(actorSystem, mailbox, ref, this, UUID.randomUUID(), path, props);
synchronized (mailboxes) {
mailboxes.put(mailbox, scope);
scopes.put(scope.getPath(), scope);
actorProps.put(path, props);
}
// Sending init message
scope.getActorRef().send(StartActor.INSTANCE);
return scope;
}
public final void disconnectScope(ActorScope scope) {
synchronized (mailboxes) {
mailboxes.remove(scope.getMailbox());
scopes.remove(scope.getPath());
}
for (Envelope envelope : scope.getMailbox().allEnvelopes()) {
if (envelope.getSender() != null) {
envelope.getSender().send(new DeadLetter(envelope.getMessage()));
}
}
}
public final void sendMessage(String path, Object message, long time, ActorRef sender) {
synchronized (mailboxes) {
if (!scopes.containsKey(path)) {
if (sender != null) {
sender.send(new DeadLetter(message));
}
} else {
Mailbox mailbox = scopes.get(path).getMailbox();
mailbox.schedule(new Envelope(message, mailbox, sender), time);
}
}
}
public final void sendMessageOnce(String path, Object message, long time, ActorRef sender) {
synchronized (mailboxes) {
if (!scopes.containsKey(path)) {
if (sender != null) {
sender.send(new DeadLetter(message));
}
} else {
Mailbox mailbox = scopes.get(path).getMailbox();
mailbox.scheduleOnce(new Envelope(message, mailbox, sender), time);
}
}
}
/**
* Processing of envelope
*
* @param envelope envelope
*/
protected void processEnvelope(Envelope envelope) {
ActorScope actor = null;
synchronized (mailboxes) {
actor = mailboxes.get(envelope.getMailbox());
}
if (actor == null) {
//TODO: add logging
return;
}
try {
if (envelope.getMessage() == StartActor.INSTANCE) {
try {
actor.createActor();
} catch (Exception e) {
e.printStackTrace();
}
} else if (envelope.getMessage() == PoisonPill.INSTANCE) {
try {
actor.shutdownActor();
} catch (Exception e) {
e.printStackTrace();
}
} else {
CurrentActor.setCurrentActor(actor.getActor());
actor.setSender(envelope.getSender());
actor.getActor().onReceiveGlobal(envelope.getMessage());
}
} finally {
dispatcher.getQueue().unlockMailbox(envelope.getMailbox());
CurrentActor.setCurrentActor(null);
}
}
}

View File

@ -0,0 +1,25 @@
package com.droidkit.actors.mailbox;
import com.droidkit.actors.ActorSystem;
import com.droidkit.actors.dispatch.Dispatch;
import com.droidkit.actors.dispatch.ThreadPoolDispatcher;
/**
* Basic ActorDispatcher backed by ThreadPoolDispatcher
*/
public class ActorDispatcher extends AbsActorDispatcher {
public ActorDispatcher(ActorSystem actorSystem, int threadsCount) {
this(actorSystem, threadsCount, Thread.MIN_PRIORITY);
}
public ActorDispatcher(ActorSystem actorSystem, int threadsCount, int priority) {
super(actorSystem);
initDispatcher(new ThreadPoolDispatcher<Envelope, MailboxesQueue>(threadsCount, priority, new MailboxesQueue(),
new Dispatch<Envelope>() {
@Override
public void dispatchMessage(Envelope message) {
processEnvelope(message);
}
}));
}
}

View File

@ -0,0 +1,54 @@
package com.droidkit.actors.mailbox;
import com.droidkit.actors.ActorRef;
/**
* Actor system envelope
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class Envelope {
private final Object message;
private final ActorRef sender;
private final Mailbox mailbox;
/**
* Creating of envelope
*
* @param message message
* @param mailbox mailbox
* @param sender sender reference
*/
public Envelope(Object message, Mailbox mailbox, ActorRef sender) {
this.message = message;
this.sender = sender;
this.mailbox = mailbox;
}
/**
* Message in envelope
*
* @return message
*/
public Object getMessage() {
return message;
}
/**
* Mailbox for envelope
*
* @return mailbox
*/
public Mailbox getMailbox() {
return mailbox;
}
/**
* Sender of message
*
* @return sender reference
*/
public ActorRef getSender() {
return sender;
}
}

View File

@ -0,0 +1,88 @@
package com.droidkit.actors.mailbox;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Actor mailbox, queue of envelopes.
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class Mailbox {
private final ConcurrentHashMap<Long, Envelope> envelopes = new ConcurrentHashMap<Long, Envelope>();
private MailboxesQueue queue;
/**
* Creating mailbox
*
* @param queue MailboxesQueue
*/
public Mailbox(MailboxesQueue queue) {
this.queue = queue;
}
/**
* Send envelope at time
*
* @param envelope envelope
* @param time time
*/
public void schedule(Envelope envelope, long time) {
if (envelope.getMailbox() != this) {
throw new RuntimeException("envelope.mailbox != this mailbox");
}
long id = queue.sendEnvelope(envelope, time);
envelopes.put(id, envelope);
}
/**
* Send envelope once at time
*
* @param envelope envelope
* @param time time
*/
public void scheduleOnce(Envelope envelope, long time) {
if (envelope.getMailbox() != this) {
throw new RuntimeException("envelope.mailbox != this mailbox");
}
Iterator<Map.Entry<Long, Envelope>> iterator = envelopes.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Long, Envelope> entry = iterator.next();
if (isEqualEnvelope(entry.getValue(), envelope)) {
queue.removeEnvelope(entry.getKey());
iterator.remove();
}
}
schedule(envelope, time);
}
void removeEnvelope(long key) {
envelopes.remove(key);
}
/**
* Override this if you need to change filtering for scheduleOnce behaviour.
* By default it check equality only of class names.
*
* @param a
* @param b
* @return is equal
*/
protected boolean isEqualEnvelope(Envelope a, Envelope b) {
return a.getMessage().getClass() == b.getMessage().getClass();
}
public synchronized Envelope[] allEnvelopes() {
return envelopes.values().toArray(new Envelope[0]);
}
public synchronized int getMailboxSize() {
return envelopes.size();
}
}

View File

@ -0,0 +1,145 @@
package com.droidkit.actors.mailbox;
import com.droidkit.actors.dispatch.AbstractDispatchQueue;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Queue of multiple mailboxes for MailboxesDispatcher
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class MailboxesQueue extends AbstractDispatchQueue<Envelope> {
private static final long MULTIPLE = 10000L;
private final TreeMap<Long, Long> timeShift = new TreeMap<Long, Long>();
private final TreeMap<Long, Envelope> envelopes = new TreeMap<Long, Envelope>();
private final HashSet<Mailbox> blocked = new HashSet<Mailbox>();
/**
* Locking mailbox from processing messages from it
*
* @param mailbox mailbox for locking
*/
public void lockMailbox(Mailbox mailbox) {
synchronized (blocked) {
blocked.add(mailbox);
}
notifyQueueChanged();
}
/**
* Unlocking mailbox
*
* @param mailbox mailbox for unlocking
*/
public void unlockMailbox(Mailbox mailbox) {
synchronized (blocked) {
blocked.remove(mailbox);
}
notifyQueueChanged();
}
/**
* Sending envelope
*
* @param envelope envelope
* @param time time (see {@link com.droidkit.actors.ActorTime#currentTime()}})
* @return envelope real time
*/
public long sendEnvelope(Envelope envelope, long time) {
long shift = 0;
synchronized (envelopes) {
if (timeShift.containsKey(time)) {
shift = timeShift.get(time);
}
while (envelopes.containsKey(time * MULTIPLE + shift)) {
shift++;
}
if (shift != 0) {
timeShift.put(time, shift);
}
envelopes.put(time * MULTIPLE + shift, envelope);
}
notifyQueueChanged();
return time * MULTIPLE + shift;
}
/**
* Removing envelope from queue
*
* @param id envelope id
*/
public void removeEnvelope(long id) {
synchronized (envelopes) {
envelopes.remove(id);
}
notifyQueueChanged();
}
/**
* getting first available envelope
* MUST BE wrapped with envelopes and blocked sync
*
* @return envelope entry
*/
private Map.Entry<Long, Envelope> firstEnvelope() {
for (Map.Entry<Long, Envelope> entry : envelopes.entrySet()) {
if (!blocked.contains(entry.getValue().getMailbox())) {
return entry;
}
}
return null;
}
@Override
public Envelope dispatch(long time) {
time = time * MULTIPLE;
synchronized (envelopes) {
synchronized (blocked) {
Map.Entry<Long, Envelope> envelope = firstEnvelope();
if (envelope != null) {
if (envelope.getKey() < time) {
envelopes.remove(envelope.getKey());
envelope.getValue().getMailbox().removeEnvelope(envelope.getKey());
//TODO: Better design
// Locking of mailbox before dispatch return
blocked.add(envelope.getValue().getMailbox());
return envelope.getValue();
}
}
}
}
return null;
}
@Override
public long waitDelay(long time) {
time = time * MULTIPLE;
synchronized (envelopes) {
synchronized (blocked) {
Map.Entry<Long, Envelope> envelope = firstEnvelope();
if (envelope != null) {
if (envelope.getKey() <= time) {
return 0;
} else {
return (time - envelope.getKey()) / MULTIPLE;
}
}
}
}
return FOREVER;
}
@Override
protected void putToQueueImpl(Envelope message, long atTime) {
sendEnvelope(message, atTime);
}
}

View File

@ -0,0 +1,21 @@
package com.droidkit.actors.messages;
/**
* DeadLetter sent whet message was not processed by target actor
*/
public class DeadLetter {
private Object message;
public DeadLetter(Object message) {
this.message = message;
}
public Object getMessage() {
return message;
}
@Override
public String toString() {
return "DeadLetter(" + message + ")";
}
}

View File

@ -0,0 +1,22 @@
package com.droidkit.actors.messages;
/**
* Simple named message
*/
public class NamedMessage {
private String name;
private Object message;
public NamedMessage(String name, Object message) {
this.name = name;
this.message = message;
}
public String getName() {
return name;
}
public Object getMessage() {
return message;
}
}

View File

@ -0,0 +1,13 @@
package com.droidkit.actors.messages;
/**
* PoisonPill message for killing actors
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public final class PoisonPill {
public static final PoisonPill INSTANCE = new PoisonPill();
private PoisonPill() {
}
}

View File

@ -0,0 +1,13 @@
package com.droidkit.actors.messages;
/**
* Message for starting actors
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public final class StartActor {
public static final StartActor INSTANCE = new StartActor();
private StartActor() {
}
}

View File

@ -0,0 +1,165 @@
package com.droidkit.actors.tasks;
import com.droidkit.actors.ActorRef;
import com.droidkit.actors.messages.DeadLetter;
import com.droidkit.actors.tasks.messages.*;
import java.util.HashMap;
/**
* Implementation of Ask pattern
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class ActorAskImpl {
private HashMap<Integer, AskContainer> asks = new HashMap<Integer, AskContainer>();
private int nextReqId = 1;
private ActorRef self;
public ActorAskImpl(ActorRef self) {
this.self = self;
}
public <T> AskFuture<T[]> combine(AskFuture... futures) {
final AskFuture resultFuture = new AskFuture(this, 0);
final CombineContainer container = new CombineContainer(futures.length);
for (int i = 0; i < futures.length; i++) {
final int index = i;
container.futures[index] = futures[index];
container.callbacks[index] = new AskCallback() {
@Override
public void onResult(Object result) {
container.completed[index] = true;
container.results[index] = result;
boolean isCompleted = true;
for (boolean c : container.completed) {
if (!c) {
isCompleted = false;
break;
}
}
if (isCompleted && !container.isCompleted) {
container.isCompleted = true;
for (int i = 0; i < container.futures.length; i++) {
container.futures[i].removeListener(container.callbacks[i]);
}
resultFuture.onResult(container.results);
}
}
@Override
public void onError(Throwable throwable) {
if (!container.isCompleted) {
container.isCompleted = true;
for (int i = 0; i < container.futures.length; i++) {
container.futures[i].removeListener(container.callbacks[i]);
container.futures[i].cancel();
}
resultFuture.onError(throwable);
}
}
};
container.futures[index].addListener(container.callbacks[index]);
}
return resultFuture;
}
public <T> AskFuture<T> ask(ActorRef ref, long timeout, AskCallback<T> callback) {
int reqId = nextReqId++;
AskFuture<T> future = new AskFuture<T>(this, reqId);
if (callback != null) {
future.addListener(callback);
}
AskContainer container = new AskContainer(future, ref, reqId);
asks.put(reqId, container);
ref.send(new TaskRequest(reqId), self);
if (timeout > 0) {
self.send(new TaskTimeout(reqId), timeout);
}
return future;
}
public boolean onTaskResult(TaskResult result) {
AskContainer container = asks.remove(result.getRequestId());
if (container != null) {
container.future.onResult(result.getRes());
return true;
}
return false;
}
public boolean onTaskError(TaskError error) {
AskContainer container = asks.remove(error.getRequestId());
if (container != null) {
container.future.onError(error.getThrowable());
return true;
}
return false;
}
public boolean onTaskTimeout(TaskTimeout taskTimeout) {
AskContainer container = asks.remove(taskTimeout.getRequestId());
if (container != null) {
container.future.onTimeout();
return true;
}
return false;
}
public boolean onTaskCancelled(int reqId) {
AskContainer container = asks.remove(reqId);
if (container != null) {
container.ref.send(new TaskCancel(reqId), self);
return true;
}
return false;
}
public boolean onDeadLetter(DeadLetter letter) {
if (letter.getMessage() instanceof TaskRequest) {
TaskRequest request = (TaskRequest) letter.getMessage();
AskContainer container = asks.remove(request.getRequestId());
if (container != null) {
// Mimic dead letter with timeout exception
container.future.onError(new AskTimeoutException());
return true;
}
}
return false;
}
private class AskContainer {
public final AskFuture future;
public final ActorRef ref;
public final int requestId;
private AskContainer(AskFuture future, ActorRef ref, int requestId) {
this.future = future;
this.ref = ref;
this.requestId = requestId;
}
}
private class CombineContainer {
public boolean isCompleted = false;
public Object[] results;
public boolean[] completed;
public AskFuture[] futures;
public AskCallback[] callbacks;
public CombineContainer(int count) {
results = new Object[count];
completed = new boolean[count];
callbacks = new AskCallback[count];
futures = new AskFuture[count];
}
}
}

View File

@ -0,0 +1,12 @@
package com.droidkit.actors.tasks;
/**
* Callback for Ask pattern
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public interface AskCallback<T> {
public void onResult(T result);
public void onError(Throwable throwable);
}

View File

@ -0,0 +1,10 @@
package com.droidkit.actors.tasks;
/**
* Exception about cancelling task
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class AskCancelledException extends Exception {
}

View File

@ -0,0 +1,105 @@
package com.droidkit.actors.tasks;
import java.util.LinkedList;
/**
* Modified future for ask pattern of Actors
* Created to work only in actor threads
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class AskFuture<T> {
private LinkedList<AskCallback> callbacks = new LinkedList<AskCallback>();
private ActorAskImpl askImpl;
private int reqId;
private boolean isCompleted = false;
private boolean isCanceled = false;
private boolean isError = false;
private T result = null;
private Throwable error = null;
AskFuture(ActorAskImpl askImpl, int reqId) {
this.askImpl = askImpl;
this.reqId = reqId;
}
public void addListener(AskCallback callback) {
callbacks.add(callback);
}
public void removeListener(AskCallback callback) {
callbacks.remove(callback);
}
public boolean isCompleted() {
return isCompleted;
}
public boolean isError() {
return isError;
}
public boolean isCanceled() {
return isCanceled;
}
public Throwable error() {
return error;
}
public T result() {
return result;
}
public void cancel() {
if (isCompleted) {
return;
}
isCompleted = true;
isError = false;
isCanceled = true;
for (AskCallback callback : callbacks) {
callback.onError(new AskCancelledException());
}
askImpl.onTaskCancelled(reqId);
}
void onError(Throwable throwable) {
if (isCompleted) {
return;
}
isCompleted = true;
isCanceled = false;
isError = true;
error = throwable;
result = null;
for (AskCallback callback : callbacks) {
callback.onError(throwable);
}
}
void onResult(T res) {
if (isCompleted) {
return;
}
isCompleted = true;
isCanceled = false;
isError = false;
error = null;
result = res;
for (AskCallback callback : callbacks) {
callback.onResult(res);
}
}
void onTimeout() {
onError(new AskTimeoutException());
}
}

View File

@ -0,0 +1,9 @@
package com.droidkit.actors.tasks;
/**
* Exception for Ask timeout
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class AskTimeoutException extends Exception {
}

View File

@ -0,0 +1,177 @@
package com.droidkit.actors.tasks;
import com.droidkit.actors.Actor;
import com.droidkit.actors.ActorRef;
import com.droidkit.actors.messages.PoisonPill;
import com.droidkit.actors.tasks.messages.TaskCancel;
import com.droidkit.actors.tasks.messages.TaskError;
import com.droidkit.actors.tasks.messages.TaskRequest;
import com.droidkit.actors.tasks.messages.TaskResult;
import java.util.HashSet;
/**
* Actor for performing various async tasks
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public abstract class TaskActor<T> extends Actor {
private final HashSet<TaskListener> requests = new HashSet<TaskListener>();
private T result;
private boolean isCompleted;
private boolean isCompletedSuccess;
private long dieTimeout = 300;
/**
* Timeout for dying after task complete
*
* @param timeOut timeout in ms
*/
public void setTimeOut(long timeOut) {
dieTimeout = timeOut;
}
@Override
public void preStart() {
startTask();
}
@Override
public void onReceive(Object message) {
if (message instanceof TaskRequest) {
TaskRequest request = (TaskRequest) message;
if (isCompleted) {
if (isCompletedSuccess) {
reply(result);
}
} else {
TaskListener listener = new TaskListener(request.getRequestId(), sender());
requests.add(listener);
}
} else if (message instanceof TaskCancel) {
if (isCompleted) {
return;
}
TaskCancel cancel = (TaskCancel) message;
TaskListener listener = new TaskListener(cancel.getRequestId(), sender());
requests.remove(listener);
if (requests.size() == 0) {
onTaskObsolete();
context().stopSelf();
}
} else if (message instanceof Result) {
if (!isCompleted) {
Result res = (Result) message;
isCompleted = true;
isCompletedSuccess = true;
result = (T) res.getRes();
for (TaskListener request : requests) {
request.getSender().send(new TaskResult<T>(request.getRequestId(), result));
}
self().send(PoisonPill.INSTANCE, dieTimeout);
}
} else if (message instanceof Error) {
if (!isCompleted) {
isCompleted = true;
Error error = (Error) message;
for (TaskListener request : requests) {
request.getSender().send(new TaskError(request.getRequestId(), error.getError()));
}
context().stopSelf();
}
}
}
/**
* Starting of task execution
*/
public abstract void startTask();
/**
* Called before killing actor after clearing TaskListeners
*/
public void onTaskObsolete() {
}
/**
* Call this method in any thread after task complete
*
* @param res result of task
*/
public void complete(T res) {
self().send(new Result(res));
}
/**
* Call this method in any thread after task exception
*
* @param t exception
*/
public void error(Throwable t) {
self().send(new Error(t));
}
private static class Error {
private Throwable error;
private Error(Throwable error) {
this.error = error;
}
public Throwable getError() {
return error;
}
}
private static class Result {
private Object res;
private Result(Object res) {
this.res = res;
}
public Object getRes() {
return res;
}
}
private static class TaskListener {
private int requestId;
private ActorRef sender;
private TaskListener(int requestId, ActorRef sender) {
this.requestId = requestId;
this.sender = sender;
}
public int getRequestId() {
return requestId;
}
public ActorRef getSender() {
return sender;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TaskListener that = (TaskListener) o;
if (requestId != that.requestId) return false;
if (!sender.equals(that.sender)) return false;
return true;
}
@Override
public int hashCode() {
int result = requestId;
result = 31 * result + sender.hashCode();
return result;
}
}
}

View File

@ -0,0 +1,18 @@
package com.droidkit.actors.tasks.messages;
/**
* Message about task cancelling
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class TaskCancel {
private int requestId;
public TaskCancel(int requestId) {
this.requestId = requestId;
}
public int getRequestId() {
return requestId;
}
}

View File

@ -0,0 +1,24 @@
package com.droidkit.actors.tasks.messages;
/**
* Message about task error
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class TaskError {
private final int requestId;
private final Throwable throwable;
public TaskError(int requestId, Throwable throwable) {
this.requestId = requestId;
this.throwable = throwable;
}
public int getRequestId() {
return requestId;
}
public Throwable getThrowable() {
return throwable;
}
}

View File

@ -0,0 +1,18 @@
package com.droidkit.actors.tasks.messages;
/**
* Message about requesting task
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class TaskRequest {
private final int requestId;
public TaskRequest(int requestId) {
this.requestId = requestId;
}
public int getRequestId() {
return requestId;
}
}

View File

@ -0,0 +1,25 @@
package com.droidkit.actors.tasks.messages;
/**
* Message with task result
*
* @param <T> type of task result
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class TaskResult<T> {
private final int requestId;
private final T res;
public TaskResult(int requestId, T res) {
this.requestId = requestId;
this.res = res;
}
public T getRes() {
return res;
}
public int getRequestId() {
return requestId;
}
}

View File

@ -0,0 +1,18 @@
package com.droidkit.actors.tasks.messages;
/**
* Message about Task timeout
*
* @author Stepan Ex3NDR Korshakov (me@ex3ndr.com)
*/
public class TaskTimeout {
private final int requestId;
public TaskTimeout(int requestId) {
this.requestId = requestId;
}
public int getRequestId() {
return requestId;
}
}