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

He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git


The following commit(s) were added to refs/heads/main by this push:
     new 2408ac0140 fix: support java.math.BigInteger/BigDecimal in 
MetricNumericConverter (#3382)
2408ac0140 is described below

commit 2408ac01402b119010ca9b434680bb04305ac27a
Author: He-Pin(kerr) <[email protected]>
AuthorDate: Sun Jul 26 05:28:33 2026 +0800

    fix: support java.math.BigInteger/BigDecimal in MetricNumericConverter 
(#3382)
    
    * fix: support java.math.BigInteger/BigDecimal in MetricNumericConverter
    
    Motivation:
    MetricNumericConverter.convertNumber only handled scala.math.BigInt and
    scala.math.BigDecimal but not java.math.BigInteger and java.math.BigDecimal.
    Since Metric.value is typed as java.lang.Number, Java users passing
    java.math.BigInteger or java.math.BigDecimal would get an
    IllegalArgumentException. The NumberInputStream deserializer also explicitly
    allows java.math classes through, so deserialized values could also fail.
    
    Modification:
    Add pattern match cases for java.math.BigInteger and java.math.BigDecimal in
    convertNumber, placed before the Scala BigInt/BigDecimal cases.
    
    Result:
    Java users can now use java.math.BigInteger and java.math.BigDecimal as
    Metric values without errors.
    
    Tests:
    - sbt "cluster-metrics / Test / testOnly 
org.apache.pekko.cluster.metrics.MetricNumericConverterSpec"
    - sbt "cluster-metrics / Test / testOnly 
org.apache.pekko.cluster.metrics.protobuf.MessageSerializerSpec"
    - All tests passed.
    
    References:
    None - discovered during Java API audit for Scala number type usage
    
    * fix: support any Number subtype in MetricNumericConverter and 
MessageSerializer
    
    Motivation:
    Metric.value is typed as java.lang.Number, so any Number subtype should
    be accepted without throwing. Previously only Int/Long/Double/Float/BigInt/
    BigDecimal were handled; other Number subtypes (Short, Byte, AtomicInteger,
    AtomicLong, java.math.BigInteger) would throw IllegalArgumentException.
    Additionally, BigInt/BigDecimal fell through to Java ObjectOutputStream
    serialization in numberToProto, which is inefficient and a security concern.
    
    Modification:
    - Add generic `case n: Number => Left(n.longValue)` catch-all in
      convertNumber, with java.math.BigDecimal kept explicit (needs doubleValue)
    - Add BigInt/BigDecimal/java.math.BigDecimal and generic Number cases in
      numberToProto to use efficient protobuf encoding (Long/Double) instead of
      Java serialization fallback
    - Expand tests to cover Short, Byte, AtomicInteger, AtomicLong, BigDecimal,
      java.math.BigInteger, java.math.BigDecimal round-trip serialization
    
    Result:
    Any java.lang.Number subtype is now accepted as a Metric value. 
Serialization
    uses efficient protobuf encoding for all Number types, eliminating Java
    ObjectOutputStream usage for known numeric types.
    
    Tests:
    - sbt "cluster-metrics / Test / test" — All 59 tests passed
    
    References:
    Refs #3385
---
 .../org/apache/pekko/cluster/metrics/Metric.scala  | 17 ++++++++------
 .../metrics/protobuf/MessageSerializer.scala       |  9 +++++++-
 .../apache/pekko/cluster/metrics/MetricSpec.scala  | 26 ++++++++++++++++++++++
 .../metrics/protobuf/MessageSerializerSpec.scala   |  6 ++++-
 4 files changed, 49 insertions(+), 9 deletions(-)

diff --git 
a/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/Metric.scala 
b/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/Metric.scala
index df7ee5d67e..fe729c7f9b 100644
--- 
a/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/Metric.scala
+++ 
b/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/Metric.scala
@@ -271,13 +271,16 @@ private[metrics] trait MetricNumericConverter {
    * May involve rounding or truncation.
    */
   def convertNumber(from: Any): Either[Long, Double] = from match {
-    case n: Int        => Left(n)
-    case n: Long       => Left(n)
-    case n: Double     => Right(n)
-    case n: Float      => Right(n)
-    case n: BigInt     => Left(n.longValue)
-    case n: BigDecimal => Right(n.doubleValue)
-    case x             => throw new IllegalArgumentException(s"Not a number 
[$x]")
+    case n: Int                  => Left(n)
+    case n: Long                 => Left(n)
+    case n: Double               => Right(n)
+    case n: Float                => Right(n)
+    case n: BigInt               => Left(n.longValue)
+    case n: BigDecimal           => Right(n.doubleValue)
+    case n: java.math.BigInteger => Left(n.longValue)
+    case n: java.math.BigDecimal => Right(n.doubleValue)
+    case n: Number               => Left(n.longValue)
+    case x                       => throw new IllegalArgumentException(s"Not a 
number [$x]")
   }
 
 }
diff --git 
a/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializer.scala
 
b/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializer.scala
index 706c221e1a..02144eb92b 100644
--- 
a/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializer.scala
+++ 
b/cluster-metrics/src/main/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializer.scala
@@ -193,7 +193,14 @@ class MessageSerializer(val system: ExtendedActorSystem) 
extends SerializerWithS
         case n: jl.Long    => 
Number.newBuilder().setType(NumberType.Long).setValue64(n)
         case n: jl.Float   => 
Number.newBuilder().setType(NumberType.Float).setValue32(jl.Float.floatToIntBits(n))
         case n: jl.Integer => 
Number.newBuilder().setType(NumberType.Integer).setValue32(n)
-        case _             =>
+        case n: BigInt     => 
Number.newBuilder().setType(NumberType.Long).setValue64(n.longValue)
+        case n: BigDecimal =>
+          
Number.newBuilder().setType(NumberType.Double).setValue64(jl.Double.doubleToLongBits(n.doubleValue))
+        case n: java.math.BigInteger => 
Number.newBuilder().setType(NumberType.Long).setValue64(n.longValue)
+        case n: java.math.BigDecimal =>
+          
Number.newBuilder().setType(NumberType.Double).setValue64(jl.Double.doubleToLongBits(n.doubleValue))
+        case n: java.lang.Number => 
Number.newBuilder().setType(NumberType.Long).setValue64(n.longValue)
+        case _                   =>
           val bos = new ByteArrayOutputStream
           val out = new ObjectOutputStream(bos)
           out.writeObject(number)
diff --git 
a/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/MetricSpec.scala
 
b/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/MetricSpec.scala
index e8cc708979..a5268eb2ce 100644
--- 
a/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/MetricSpec.scala
+++ 
b/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/MetricSpec.scala
@@ -39,6 +39,32 @@ class MetricNumericConverterSpec extends AnyWordSpec with 
Matchers with MetricNu
       convertNumber(0.0).isRight should ===(true)
     }
 
+    "convert java.math.BigInteger and java.math.BigDecimal" in {
+      convertNumber(java.math.BigInteger.valueOf(42)).isLeft should ===(true)
+      convertNumber(java.math.BigInteger.valueOf(42)).left.get should ===(42L)
+      convertNumber(new java.math.BigDecimal("3.14")).isRight should ===(true)
+      convertNumber(new java.math.BigDecimal("3.14")).right.get should 
===(3.14 +- 0.0001)
+    }
+
+    "convert any Number subtype" in {
+      convertNumber(42.toShort).isLeft should ===(true)
+      convertNumber(42.toShort).left.get should ===(42L)
+      convertNumber(7.toByte).isLeft should ===(true)
+      convertNumber(7.toByte).left.get should ===(7L)
+      convertNumber(new java.util.concurrent.atomic.AtomicInteger(99)).isLeft 
should ===(true)
+      convertNumber(new 
java.util.concurrent.atomic.AtomicInteger(99)).left.get should ===(99L)
+      convertNumber(new java.util.concurrent.atomic.AtomicLong(1234L)).isLeft 
should ===(true)
+      convertNumber(new 
java.util.concurrent.atomic.AtomicLong(1234L)).left.get should ===(1234L)
+    }
+
+    "define a metric with java.math.BigInteger and java.math.BigDecimal 
values" in {
+      val Some(intMetric) = Metric.create("big-int", 
java.math.BigInteger.valueOf(1024L), None): @unchecked
+      intMetric.value should ===(java.math.BigInteger.valueOf(1024L))
+
+      val Some(decMetric) = Metric.create("big-dec", new 
java.math.BigDecimal("99.9"), None): @unchecked
+      decMetric.value should ===(new java.math.BigDecimal("99.9"))
+    }
+
     "define a new metric" in {
       val Some(metric) = Metric.create(HeapMemoryUsed, 256L, decayFactor = 
Some(0.18)): @unchecked
       metric.name should ===(HeapMemoryUsed)
diff --git 
a/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializerSpec.scala
 
b/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializerSpec.scala
index 3a538c4a77..21b16ccd06 100644
--- 
a/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializerSpec.scala
+++ 
b/cluster-metrics/src/test/scala/org/apache/pekko/cluster/metrics/protobuf/MessageSerializerSpec.scala
@@ -63,7 +63,11 @@ class MessageSerializerSpec extends PekkoSpec("""
               Metric("bar2", Float.MaxValue, None),
               Metric("bar3", Int.MaxValue, None),
               Metric("bar4", Long.MaxValue, None),
-              Metric("bar5", BigInt(Long.MaxValue), None)))))
+              Metric("bar5", BigInt(Long.MaxValue), None),
+              Metric("bar6", BigDecimal("3.14"), None),
+              Metric("bar7", java.math.BigInteger.valueOf(9999L), None),
+              Metric("bar8", new java.math.BigDecimal("2.718"), None),
+              Metric("bar9", 42.toShort, None)))))
 
       checkSerialization(MetricsGossipEnvelope(a1.address, metricsGossip, 
true))
 


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

Reply via email to