This is an automated email from the ASF dual-hosted git repository.

He-Pin pushed a commit to branch java-api-replace-tuple2-with-pair
in repository https://gitbox.apache.org/repos/asf/pekko.git

commit 91f7b8454f7e4e0c2f94868ba4de6292f392771f
Author: 虎鸣 <[email protected]>
AuthorDate: Sat Jul 25 19:29:40 2026 +0800

    Replace scala.Tuple2 with pekko.japi.Pair in FSMTransitionHandlerBuilder
    
    Motivation:
    Java users see scala.Tuple2 in their IDE when using 
FSMTransitionHandlerBuilder,
    which violates the principle of avoiding Scala data structures in Java APIs.
    
    Modification:
    - Replace scala.Tuple2 with pekko.japi.Pair in FSMTransitionHandlerBuilder
    - Update AbstractFSM and PersistentFSMBase to use a proper adapter instead 
of
      an unsafe .asInstanceOf[TransitionHandler] cast
    - Add MiMa filters for the binary incompatibility
    
    Result:
    Java FSM API no longer exposes scala.Tuple2; uses the established 
pekko.japi.Pair
    type instead. The adapter is also type-safe (no more unchecked cast).
    
    Tests:
    - sbt "actor-tests / Test / testOnly 
org.apache.pekko.actor.AbstractFSMActorTest" PASSED
    - sbt "docs / Test / testOnly jdocs.actor.fsm.*" PASSED
    - sbt "persistence / Test / testOnly 
org.apache.pekko.persistence.fsm.InmemPersistentFSMSpec" PASSED
    - sbt "actor / mimaReportBinaryIssues" PASSED
    
    References:
    Refs #2086
---
 .../pekko/japi/pf/FSMTransitionHandlerBuilder.java | 38 +++++++++++-----------
 .../replace-tuple2-with-pair.excludes              |  5 +++
 .../scala/org/apache/pekko/actor/AbstractFSM.scala | 10 ++++--
 .../pekko/persistence/fsm/PersistentFSMBase.scala  | 10 ++++--
 4 files changed, 40 insertions(+), 23 deletions(-)

diff --git 
a/actor/src/main/java/org/apache/pekko/japi/pf/FSMTransitionHandlerBuilder.java 
b/actor/src/main/java/org/apache/pekko/japi/pf/FSMTransitionHandlerBuilder.java
index 4447adfe79..c0793df780 100644
--- 
a/actor/src/main/java/org/apache/pekko/japi/pf/FSMTransitionHandlerBuilder.java
+++ 
b/actor/src/main/java/org/apache/pekko/japi/pf/FSMTransitionHandlerBuilder.java
@@ -13,12 +13,12 @@
 
 package org.apache.pekko.japi.pf;
 
+import org.apache.pekko.japi.Pair;
 import org.apache.pekko.japi.function.Effect;
 import org.apache.pekko.japi.function.Predicate;
 import org.apache.pekko.japi.function.Procedure;
 import org.apache.pekko.japi.function.Procedure2;
 import scala.PartialFunction;
