From 2aa7fe0c1ca405b2559fd0da5d902955726b68c4 Mon Sep 17 00:00:00 2001 From: Patrick Zimmer Date: Sun, 1 Oct 2023 14:44:41 +0200 Subject: [PATCH] leanup tests --- .../paxel/lintstone/impl/ActorSystem.java | 14 ++- .../paxel/lintstone/api/ActorSortTest.java | 3 +- .../java/paxel/lintstone/api/EndMessage.java | 8 -- .../paxel/lintstone/api/ExternalAskTest.java | 42 +------ .../paxel/lintstone/api/FailingTests.java | 81 +------------ .../paxel/lintstone/api/InternalAskTest.java | 82 +------------ .../lintstone/api/LintStoneSystemTest.java | 3 + .../api/{ => actors}/AdderActor.java | 20 ++-- .../paxel/lintstone/api/actors/CharCount.java | 25 ++++ .../lintstone/api/actors/Distributor.java | 45 +++++++ .../paxel/lintstone/api/actors/Md5Actor.java | 59 ++++++++++ .../api/{ => actors}/SortNodeActor.java | 7 +- .../paxel/lintstone/api/actors/Sorter.java | 32 +++++ .../lintstone/api/actors/StupidActor.java | 87 ++++++++++++++ .../lintstone/api/{ => actors}/SumActor.java | 15 ++- .../paxel/lintstone/api/actors/WordCount.java | 28 +++++ .../api/example/IdProviderActor.java | 78 +++++++++++++ .../api/example/WordGeneratorActor.java | 110 ++++++++++++++++++ .../lintstone/api/example/utils/Snippets.java | 37 ++++++ .../api/{ => messages}/DieMessage.java | 5 +- .../lintstone/api/messages/EndMessage.java | 8 ++ 21 files changed, 564 insertions(+), 225 deletions(-) delete mode 100644 src/test/java/paxel/lintstone/api/EndMessage.java rename src/test/java/paxel/lintstone/api/{ => actors}/AdderActor.java (70%) create mode 100644 src/test/java/paxel/lintstone/api/actors/CharCount.java create mode 100644 src/test/java/paxel/lintstone/api/actors/Distributor.java create mode 100644 src/test/java/paxel/lintstone/api/actors/Md5Actor.java rename src/test/java/paxel/lintstone/api/{ => actors}/SortNodeActor.java (93%) create mode 100644 src/test/java/paxel/lintstone/api/actors/Sorter.java create mode 100644 src/test/java/paxel/lintstone/api/actors/StupidActor.java rename src/test/java/paxel/lintstone/api/{ => actors}/SumActor.java (67%) create mode 100644 src/test/java/paxel/lintstone/api/actors/WordCount.java create mode 100644 src/test/java/paxel/lintstone/api/example/IdProviderActor.java create mode 100644 src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java create mode 100644 src/test/java/paxel/lintstone/api/example/utils/Snippets.java rename src/test/java/paxel/lintstone/api/{ => messages}/DieMessage.java (61%) create mode 100644 src/test/java/paxel/lintstone/api/messages/EndMessage.java diff --git a/src/main/java/paxel/lintstone/impl/ActorSystem.java b/src/main/java/paxel/lintstone/impl/ActorSystem.java index 316022d..22082de 100644 --- a/src/main/java/paxel/lintstone/impl/ActorSystem.java +++ b/src/main/java/paxel/lintstone/impl/ActorSystem.java @@ -53,13 +53,13 @@ private LintStoneActorAccessor registerActor(String name, LintStoneActorFactory @Override public void shutDown() { - actors.entrySet().stream().map(Map.Entry::getValue).forEach(a -> a.shutdown(false)); + shutdownActors(false); groupingExecutor.shutdown(); } @Override public void shutDownAndWait() throws InterruptedException { - actors.entrySet().stream().map(Map.Entry::getValue).forEach(a -> a.shutdown(false)); + shutdownActors(false); groupingExecutor.shutdown(); //wait forever and a day groupingExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS); @@ -67,17 +67,23 @@ public void shutDownAndWait() throws InterruptedException { @Override public boolean shutDownAndWait(Duration timeout) throws InterruptedException { - actors.entrySet().stream().map(Map.Entry::getValue).forEach(a -> a.shutdown(false)); + shutdownActors(false); groupingExecutor.shutdown(); return groupingExecutor.awaitTermination(timeout.getSeconds(), TimeUnit.SECONDS); } @Override public void shutDownNow() { - actors.entrySet().stream().map(Map.Entry::getValue).forEach(a -> a.shutdown(true)); + shutdownActors(true); groupingExecutor.shutdownNow(); } + private void shutdownActors(boolean now) { + synchronized (actors) { + actors.entrySet().stream().map(Map.Entry::getValue).forEach(a -> a.shutdown(now)); + } + } + @Override public boolean unregisterActor(String name) { diff --git a/src/test/java/paxel/lintstone/api/ActorSortTest.java b/src/test/java/paxel/lintstone/api/ActorSortTest.java index 524c1af..b7ee571 100644 --- a/src/test/java/paxel/lintstone/api/ActorSortTest.java +++ b/src/test/java/paxel/lintstone/api/ActorSortTest.java @@ -1,6 +1,7 @@ package paxel.lintstone.api; import org.junit.jupiter.api.Test; +import paxel.lintstone.api.actors.SortNodeActor; import java.util.List; import java.util.Random; @@ -37,6 +38,6 @@ void sort() throws ExecutionException, InterruptedException { } // stop system - system.shutDown(); + system.shutDownNow(); } } diff --git a/src/test/java/paxel/lintstone/api/EndMessage.java b/src/test/java/paxel/lintstone/api/EndMessage.java deleted file mode 100644 index 41f895c..0000000 --- a/src/test/java/paxel/lintstone/api/EndMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package paxel.lintstone.api; - -/** - * simple mesaage type that stops summing up. - */ -class EndMessage { - -} diff --git a/src/test/java/paxel/lintstone/api/ExternalAskTest.java b/src/test/java/paxel/lintstone/api/ExternalAskTest.java index a025a17..4ca2544 100644 --- a/src/test/java/paxel/lintstone/api/ExternalAskTest.java +++ b/src/test/java/paxel/lintstone/api/ExternalAskTest.java @@ -1,12 +1,10 @@ package paxel.lintstone.api; import org.junit.Test; +import paxel.lintstone.api.actors.Md5Actor; +import paxel.lintstone.api.messages.EndMessage; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Formatter; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; @@ -86,40 +84,4 @@ public void testAskExternal() throws InterruptedException { system.shutDown(); } - private static class Md5Actor implements LintStoneActor { - private MessageDigest md5; - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(String.class, (name, m) -> this.add(name.getBytes(StandardCharsets.UTF_8))).inCase(ByteBuffer.class, (byteBuffer, m) -> { - if (byteBuffer.hasArray()) - add(byteBuffer.array()); - }).inCase(EndMessage.class, (dmg, m) -> { - m.reply(getMd5String()); - // let's die - m.unregister(); - }); - } - - private Object getMd5String() { - byte[] digest = md5.digest(); - Formatter f = new Formatter(new StringBuilder()); - for (byte x : - digest) { - f.format("%01x", x); - } - return f.toString(); - } - - private void add(byte[] bytes) { - if (md5 == null) { - try { - md5 = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - // ignorable for this test - } - } - md5.update(bytes); - } - } } diff --git a/src/test/java/paxel/lintstone/api/FailingTests.java b/src/test/java/paxel/lintstone/api/FailingTests.java index 13ee534..219da31 100644 --- a/src/test/java/paxel/lintstone/api/FailingTests.java +++ b/src/test/java/paxel/lintstone/api/FailingTests.java @@ -2,7 +2,7 @@ import org.junit.Assert; import org.junit.Test; -import paxel.lintstone.impl.FailedMessage; +import paxel.lintstone.api.actors.StupidActor; import java.util.ArrayList; import java.util.List; @@ -86,83 +86,4 @@ public void testGetDataOut() throws InterruptedException, ExecutionException { system.shutDownAndWait(); } - private static class StupidActor implements LintStoneActor { - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec - .inCase(String.class, this::handleString) - .inCase(FailedMessage.class, this::handleFail) - .otherwise((o, m) -> System.out.println("otherwise: " + o)); - } - - private void handleString(String go, LintStoneMessageEventContext mec) { - LintStoneActorAccessor registered = mec.registerActor(FAILING, () -> m -> { - // this temporay actor will fail with each message that it receives - throw new IllegalArgumentException("Go away"); - }, ActorSettings.DEFAULT); - - if (registered.exists()) { - // the actor is registered, registering it again will not create a new actor but the previous one - // in that case the "Go away" actor - LintStoneActorAccessor reRegister = mec.registerActor(FAILING, () -> m -> { - // no fail anymore, but this factory will not be called - }, ActorSettings.DEFAULT); - - if (reRegister.exists()) { - // so this first message to the actor should fail and be given to the errorhandler - // it also should cause a FailedMessage to be returned to us, that the Message could not be processed - reRegister.send("Hi!"); - } - } - // We send a message to ourselves, that we don't support - // the false object will end in the otherwise branch of newMessageEvent - mec.send(mec.getName(), Boolean.FALSE); - try { - mec.send("Unknown Actor", "Will not be delivered"); - throw new IllegalStateException("Should have failed"); - } catch (UnregisteredRecipientException unregisteredRecipientException) { - // we can't send to unknown actors - } - - LintStoneActorAccessor actor = mec.getActor(NOT_EXISTANT); - - if (!actor.exists()) { - try { - actor.send("fail me"); - throw new IllegalStateException("Should have failed"); - } catch (UnregisteredRecipientException unregisteredRecipientException) { - } - // register an actor with that name - mec.registerActor(NOT_EXISTANT, () -> a -> { - }, ActorSettings.DEFAULT); - - boolean exists = actor.exists(); - // would throw exception if LintStoneActorAccessor is not self updating - actor.send("This actor reference works now: " + exists); - } - } - - private void handleFail(FailedMessage go, LintStoneMessageEventContext m) { - // The failed message was sent by the temporary actor, because it could not process it - System.out.println("Failed on " + go.actorName() + " because " + go.cause() + " when processing " + go.message()); - - final LintStoneActorAccessor me = m.getActor(m.getName()); - me.send(true); - // we unregister ourselves - m.unregister(); - if (me.exists()) { - throw new IllegalStateException("I was just unregistered"); - } - try { - me.send("will not happen"); - throw new IllegalStateException("Should have failed"); - } catch (UnregisteredRecipientException unregisteredRecipientException) { - } - - // end the test - m.getActor(STOP_ACTOR).send("stop"); - } - } - } diff --git a/src/test/java/paxel/lintstone/api/InternalAskTest.java b/src/test/java/paxel/lintstone/api/InternalAskTest.java index d22e05b..13a17e5 100644 --- a/src/test/java/paxel/lintstone/api/InternalAskTest.java +++ b/src/test/java/paxel/lintstone/api/InternalAskTest.java @@ -1,14 +1,16 @@ package paxel.lintstone.api; import org.junit.Test; +import paxel.lintstone.api.actors.CharCount; +import paxel.lintstone.api.actors.Distributor; +import paxel.lintstone.api.actors.Sorter; +import paxel.lintstone.api.actors.WordCount; +import paxel.lintstone.api.messages.EndMessage; -import java.text.MessageFormat; -import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -35,8 +37,7 @@ public InternalAskTest() { @Test public void testAskExternal() throws InterruptedException, ExecutionException, TimeoutException { LintStoneSystem system = LintStoneSystemFactory.create(); - // the entry actor is limited to 1 message at the time input queue - // just to test that the messages are added even so we send faster than the actor can process (creating backpressure) + LintStoneActorAccessor dist = system.registerActor("dist", Distributor::new, ActorSettings.DEFAULT); system.registerActor("wordCount", WordCount::new, ActorSettings.DEFAULT); system.registerActor("charCount", CharCount::new, ActorSettings.DEFAULT); @@ -63,75 +64,4 @@ public void testAskExternal() throws InterruptedException, ExecutionException, T } - private static class Distributor implements LintStoneActor { - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(String.class, this::send).inCase(EndMessage.class, (dmg, askContext) -> { - CompletableFuture words = new CompletableFuture<>(); - CompletableFuture chars = new CompletableFuture<>(); - CompletableFuture sort = new CompletableFuture<>(); - // each of these completes will be called in the thread context of this actor - mec.ask("wordCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> words.complete(r))); - mec.ask("charCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> chars.complete(r))); - mec.ask("sorter", new EndMessage(), c -> c.inCase(String.class, (r, replyContext) -> sort.complete(r))); - - // when the last reply comes, the reply of the external ask is fulfilled. - CompletableFuture.allOf(words, chars, sort).thenApply(x -> { - try { - askContext.reply(MessageFormat.format("{0} words, {1} letters, {2}", words.get(), chars.get(), sort.get())); - } catch (Exception e) { - askContext.reply(e.getMessage()); - } - return null; - }); - }); - } - - - private void send(String txt, LintStoneMessageEventContext mec) { - mec.send("wordCount", txt); - mec.send("charCount", txt); - mec.send("sorter", txt); - } - } - - private static class WordCount implements LintStoneActor { - private int count; - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(String.class, (txt, m) -> { - int length = (int) Arrays.stream(txt.trim().split(" ")).filter(f->!f.trim().isEmpty()).count(); - this.count += length; - }).inCase(EndMessage.class, (dmg, askContext) -> { - askContext.reply(count); - askContext.unregister(); - }); - } - } - - private static class CharCount implements LintStoneActor { - private int count; - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(String.class, (txt, m) -> this.count += txt.replaceAll(" ", "").length()).inCase(EndMessage.class, (dmg, askContext) -> { - askContext.reply(count); - askContext.unregister(); - }); - } - } - - private static class Sorter implements LintStoneActor { - final Set words = new HashSet<>(); - - @Override - public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(String.class, (txt, m) -> words.addAll(Arrays.stream(txt.trim().split(" ")).filter(f->!f.trim().isEmpty()).map(String::toLowerCase).collect(Collectors.toList()))).inCase(EndMessage.class, (dmg, askContext) -> { - ArrayList list = new ArrayList<>(words); - Collections.sort(list); - askContext.reply(String.join(",", list)); - }); - } - } } diff --git a/src/test/java/paxel/lintstone/api/LintStoneSystemTest.java b/src/test/java/paxel/lintstone/api/LintStoneSystemTest.java index 7ca2426..dbb9dc6 100644 --- a/src/test/java/paxel/lintstone/api/LintStoneSystemTest.java +++ b/src/test/java/paxel/lintstone/api/LintStoneSystemTest.java @@ -9,6 +9,9 @@ import static org.hamcrest.MatcherAssert.*; import org.junit.Test; +import paxel.lintstone.api.actors.AdderActor; +import paxel.lintstone.api.actors.SumActor; +import paxel.lintstone.api.messages.EndMessage; /** * diff --git a/src/test/java/paxel/lintstone/api/AdderActor.java b/src/test/java/paxel/lintstone/api/actors/AdderActor.java similarity index 70% rename from src/test/java/paxel/lintstone/api/AdderActor.java rename to src/test/java/paxel/lintstone/api/actors/AdderActor.java index d5f85eb..23f3ce3 100644 --- a/src/test/java/paxel/lintstone/api/AdderActor.java +++ b/src/test/java/paxel/lintstone/api/actors/AdderActor.java @@ -1,26 +1,26 @@ -package paxel.lintstone.api; +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.DieMessage; +import paxel.lintstone.api.messages.EndMessage; public class AdderActor implements LintStoneActor { private long sum; private String name; + @Override public void newMessageEvent(LintStoneMessageEventContext mec) { mec.inCase(Integer.class, this::addInteger) .inCase(EndMessage.class, this::endSum) .inCase(String.class, this::name) .inCase(DieMessage.class, this::unregister) - .otherwise((o, m) -> System.err.println("Unknown message " + o)); + .otherwise(this::other); } private void addInteger(Integer num, LintStoneMessageEventContext mec) { - int last = -1; - if (last >= num) { - - // make sure that the order is correct - throw new IllegalStateException("Expected something bigger than " + last + " but got "+num); - } sum += num; } @@ -37,4 +37,8 @@ private void unregister(DieMessage msg, LintStoneMessageEventContext mec) { System.out.println("Actor " + name + " unregistered: " + unregister); } + private void other(Object o, LintStoneMessageEventContext m) { + System.err.println("Unknown message " + o); + } + } diff --git a/src/test/java/paxel/lintstone/api/actors/CharCount.java b/src/test/java/paxel/lintstone/api/actors/CharCount.java new file mode 100644 index 0000000..937a703 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/CharCount.java @@ -0,0 +1,25 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.EndMessage; + +public class CharCount implements LintStoneActor { + private int count; + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::handleString) + .inCase(EndMessage.class, this::handleEnd); + } + + private void handleString(String txt, LintStoneMessageEventContext m) { + this.count += txt.replaceAll(" ", "").length(); + } + + private void handleEnd(EndMessage dmg, LintStoneMessageEventContext askContext) { + askContext.reply(count); + askContext.unregister(); + } +} diff --git a/src/test/java/paxel/lintstone/api/actors/Distributor.java b/src/test/java/paxel/lintstone/api/actors/Distributor.java new file mode 100644 index 0000000..a7c6e88 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/Distributor.java @@ -0,0 +1,45 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.EndMessage; + +import java.text.MessageFormat; +import java.util.concurrent.CompletableFuture; + +public class Distributor implements LintStoneActor { + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::send) + .inCase(EndMessage.class, this::handleEnd); + } + + + private void send(String txt, LintStoneMessageEventContext mec) { + mec.send("wordCount", txt); + mec.send("charCount", txt); + mec.send("sorter", txt); + } + + private void handleEnd(EndMessage dmg, LintStoneMessageEventContext askContext) { + CompletableFuture words = new CompletableFuture<>(); + CompletableFuture chars = new CompletableFuture<>(); + CompletableFuture sort = new CompletableFuture<>(); +// each of these completes will be called in the thread context of this actor + askContext.ask("wordCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> words.complete(r))); + askContext.ask("charCount", new EndMessage(), c -> c.inCase(Integer.class, (r, replyContext) -> chars.complete(r))); + askContext.ask("sorter", new EndMessage(), c -> c.inCase(String.class, (r, replyContext) -> sort.complete(r))); + +// when the last reply comes, the reply of the external ask is fulfilled. + CompletableFuture.allOf(words, chars, sort).thenApply(x -> { + try { + askContext.reply(MessageFormat.format("{0} words, {1} letters, {2}", words.get(), chars.get(), sort.get())); + } catch (Exception e) { + askContext.reply(e.getMessage()); + } + return null; + }); + } +} diff --git a/src/test/java/paxel/lintstone/api/actors/Md5Actor.java b/src/test/java/paxel/lintstone/api/actors/Md5Actor.java new file mode 100644 index 0000000..46f5960 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/Md5Actor.java @@ -0,0 +1,59 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.EndMessage; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Formatter; + +public class Md5Actor implements LintStoneActor { + private MessageDigest md5; + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::handleString) + .inCase(ByteBuffer.class, this::handleByteBuffer) + .inCase(EndMessage.class, this::handleEnd); + } + + private Object getMd5String() { + byte[] digest = md5.digest(); + Formatter f = new Formatter(new StringBuilder()); + for (byte x : + digest) { + f.format("%01x", x); + } + return f.toString(); + } + + private void add(byte[] bytes) { + if (md5 == null) { + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + // ignorable for this test + } + } + md5.update(bytes); + } + + private void handleString(String name, LintStoneMessageEventContext m) { + this.add(name.getBytes(StandardCharsets.UTF_8)); + } + + private void handleByteBuffer(ByteBuffer byteBuffer, LintStoneMessageEventContext m) { + if (byteBuffer.hasArray()) + add(byteBuffer.array()); + } + + private void handleEnd(EndMessage dmg, LintStoneMessageEventContext m) { + m.reply(getMd5String()); +// let's die + m.unregister(); + } +} diff --git a/src/test/java/paxel/lintstone/api/SortNodeActor.java b/src/test/java/paxel/lintstone/api/actors/SortNodeActor.java similarity index 93% rename from src/test/java/paxel/lintstone/api/SortNodeActor.java rename to src/test/java/paxel/lintstone/api/actors/SortNodeActor.java index e410f4b..76e80e6 100644 --- a/src/test/java/paxel/lintstone/api/SortNodeActor.java +++ b/src/test/java/paxel/lintstone/api/actors/SortNodeActor.java @@ -1,4 +1,9 @@ -package paxel.lintstone.api; +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.ActorSettings; +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneActorAccessor; +import paxel.lintstone.api.LintStoneMessageEventContext; import java.util.ArrayList; import java.util.Collections; diff --git a/src/test/java/paxel/lintstone/api/actors/Sorter.java b/src/test/java/paxel/lintstone/api/actors/Sorter.java new file mode 100644 index 0000000..347fdc1 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/Sorter.java @@ -0,0 +1,32 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.EndMessage; + +import java.util.*; +import java.util.stream.Collectors; + +public class Sorter implements LintStoneActor { + final Set words = new HashSet<>(); + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::handle) + .inCase(EndMessage.class, this::handle); + } + + private void handle(String txt, LintStoneMessageEventContext m) { + words.addAll(Arrays.stream(txt.trim().split(" ")) + .filter(f -> !f.trim().isEmpty()) + .map(String::toLowerCase) + .collect(Collectors.toList())); + } + + private void handle(EndMessage dmg, LintStoneMessageEventContext askContext) { + ArrayList list = new ArrayList<>(words); + Collections.sort(list); + askContext.reply(String.join(",", list)); + } +} diff --git a/src/test/java/paxel/lintstone/api/actors/StupidActor.java b/src/test/java/paxel/lintstone/api/actors/StupidActor.java new file mode 100644 index 0000000..ca8766b --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/StupidActor.java @@ -0,0 +1,87 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.*; +import paxel.lintstone.impl.FailedMessage; + +public class StupidActor implements LintStoneActor { + + private static void otherwise(Object o, LintStoneMessageEventContext m) { + System.out.println("otherwise: " + o); + } + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::handleString) + .inCase(FailedMessage.class, this::handleFail) + .otherwise(StupidActor::otherwise); + } + + private void handleString(String go, LintStoneMessageEventContext mec) { + LintStoneActorAccessor registered = mec.registerActor(FailingTests.FAILING, () -> m -> { + // this temporay actor will fail with each message that it receives + throw new IllegalArgumentException("Go away"); + }, ActorSettings.DEFAULT); + + if (registered.exists()) { + // the actor is registered, registering it again will not create a new actor but the previous one + // in that case the "Go away" actor + LintStoneActorAccessor reRegister = mec.registerActor(FailingTests.FAILING, () -> m -> { + // no fail anymore, but this factory will not be called + }, ActorSettings.DEFAULT); + + if (reRegister.exists()) { + // so this first message to the actor should fail and be given to the errorhandler + // it also should cause a FailedMessage to be returned to us, that the Message could not be processed + reRegister.send("Hi!"); + } + } + // We send a message to ourselves, that we don't support + // the false object will end in the otherwise branch of newMessageEvent + mec.send(mec.getName(), Boolean.FALSE); + try { + mec.send("Unknown Actor", "Will not be delivered"); + throw new IllegalStateException("Should have failed"); + } catch (UnregisteredRecipientException unregisteredRecipientException) { + // we can't send to unknown actors + } + + LintStoneActorAccessor actor = mec.getActor(FailingTests.NOT_EXISTANT); + + if (!actor.exists()) { + try { + actor.send("fail me"); + throw new IllegalStateException("Should have failed"); + } catch (UnregisteredRecipientException unregisteredRecipientException) { + } + // register an actor with that name + mec.registerActor(FailingTests.NOT_EXISTANT, () -> a -> { + }, ActorSettings.DEFAULT); + + boolean exists = actor.exists(); + // would throw exception if LintStoneActorAccessor is not self updating + actor.send("This actor reference works now: " + exists); + } + } + + private void handleFail(FailedMessage go, LintStoneMessageEventContext m) { + // The failed message was sent by the temporary actor, because it could not process it + System.out.println("Failed on " + go.actorName() + " because " + go.cause() + " when processing " + go.message()); + + final LintStoneActorAccessor me = m.getActor(m.getName()); + me.send(true); + // we unregister ourselves + m.unregister(); + if (me.exists()) { + throw new IllegalStateException("I was just unregistered"); + } + try { + me.send("will not happen"); + throw new IllegalStateException("Should have failed"); + } catch (UnregisteredRecipientException unregisteredRecipientException) { + } + + // end the test + m.getActor(FailingTests.STOP_ACTOR).send("stop"); + } +} diff --git a/src/test/java/paxel/lintstone/api/SumActor.java b/src/test/java/paxel/lintstone/api/actors/SumActor.java similarity index 67% rename from src/test/java/paxel/lintstone/api/SumActor.java rename to src/test/java/paxel/lintstone/api/actors/SumActor.java index 24a0d37..1f3fb2c 100644 --- a/src/test/java/paxel/lintstone/api/SumActor.java +++ b/src/test/java/paxel/lintstone/api/actors/SumActor.java @@ -1,4 +1,8 @@ -package paxel.lintstone.api; +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.DieMessage; import java.util.function.Consumer; @@ -14,11 +18,16 @@ public SumActor(Consumer result) { private int expected; private int received; + private static void otherwise(Object o, LintStoneMessageEventContext m) { + System.err.println("Unknown message " + o); + } + @Override public void newMessageEvent(LintStoneMessageEventContext mec) { - mec.inCase(Long.class, this::addLong) + mec + .inCase(Long.class, this::addLong) .inCase(String.class, this::incExpected) - .otherwise((o, m) -> System.err.println("Unknown message " + o)); + .otherwise(SumActor::otherwise); } private void addLong(Long num, LintStoneMessageEventContext mec) { diff --git a/src/test/java/paxel/lintstone/api/actors/WordCount.java b/src/test/java/paxel/lintstone/api/actors/WordCount.java new file mode 100644 index 0000000..9e20256 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/actors/WordCount.java @@ -0,0 +1,28 @@ +package paxel.lintstone.api.actors; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.messages.EndMessage; + +import java.util.Arrays; + +public class WordCount implements LintStoneActor { + private int count; + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec + .inCase(String.class, this::handle) + .inCase(EndMessage.class, this::handle); + } + + private void handle(String txt, LintStoneMessageEventContext m) { + int length = (int) Arrays.stream(txt.trim().split(" ")).filter(f -> !f.trim().isEmpty()).count(); + this.count += length; + } + + private void handle(EndMessage dmg, LintStoneMessageEventContext askContext) { + askContext.reply(count); + askContext.unregister(); + } +} diff --git a/src/test/java/paxel/lintstone/api/example/IdProviderActor.java b/src/test/java/paxel/lintstone/api/example/IdProviderActor.java new file mode 100644 index 0000000..9387089 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/example/IdProviderActor.java @@ -0,0 +1,78 @@ +package paxel.lintstone.api.example; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; + +import java.util.*; +import java.util.logging.Logger; + +public class IdProviderActor implements LintStoneActor { + + private Logger log = Logger.getLogger(this.getClass().getName()); + private int nextId = 0; + private LinkedList removed = new LinkedList<>(); + private Map ids = new HashMap<>(); + + public record AddText(String value) { + } + + public record RemoveText(String value) { + } + + public record Id(Integer value) { + } + + public record SizeRequest() { + + } + + public record SizeResponse(Integer current, Integer peak) { + + } + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec.inCase(AddText.class, this::cacheText) + .inCase(RemoveText.class, this::removeText) + .inCase(SizeRequest.class, this::size) + .otherwise(this::unkownMessage); + } + + private void size(SizeRequest sizeRequest, LintStoneMessageEventContext lintStoneMessageEventContext) { + lintStoneMessageEventContext.reply(new SizeResponse(ids.size(), nextId)); + } + + private void unkownMessage(Object o, LintStoneMessageEventContext lintStoneMessageEventContext) { + log.severe("Unsupported message received: " + o.getClass() + ": <" + o + ">"); + } + + private void removeText(RemoveText removeText, LintStoneMessageEventContext lintStoneMessageEventContext) { + // removes the text from the cache and adds the ID for re-usage. + Integer remove = ids.remove(removeText.value()); + if (remove != null) + removed.add(remove); + } + + private void cacheText(AddText text, LintStoneMessageEventContext lintStoneMessageEventContext) { + Integer i = ids.get(text.value()); + if (i != null) + // we know this one already + lintStoneMessageEventContext.reply(new Id(i)); + else { + Integer poll = removed.poll(); + if (poll != null) { + // we can reuse an old ID + cacheEntry(text, lintStoneMessageEventContext, poll); + } else { + // create a new ID + cacheEntry(text, lintStoneMessageEventContext, nextId); + nextId++; + } + } + } + + private void cacheEntry(AddText text, LintStoneMessageEventContext lintStoneMessageEventContext, Integer poll) { + ids.put(text.value(), poll); + lintStoneMessageEventContext.reply(new Id(poll)); + } +} diff --git a/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java b/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java new file mode 100644 index 0000000..c4b3312 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/example/WordGeneratorActor.java @@ -0,0 +1,110 @@ +package paxel.lintstone.api.example; + +import paxel.lintstone.api.LintStoneActor; +import paxel.lintstone.api.LintStoneMessageEventContext; +import paxel.lintstone.api.example.utils.Snippets; + +import java.util.Random; +import java.util.logging.Logger; + +public class WordGeneratorActor implements LintStoneActor { + + private Logger log = Logger.getLogger(this.getClass().getName()); + + private Snippets consonants; + private Snippets vocals; + private Random random; + + public record Init(Long seed) { + } + + public record Request(int sylibls) { + } + + public record Word(String value) { + } + + @Override + public void newMessageEvent(LintStoneMessageEventContext mec) { + mec.inCase(Init.class, this::reinit) + .inCase(Request.class, this::createWord) + .otherwise(this::unknownMessage); + } + + private void unknownMessage(Object o, LintStoneMessageEventContext lintStoneMessageEventContext) { + log.severe("Unsupported message received: " + o.getClass() + ": <" + o + ">"); + } + + private void createWord(Request request, LintStoneMessageEventContext lintStoneMessageEventContext) { + if (random == null) + reinit(0L); + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < request.sylibls(); i++) { + stringBuilder.append(consonants.get(random.nextDouble())); + stringBuilder.append(vocals.get(random.nextDouble())); + } + lintStoneMessageEventContext.reply(new Word(stringBuilder.toString())); + } + + private void reinit(long l) { + consonants = new Snippets(); + consonants.add("q", random.nextInt()); + consonants.add("w", random.nextInt()); + consonants.add("r", random.nextInt()); + consonants.add("t", random.nextInt()); + consonants.add("z", random.nextInt()); + consonants.add("p", random.nextInt()); + consonants.add("s", random.nextInt()); + consonants.add("d", random.nextInt()); + consonants.add("f", random.nextInt()); + consonants.add("g", random.nextInt()); + consonants.add("h", random.nextInt()); + consonants.add("j", random.nextInt()); + consonants.add("k", random.nextInt()); + consonants.add("l", random.nextInt()); + consonants.add("x", random.nextInt()); + consonants.add("c", random.nextInt()); + consonants.add("v", random.nextInt()); + consonants.add("b", random.nextInt()); + consonants.add("n", random.nextInt()); + consonants.add("m", random.nextInt()); + consonants.add("sch", random.nextInt()); + consonants.add("ch", random.nextInt()); + consonants.add("sz", random.nextInt()); + consonants.add("st", random.nextInt()); + consonants.add("sp", random.nextInt()); + consonants.add("ck", random.nextInt()); + consonants.add("lg", random.nextInt()); + consonants.add("mm", random.nextInt()); + consonants.add("nn", random.nextInt()); + consonants.add("tt", random.nextInt()); + consonants.add("qu", random.nextInt()); + consonants.add("ff", random.nextInt()); + consonants.add("ll", random.nextInt()); + consonants.add("bb", random.nextInt()); + consonants.add("pp", random.nextInt()); + consonants.add("", random.nextInt()); + + vocals = new Snippets(); + vocals.add("a", random.nextInt()); + vocals.add("e", random.nextInt()); + vocals.add("i", random.nextInt()); + vocals.add("o", random.nextInt()); + vocals.add("u", random.nextInt()); + vocals.add("y", random.nextInt()); + vocals.add("ee", random.nextInt()); + vocals.add("au", random.nextInt()); + vocals.add("eu", random.nextInt()); + vocals.add("ei", random.nextInt()); + vocals.add("ae", random.nextInt()); + vocals.add("oe", random.nextInt()); + vocals.add("ue", random.nextInt()); + vocals.add("ie", random.nextInt()); + vocals.add("ye", random.nextInt()); + vocals.add("", random.nextInt()); + } + + private void reinit(Init init, LintStoneMessageEventContext lintStoneMessageEventContext) { + reinit(init.seed()); + } +} diff --git a/src/test/java/paxel/lintstone/api/example/utils/Snippets.java b/src/test/java/paxel/lintstone/api/example/utils/Snippets.java new file mode 100644 index 0000000..d6672f7 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/example/utils/Snippets.java @@ -0,0 +1,37 @@ +package paxel.lintstone.api.example.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Snippets { + + private int current = 0; + + record WeightedSnippet(Integer value, String text) implements Comparable { + + @Override + public int compareTo(WeightedSnippet o) { + return Integer.compare(value, o.value); + } + } + + List snippets = new ArrayList<>(); + + public String get(double v) { + int index = (int) (v * current); + int i = Collections.binarySearch(snippets, new WeightedSnippet(index, "")); + if (i < 0) { + i = Math.abs(i) + 1; + } + if (snippets.size() <= i) + return snippets.get(i).text(); + + throw new IllegalStateException("double " + v + " current:" + current + " i:" + i + " size:" + snippets.size()); + } + + public void add(String snippet, int weight) { + snippets.add(new WeightedSnippet(current + weight, snippet)); + current += weight; + } +} diff --git a/src/test/java/paxel/lintstone/api/DieMessage.java b/src/test/java/paxel/lintstone/api/messages/DieMessage.java similarity index 61% rename from src/test/java/paxel/lintstone/api/DieMessage.java rename to src/test/java/paxel/lintstone/api/messages/DieMessage.java index 4f702a8..bae4cd3 100644 --- a/src/test/java/paxel/lintstone/api/DieMessage.java +++ b/src/test/java/paxel/lintstone/api/messages/DieMessage.java @@ -1,8 +1,5 @@ -package paxel.lintstone.api; +package paxel.lintstone.api.messages; -/** - * - */ public class DieMessage { public DieMessage() { diff --git a/src/test/java/paxel/lintstone/api/messages/EndMessage.java b/src/test/java/paxel/lintstone/api/messages/EndMessage.java new file mode 100644 index 0000000..6b93ed4 --- /dev/null +++ b/src/test/java/paxel/lintstone/api/messages/EndMessage.java @@ -0,0 +1,8 @@ +package paxel.lintstone.api.messages; + +/** + * simple mesaage type that stops summing up. + */ +public class EndMessage { + +}