diff --git a/Java/actor-closure/Actor.java b/Java/actor-closure/Actor.java new file mode 100644 index 0000000..39de7b8 --- /dev/null +++ b/Java/actor-closure/Actor.java @@ -0,0 +1,7 @@ +import java.util.concurrent.CompletableFuture; + +public interface Actor { + void process(); + + CompletableFuture send(String methodName, Object... args); +} diff --git a/Java/actor-closure/ActorClosureFactory.java b/Java/actor-closure/ActorClosureFactory.java new file mode 100644 index 0000000..2aab38f --- /dev/null +++ b/Java/actor-closure/ActorClosureFactory.java @@ -0,0 +1,70 @@ +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ActorClosureFactory { + private static Class[] convertParamTypes(Object... args) { + Class[] types = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + types[i] = args[i].getClass(); + } + + return types; + } + + private static T instantiate(Class clazz, Object... args) { + Class[] paramTypes = convertParamTypes(args); + + try { + return clazz + .getConstructor(paramTypes) + .newInstance(args); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not instantiate " + clazz, e); + } + } + + public static Actor createActor(Class clazz, Object... args) { + final AtomicBoolean isProcessing = new AtomicBoolean(false); + final Queue queue = new ConcurrentLinkedQueue<>(); + final T state = instantiate(clazz, args); + + return new Actor() { + @Override + public void process() { + new Thread(() -> { + if (!isProcessing.compareAndSet(false, true)) + return; + + while (!queue.isEmpty()) { + Operation operation = queue.poll(); + CompletableFuture resolve = operation.resolve(); + + try { + Method method = state.getClass().getMethod(operation.method(), convertParamTypes(operation.args())); + Object result = method.invoke(state, operation.args()); + resolve.complete(result); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + resolve.completeExceptionally(e); + } + } + + isProcessing.set(false); + }).start(); + } + + @Override + public CompletableFuture send(String method, Object... args) { + CompletableFuture resultReference = new CompletableFuture<>(); + + queue.add(new Operation(method, args, resultReference)); + this.process(); + + return resultReference; + } + }; + } +} diff --git a/Java/actor-closure/Operation.java b/Java/actor-closure/Operation.java new file mode 100644 index 0000000..99dbb83 --- /dev/null +++ b/Java/actor-closure/Operation.java @@ -0,0 +1,7 @@ +import java.util.concurrent.CompletableFuture; + +public record Operation( + String method, + Object[] args, + CompletableFuture resolve +) {} diff --git a/Java/actor-closure/Point.java b/Java/actor-closure/Point.java new file mode 100644 index 0000000..d956caa --- /dev/null +++ b/Java/actor-closure/Point.java @@ -0,0 +1,23 @@ +public class Point { + private int x; + private int y; + + public Point(Integer x, Integer y) { + this.x = x; + this.y = y; + } + + public void move(Integer dx, Integer dy) { + this.x += dx; + this.y += dy; + } + + public Actor cloneAsActor() { + return ActorClosureFactory.createActor(Point.class, this.x, this.y); + } + + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } +} diff --git a/Java/actor-closure/Runner.java b/Java/actor-closure/Runner.java new file mode 100644 index 0000000..cf98795 --- /dev/null +++ b/Java/actor-closure/Runner.java @@ -0,0 +1,31 @@ +import java.util.concurrent.ExecutionException; + +public class Runner { + private static final String OUTPUT_METHOD = "toString"; + private static final String MOVE_METHOD = "move"; + private static final String CLONE_METHOD = "cloneAsActor"; + + public static void main(String[] args) throws ExecutionException, InterruptedException { + Actor actor = ActorClosureFactory.createActor(Point.class, 10, 20); + + // "toString" function must return string with coords + String output = (String) actor.send(OUTPUT_METHOD).get(); + System.out.println(output); // "(10, 20)" + + // "cloneAsActor" function must create another actor + Actor clone = (Actor) actor.send(CLONE_METHOD).get(); + System.out.println(actor == clone); // false + + // "move" function should not return anything + Object move = clone.send(MOVE_METHOD, -5, 10).get(); + System.out.println(move); // null + + // "toString" on CLONED object must return changed coords + String movedClonedOutput = (String) clone.send(OUTPUT_METHOD).get(); + System.out.println(movedClonedOutput); // "(5, 30)" + + // "toString" on SOURCE object must return original unchanged coords + String movedSourceOutput = (String) actor.send(OUTPUT_METHOD).get(); + System.out.println(movedSourceOutput); // "(10, 20)" + } +}