diff --git a/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/StashBufferTest.java b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/StashBufferTest.java new file mode 100644 index 0000000000..52d23ed14b --- /dev/null +++ b/akka-actor-typed-tests/src/test/java/akka/actor/typed/javadsl/StashBufferTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 Lightbend Inc. + */ + +package akka.actor.typed.javadsl; + +import akka.actor.testkit.typed.internal.StubbedActorContext; +import akka.actor.testkit.typed.javadsl.LogCapturing; +import akka.actor.typed.internal.StashBufferImpl; +import org.junit.Rule; +import org.junit.Test; +import org.scalatestplus.junit.JUnitSuite; + +import java.util.function.Function; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class StashBufferTest extends JUnitSuite { + + @Rule public final LogCapturing logCapturing = new LogCapturing(); + + StubbedActorContext context = + new StubbedActorContext( + "StashBufferTest", + () -> { + throw new UnsupportedOperationException("Will never be invoked in this test"); + }); + + @Test + public void testProcessElementsInTheRightOrder() { + + StashBuffer buffer = StashBufferImpl.apply(context, 10); + buffer.stash("m1"); + buffer.stash("m2"); + buffer.stash("m3"); + + StringBuilder sb1 = new StringBuilder(); + buffer.forEach(sb1::append); + assertEquals("m1m2m3", sb1.toString()); + + buffer.unstash(Behaviors.ignore(), 1, Function.identity()); + StringBuilder sb2 = new StringBuilder(); + buffer.forEach(sb2::append); + assertEquals("m2m3", sb2.toString()); + } + + @Test + public void testAnyMatchAndContains() { + StashBuffer buffer = StashBufferImpl.apply(context, 10); + buffer.stash("m1"); + buffer.stash("m2"); + + assertTrue(buffer.anyMatch(m -> m.startsWith("m"))); + assertTrue(buffer.anyMatch(m -> m.endsWith("2"))); + + assertTrue(buffer.contains("m1")); + assertTrue(buffer.contains("m2")); + } +} diff --git a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala index bc56511023..0d966fbe1f 100644 --- a/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala +++ b/akka-actor-typed-tests/src/test/scala/akka/actor/typed/scaladsl/StashBufferSpec.scala @@ -81,6 +81,18 @@ class StashBufferSpec extends AnyWordSpec with Matchers with LogCapturing { sb2.toString() should ===("m2m3") } + "answer 'exists' and 'contains' correctly" in { + val buffer = StashBuffer[String](context, 10) + buffer.stash("m1") + buffer.stash("m2") + + buffer.contains("m1") shouldBe true + buffer.exists(_ == "m2") shouldBe true + + buffer.contains("m3") shouldBe false + buffer.exists(_ == "m4") shouldBe false + } + "unstash to returned behaviors" in { val buffer = StashBuffer[String](context, 10) buffer.stash("m1") diff --git a/akka-actor-typed/src/main/mima-filters/2.6.10.backwards.excludes/stash-buffer-exists-contains.excludes b/akka-actor-typed/src/main/mima-filters/2.6.10.backwards.excludes/stash-buffer-exists-contains.excludes new file mode 100644 index 0000000000..d3c303271e --- /dev/null +++ b/akka-actor-typed/src/main/mima-filters/2.6.10.backwards.excludes/stash-buffer-exists-contains.excludes @@ -0,0 +1,4 @@ +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.javadsl.StashBuffer.contains") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.javadsl.StashBuffer.anyMatch") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.scaladsl.StashBuffer.contains") +ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.actor.typed.scaladsl.StashBuffer.exists") diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/StashBufferImpl.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/StashBufferImpl.scala index 47dab79f59..841b5b648f 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/internal/StashBufferImpl.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/internal/StashBufferImpl.scala @@ -5,10 +5,8 @@ package akka.actor.typed.internal import java.util.function.{ Function => JFunction } - import scala.annotation.tailrec import scala.util.control.NonFatal - import akka.actor.DeadLetter import akka.actor.typed.Behavior import akka.actor.typed.Signal @@ -17,7 +15,7 @@ import akka.actor.typed.javadsl import akka.actor.typed.scaladsl import akka.actor.typed.scaladsl.ActorContext import akka.annotation.{ InternalApi, InternalStableApi } -import akka.japi.function.Procedure +import akka.japi.function.{ Predicate, Procedure } import akka.util.{ unused, ConstantFun } import akka.util.OptionVal @@ -128,6 +126,21 @@ import akka.util.OptionVal override def forEach(f: Procedure[T]): Unit = foreach(f.apply) + override def contains[U >: T](message: U): Boolean = + exists(_ == message) + + override def exists(predicate: T => Boolean): Boolean = { + var hasElement = false + var node = _first + while (node != null && !hasElement) { + hasElement = predicate(node.message) + node = node.next + } + hasElement + } + + override def anyMatch(predicate: Predicate[T]): Boolean = exists(predicate.test) + override def unstashAll(behavior: Behavior[T]): Behavior[T] = { val behav = unstash(behavior, size, ConstantFun.scalaIdentityFunction[T]) stashCleared(ctx) diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/StashBuffer.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/StashBuffer.scala index fac124ea41..ce8fc753b4 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/StashBuffer.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/javadsl/StashBuffer.scala @@ -4,12 +4,11 @@ package akka.actor.typed.javadsl -import java.util.function.{ Function => JFunction } - -import akka.actor.typed.Behavior -import akka.actor.typed.scaladsl +import akka.actor.typed.{ scaladsl, Behavior } import akka.annotation.DoNotInherit -import akka.japi.function.Procedure +import akka.japi.function.{ Predicate, Procedure } + +import java.util.function.{ Function => JFunction } /** * A non thread safe mutable message buffer that can be used to buffer messages inside actors @@ -81,6 +80,22 @@ import akka.japi.function.Procedure */ def forEach(f: Procedure[T]): Unit + /** + * Tests whether this [[StashBuffer]] contains a given message. + * + * @param message the message to test + * @return true if the buffer contains the message, false otherwise. + */ + def contains[U >: T](message: U): Boolean + + /** + * Tests whether a predicate holds for at least one element of this [[StashBuffer]]. + * + * @param predicate the predicate used to test + * @return true if the predicate holds for at least one message, false otherwise. + */ + def anyMatch(predicate: Predicate[T]): Boolean + /** * Removes all messages from the buffer. */ diff --git a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/StashBuffer.scala b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/StashBuffer.scala index add12b5dac..ebbdf9c124 100644 --- a/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/StashBuffer.scala +++ b/akka-actor-typed/src/main/scala/akka/actor/typed/scaladsl/StashBuffer.scala @@ -92,6 +92,22 @@ import akka.annotation.{ DoNotInherit, InternalApi } */ def foreach(f: T => Unit): Unit + /** + * Tests whether this [[StashBuffer]] contains a given message. + * + * @param message the message to test + * @return true if the buffer contains the message, false otherwise. + */ + def contains[U >: T](message: U): Boolean + + /** + * Tests whether a predicate holds for at least one element of this [[StashBuffer]]. + * + * @param predicate the predicate used to test + * @return true if the predicate holds for at least one message, false otherwise. + */ + def exists(predicate: T => Boolean): Boolean + /** * Removes all messages from the buffer. */