-import scala.Tuple2;
 import scala.runtime.BoxedUnit;
 
 /**
@@ -28,7 +28,7 @@ import scala.runtime.BoxedUnit;
  */
 public class FSMTransitionHandlerBuilder<S> {
 
-  private final UnitPFBuilder<Tuple2<S, S>> builder = new 
UnitPFBuilder<Tuple2<S, S>>();
+  private final UnitPFBuilder<Pair<S, S>> builder = new UnitPFBuilder<Pair<S, 
S>>();
 
   /**
    * Add a case statement that matches on a from state and a to state.
@@ -41,17 +41,17 @@ public class FSMTransitionHandlerBuilder<S> {
   public FSMTransitionHandlerBuilder<S> state(
       final S fromState, final S toState, final Effect apply) {
     builder.match(
-        Tuple2.class,
-        new Predicate<Tuple2>() {
+        Pair.class,
+        new Predicate<Pair>() {
           @Override
-          public boolean test(Tuple2 t) {
-            return (fromState == null || fromState.equals(t._1()))
-                && (toState == null || toState.equals(t._2()));
+          public boolean test(Pair t) {
+            return (fromState == null || fromState.equals(t.first()))
+                && (toState == null || toState.equals(t.second()));
           }
         },
-        new Procedure<Tuple2>() {
+        new Procedure<Pair>() {
           @Override
-          public void apply(Tuple2 t) throws Exception {
+          public void apply(Pair t) throws Exception {
             apply.apply();
           }
         });
@@ -69,21 +69,21 @@ public class FSMTransitionHandlerBuilder<S> {
   public FSMTransitionHandlerBuilder<S> state(
       final S fromState, final S toState, final Procedure2<S, S> apply) {
     builder.match(
-        Tuple2.class,
-        new Predicate<Tuple2>() {
+        Pair.class,
+        new Predicate<Pair>() {
           @Override
-          public boolean test(Tuple2 t) {
-            return (fromState == null || fromState.equals(t._1()))
-                && (toState == null || toState.equals(t._2()));
+          public boolean test(Pair t) {
+            return (fromState == null || fromState.equals(t.first()))
+                && (toState == null || toState.equals(t.second()));
           }
         },
-        new Procedure<Tuple2>() {
+        new Procedure<Pair>() {
           @Override
-          public void apply(Tuple2 t) throws Exception {
+          public void apply(Pair t) throws Exception {
             @SuppressWarnings("unchecked")
-            S sf = (S) t._1();
+            S sf = (S) t.first();
             @SuppressWarnings("unchecked")
-            S st = (S) t._2();
+            S st = (S) t.second();
             apply.apply(sf, st);
           }
         });
@@ -96,7 +96,7 @@ public class FSMTransitionHandlerBuilder<S> {
    *
    * @return a PartialFunction for this builder.
    */
-  public PartialFunction<Tuple2<S, S>, BoxedUnit> build() {
+  public PartialFunction<Pair<S, S>, BoxedUnit> build() {
     return builder.build();
   }
 }
diff --git 
a/actor/src/main/mima-filters/2.0.x.backwards.excludes/replace-tuple2-with-pair.excludes
 
b/actor/src/main/mima-filters/2.0.x.backwards.excludes/replace-tuple2-with-pair.excludes
new file mode 100644
index 0000000000..5ea8aa4423
--- /dev/null
+++ 
b/actor/src/main/mima-filters/2.0.x.backwards.excludes/replace-tuple2-with-pair.excludes
@@ -0,0 +1,5 @@
+# Replace scala.Tuple2 with pekko.japi.Pair in FSMTransitionHandlerBuilder
+# https://github.com/apache/pekko/issues/2086
+ProblemFilters.exclude[IncompatibleResultTypeProblem]("org.apache.pekko.japi.pf.FSMTransitionHandlerBuilder.build")
+ProblemFilters.exclude[IncompatibleSignatureProblem]("org.apache.pekko.japi.pf.FSMTransitionHandlerBuilder.build")
+ProblemFilters.exclude[IncompatibleMethTypeProblem]("org.apache.pekko.japi.pf.FSMTransitionHandlerBuilder.state")
diff --git a/actor/src/main/scala/org/apache/pekko/actor/AbstractFSM.scala 
b/actor/src/main/scala/org/apache/pekko/actor/AbstractFSM.scala
index fd26155ced..e8e33c15e8 100644
--- a/actor/src/main/scala/org/apache/pekko/actor/AbstractFSM.scala
+++ b/actor/src/main/scala/org/apache/pekko/actor/AbstractFSM.scala
@@ -17,6 +17,7 @@ import scala.concurrent.duration.FiniteDuration
 import scala.jdk.DurationConverters._
 
 import org.apache.pekko
+import pekko.japi.Pair
 import pekko.japi.function.{ Effect, Function2, Predicate, Predicate2, 
Procedure, Procedure2, Procedure3 }
 
 /**
@@ -169,8 +170,13 @@ abstract class AbstractFSM[S, D] extends FSM[S, D] {
    * <b>Multiple handlers may be installed, and every one of them will be
    * called, not only the first one matching.</b>
    */
-  final def onTransition(transitionHandlerBuilder: 
FSMTransitionHandlerBuilder[S]): Unit =
-    
super.onTransition(transitionHandlerBuilder.build().asInstanceOf[TransitionHandler])
+  final def onTransition(transitionHandlerBuilder: 
FSMTransitionHandlerBuilder[S]): Unit = {
+    val pf = transitionHandlerBuilder.build()
+    super.onTransition(new TransitionHandler {
+      def isDefinedAt(in: (S, S)): Boolean = pf.isDefinedAt(Pair(in._1, in._2))
+      def apply(in: (S, S)): Unit = pf(Pair(in._1, in._2))
+    })
+  }
 
   /**
    * Add a handler which is called upon each state transition, i.e. not when
diff --git 
a/persistence/src/main/scala/org/apache/pekko/persistence/fsm/PersistentFSMBase.scala
 
b/persistence/src/main/scala/org/apache/pekko/persistence/fsm/PersistentFSMBase.scala
index 1e7455a0fb..4a47db624a 100644
--- 
a/persistence/src/main/scala/org/apache/pekko/persistence/fsm/PersistentFSMBase.scala
+++ 
b/persistence/src/main/scala/org/apache/pekko/persistence/fsm/PersistentFSMBase.scala
@@ -23,6 +23,7 @@ import language.implicitConversions
 import org.apache.pekko
 import pekko.actor._
 import pekko.japi.function.{ Effect, Function2, Predicate, Predicate2, 
Procedure, Procedure2, Procedure3 }
+import pekko.japi.Pair
 import pekko.japi.pf.{ FSMTransitionHandlerBuilder, UnitMatch, UnitPFBuilder }
 import pekko.routing.{ Deafen, Listen, Listeners }
 
@@ -850,8 +851,13 @@ abstract class AbstractPersistentFSMBase[S, D, E] extends 
PersistentFSMBase[S, D
    * <b>Multiple handlers may be installed, and every one of them will be
    * called, not only the first one matching.</b>
    */
-  final def onTransition(transitionHandlerBuilder: 
FSMTransitionHandlerBuilder[S]): Unit =
-    
onTransition(transitionHandlerBuilder.build().asInstanceOf[TransitionHandler])
+  final def onTransition(transitionHandlerBuilder: 
FSMTransitionHandlerBuilder[S]): Unit = {
+    val pf = transitionHandlerBuilder.build()
+    onTransition(new TransitionHandler {
+      def isDefinedAt(in: (S, S)): Boolean = pf.isDefinedAt(Pair(in._1, in._2))
+      def apply(in: (S, S)): Unit = pf(Pair(in._1, in._2))
+    })
+  }
 
   /**
    * Add a handler which is called upon each state transition, i.e. not when


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to