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:
297
src/com/droidkit/actors/Actor.java
Normal file
297
src/com/droidkit/actors/Actor.java
Normal 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);
|
||||
}
|
||||
}
|
59
src/com/droidkit/actors/ActorContext.java
Normal file
59
src/com/droidkit/actors/ActorContext.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
15
src/com/droidkit/actors/ActorCreator.java
Normal file
15
src/com/droidkit/actors/ActorCreator.java
Normal 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();
|
||||
}
|
121
src/com/droidkit/actors/ActorRef.java
Normal file
121
src/com/droidkit/actors/ActorRef.java
Normal 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);
|
||||
}
|
||||
}
|
128
src/com/droidkit/actors/ActorScope.java
Normal file
128
src/com/droidkit/actors/ActorScope.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
24
src/com/droidkit/actors/ActorSelection.java
Normal file
24
src/com/droidkit/actors/ActorSelection.java
Normal 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;
|
||||
}
|
||||
}
|
129
src/com/droidkit/actors/ActorSystem.java
Normal file
129
src/com/droidkit/actors/ActorSystem.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
src/com/droidkit/actors/ActorTime.java
Normal file
27
src/com/droidkit/actors/ActorTime.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
19
src/com/droidkit/actors/CurrentActor.java
Normal file
19
src/com/droidkit/actors/CurrentActor.java
Normal 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();
|
||||
}
|
||||
}
|
88
src/com/droidkit/actors/Props.java
Normal file
88
src/com/droidkit/actors/Props.java
Normal 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);
|
||||
}
|
||||
}
|
137
src/com/droidkit/actors/ReflectedActor.java
Normal file
137
src/com/droidkit/actors/ReflectedActor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
84
src/com/droidkit/actors/dispatch/AbstractDispatchQueue.java
Normal file
84
src/com/droidkit/actors/dispatch/AbstractDispatchQueue.java
Normal 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;
|
||||
}
|
||||
}
|
47
src/com/droidkit/actors/dispatch/AbstractDispatcher.java
Normal file
47
src/com/droidkit/actors/dispatch/AbstractDispatcher.java
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
8
src/com/droidkit/actors/dispatch/Dispatch.java
Normal file
8
src/com/droidkit/actors/dispatch/Dispatch.java
Normal file
@ -0,0 +1,8 @@
|
||||
package com.droidkit.actors.dispatch;
|
||||
|
||||
/**
|
||||
* Used as callback for message processing
|
||||
*/
|
||||
public interface Dispatch<T> {
|
||||
void dispatchMessage(T message);
|
||||
}
|
10
src/com/droidkit/actors/dispatch/QueueListener.java
Normal file
10
src/com/droidkit/actors/dispatch/QueueListener.java
Normal 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();
|
||||
}
|
70
src/com/droidkit/actors/dispatch/RunnableDispatcher.java
Normal file
70
src/com/droidkit/actors/dispatch/RunnableDispatcher.java
Normal 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);
|
||||
}
|
||||
}
|
116
src/com/droidkit/actors/dispatch/SimpleDispatchQueue.java
Normal file
116
src/com/droidkit/actors/dispatch/SimpleDispatchQueue.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
129
src/com/droidkit/actors/dispatch/ThreadPoolDispatcher.java
Normal file
129
src/com/droidkit/actors/dispatch/ThreadPoolDispatcher.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
131
src/com/droidkit/actors/mailbox/AbsActorDispatcher.java
Normal file
131
src/com/droidkit/actors/mailbox/AbsActorDispatcher.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
25
src/com/droidkit/actors/mailbox/ActorDispatcher.java
Normal file
25
src/com/droidkit/actors/mailbox/ActorDispatcher.java
Normal 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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
54
src/com/droidkit/actors/mailbox/Envelope.java
Normal file
54
src/com/droidkit/actors/mailbox/Envelope.java
Normal 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;
|
||||
}
|
||||
}
|
88
src/com/droidkit/actors/mailbox/Mailbox.java
Normal file
88
src/com/droidkit/actors/mailbox/Mailbox.java
Normal 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();
|
||||
}
|
||||
}
|
145
src/com/droidkit/actors/mailbox/MailboxesQueue.java
Normal file
145
src/com/droidkit/actors/mailbox/MailboxesQueue.java
Normal 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);
|
||||
}
|
||||
}
|
21
src/com/droidkit/actors/messages/DeadLetter.java
Normal file
21
src/com/droidkit/actors/messages/DeadLetter.java
Normal 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 + ")";
|
||||
}
|
||||
}
|
22
src/com/droidkit/actors/messages/NamedMessage.java
Normal file
22
src/com/droidkit/actors/messages/NamedMessage.java
Normal 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;
|
||||
}
|
||||
}
|
13
src/com/droidkit/actors/messages/PoisonPill.java
Normal file
13
src/com/droidkit/actors/messages/PoisonPill.java
Normal 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() {
|
||||
}
|
||||
}
|
13
src/com/droidkit/actors/messages/StartActor.java
Normal file
13
src/com/droidkit/actors/messages/StartActor.java
Normal 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() {
|
||||
}
|
||||
}
|
165
src/com/droidkit/actors/tasks/ActorAskImpl.java
Normal file
165
src/com/droidkit/actors/tasks/ActorAskImpl.java
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
12
src/com/droidkit/actors/tasks/AskCallback.java
Normal file
12
src/com/droidkit/actors/tasks/AskCallback.java
Normal 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);
|
||||
}
|
10
src/com/droidkit/actors/tasks/AskCancelledException.java
Normal file
10
src/com/droidkit/actors/tasks/AskCancelledException.java
Normal 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 {
|
||||
|
||||
}
|
105
src/com/droidkit/actors/tasks/AskFuture.java
Normal file
105
src/com/droidkit/actors/tasks/AskFuture.java
Normal 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());
|
||||
}
|
||||
}
|
9
src/com/droidkit/actors/tasks/AskTimeoutException.java
Normal file
9
src/com/droidkit/actors/tasks/AskTimeoutException.java
Normal 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 {
|
||||
}
|
177
src/com/droidkit/actors/tasks/TaskActor.java
Normal file
177
src/com/droidkit/actors/tasks/TaskActor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
18
src/com/droidkit/actors/tasks/messages/TaskCancel.java
Normal file
18
src/com/droidkit/actors/tasks/messages/TaskCancel.java
Normal 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;
|
||||
}
|
||||
}
|
24
src/com/droidkit/actors/tasks/messages/TaskError.java
Normal file
24
src/com/droidkit/actors/tasks/messages/TaskError.java
Normal 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;
|
||||
}
|
||||
}
|
18
src/com/droidkit/actors/tasks/messages/TaskRequest.java
Normal file
18
src/com/droidkit/actors/tasks/messages/TaskRequest.java
Normal 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;
|
||||
}
|
||||
}
|
25
src/com/droidkit/actors/tasks/messages/TaskResult.java
Normal file
25
src/com/droidkit/actors/tasks/messages/TaskResult.java
Normal 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;
|
||||
}
|
||||
}
|
18
src/com/droidkit/actors/tasks/messages/TaskTimeout.java
Normal file
18
src/com/droidkit/actors/tasks/messages/TaskTimeout.java
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user