feat: ffi uint types
This commit is contained in:
parent
42c30b84dd
commit
8eee5f3827
@ -51,9 +51,10 @@ lazy val root = project
|
||||
println(Seq("sh", "-c", cmd) !!)
|
||||
},
|
||||
cargoBuild := {
|
||||
val cmd =
|
||||
Seq("sh", "-c", "cd glue; cargo rustc -- -Awarnings")
|
||||
println(cmd !!)
|
||||
println(Seq("sh", "-c", "cd glue; cargo rustc -- -Awarnings") !!)
|
||||
println(
|
||||
Seq("sh", "-c", "cd glue; cargo test --quiet") !!
|
||||
) // very important: test suite validates interfaces
|
||||
},
|
||||
fullBuild := {
|
||||
cargoBuild.value
|
||||
|
@ -18,7 +18,6 @@ macro_rules! package {
|
||||
(ext $start:ident.$($thing:ident).+) => {concat!(stringify!($start), $("/", stringify!($thing)),+)};
|
||||
}
|
||||
|
||||
pub mod uint;
|
||||
pub mod message_code;
|
||||
pub mod message_opt_ref;
|
||||
pub mod message_opt_value_ref;
|
||||
@ -26,6 +25,7 @@ pub mod message_ref;
|
||||
pub mod message_type;
|
||||
pub mod retry_strategy;
|
||||
pub mod runtime_config;
|
||||
pub mod uint;
|
||||
|
||||
// Class: dev_toad_Runtime
|
||||
// Method: init
|
||||
@ -39,13 +39,30 @@ pub unsafe extern "system" fn Java_dev_toad_Runtime_init<'local>(mut env: JNIEnv
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use jni::{InitArgsBuilder, JavaVM};
|
||||
use std::sync::Once;
|
||||
|
||||
use jni::{InitArgsBuilder, JNIEnv, JavaVM};
|
||||
use toad::retry::Strategy;
|
||||
use toad::time::Millis;
|
||||
|
||||
use crate::retry_strategy::RetryStrategy;
|
||||
use crate::runtime_config::RuntimeConfig;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
pub fn init<'a>() -> JNIEnv<'a> {
|
||||
INIT.call_once(|| {
|
||||
let jvm =
|
||||
JavaVM::new(InitArgsBuilder::new().option("--enable-preview")
|
||||
.option("-Djava.class.path=../target/scala-3.2.2/classes/")
|
||||
.build()
|
||||
.unwrap()).unwrap();
|
||||
toad_jni::global::init_with(jvm);
|
||||
});
|
||||
|
||||
toad_jni::global::jvm().attach_current_thread_permanently();
|
||||
toad_jni::global::env()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package() {
|
||||
assert_eq!(package!(dev.toad.msg.Foo.Bar.Baz),
|
||||
@ -54,20 +71,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jvm_tests() {
|
||||
let jvm =
|
||||
JavaVM::new(InitArgsBuilder::new().option("--enable-preview")
|
||||
.option("-Djava.class.path=../target/scala-3.2.2/classes/")
|
||||
.build()
|
||||
.unwrap()).unwrap();
|
||||
jvm.attach_current_thread_permanently();
|
||||
toad_jni::global::init_with(jvm);
|
||||
|
||||
let mut e = toad_jni::global::env();
|
||||
fn runtime_config() {
|
||||
let mut e = init();
|
||||
let e = &mut e;
|
||||
|
||||
let r = RuntimeConfig::new(e);
|
||||
assert_eq!(r.to_toad(e), Default::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_strategy() {
|
||||
let mut e = init();
|
||||
let e = &mut e;
|
||||
|
||||
let r = Strategy::Exponential { init_min: Millis::new(0),
|
||||
init_max: Millis::new(100) };
|
||||
@ -77,4 +92,24 @@ mod tests {
|
||||
max: Millis::new(100) };
|
||||
assert_eq!(RetryStrategy::from_toad(e, r).to_toad(e), r);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uint() {
|
||||
use crate::uint;
|
||||
|
||||
let mut e = init();
|
||||
let e = &mut e;
|
||||
|
||||
macro_rules! case {
|
||||
($u:ident) => {{
|
||||
assert_eq!(uint::$u::from_rust(e, $u::MAX).to_rust(e), $u::MAX);
|
||||
assert_eq!(uint::$u::from_rust(e, 0).to_rust(e), 0);
|
||||
}};
|
||||
}
|
||||
|
||||
case!(u64);
|
||||
case!(u32);
|
||||
case!(u16);
|
||||
case!(u8);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ use toad_jni::cls::java;
|
||||
use toad_jni::convert::Object;
|
||||
use toad_jni::Sig;
|
||||
|
||||
use crate::uint;
|
||||
|
||||
pub struct RetryStrategy(GlobalRef);
|
||||
|
||||
impl RetryStrategy {
|
||||
@ -29,13 +31,13 @@ impl RetryStrategy {
|
||||
}
|
||||
|
||||
pub fn millis_field<'a>(&self, e: &mut JNIEnv<'a>, key: &str) -> Millis {
|
||||
let o = e.get_field(&self.0, key, Sig::class(java::time::Duration::PATH))
|
||||
let o = e.get_field(&self.0, key, Sig::class(uint::u64::PATH))
|
||||
.unwrap()
|
||||
.l()
|
||||
.unwrap();
|
||||
let g = e.new_global_ref(o).unwrap();
|
||||
let d = java::time::Duration::from_java(g);
|
||||
Millis::new(d.to_millis(e) as u64)
|
||||
let d = uint::u64::from_java(g);
|
||||
Millis::new(d.to_rust(e))
|
||||
}
|
||||
|
||||
pub fn to_toad<'a>(self, e: &mut JNIEnv<'a>) -> Strategy {
|
||||
|
173
glue/src/uint.rs
173
glue/src/uint.rs
@ -1,58 +1,139 @@
|
||||
use jni::{JNIEnv, objects::{JObject, JByteArray}};
|
||||
use core::primitive as rust;
|
||||
|
||||
use jni::objects::{GlobalRef, JByteArray, JObject};
|
||||
use jni::JNIEnv;
|
||||
use toad_jni::cls::java;
|
||||
use toad_jni::convert::Object;
|
||||
use toad_jni::Sig;
|
||||
|
||||
pub mod path {
|
||||
pub const U64: &'static str = package!(dev.toad.ffi.u64);
|
||||
pub const U32: &'static str = package!(dev.toad.ffi.u32);
|
||||
pub const U16: &'static str = package!(dev.toad.ffi.u16);
|
||||
pub const U8: &'static str = package!(dev.toad.ffi.u8);
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct u64(GlobalRef);
|
||||
impl u64 {
|
||||
pub const PATH: &'static str = package!(dev.toad.ffi.u64);
|
||||
pub const CTOR: Sig = Sig::new().arg(Sig::class(java::math::BigInteger::PATH))
|
||||
.returning(Sig::VOID);
|
||||
pub const BIGINT_VALUE: Sig = Sig::new().returning(Sig::class(java::math::BigInteger::PATH));
|
||||
|
||||
pub fn to_rust<'a>(&self, e: &mut JNIEnv<'a>) -> rust::u64 {
|
||||
let bi = e.call_method(self.0.as_obj(), "bigintValue", Self::BIGINT_VALUE, &[])
|
||||
.unwrap()
|
||||
.l()
|
||||
.unwrap();
|
||||
let bi = e.new_global_ref(bi).unwrap();
|
||||
let bi = java::math::BigInteger::from_java(bi);
|
||||
bi.to_i128(e) as rust::u64
|
||||
}
|
||||
|
||||
pub fn from_rust<'a>(e: &mut JNIEnv<'a>, u: rust::u64) -> Self {
|
||||
let bi = java::math::BigInteger::from_be_bytes(e, &i128::from(u).to_be_bytes());
|
||||
let bi = e.new_object(Self::PATH, Self::CTOR, &[bi.to_java().as_obj().into()])
|
||||
.unwrap();
|
||||
Self(e.new_global_ref(bi).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u64<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u64 {
|
||||
let bi = e.call_method(o, "bigintValue", Sig::new().returning(Sig::class("java.math.BigInteger")), &[]).unwrap().l().unwrap();
|
||||
let barr: JByteArray<'a> = e.call_method(bi, "toByteArray", Sig::new().returning(Sig::array_of(Sig::BYTE)), &[]).unwrap().l().unwrap().try_into().unwrap();
|
||||
impl Object for u64 {
|
||||
fn from_java(jobj: GlobalRef) -> Self {
|
||||
Self(jobj)
|
||||
}
|
||||
|
||||
let mut bytes = [0i8; 8];
|
||||
|
||||
// BigInteger is a growable two's complement integer
|
||||
//
|
||||
// the "growable" comes from its backing structure being a simple
|
||||
// int array `int[]`, where bytes are added as needed to afford capacity.
|
||||
//
|
||||
// two's-complement means the most significant bit (the first bit of the first byte)
|
||||
// indicates the sign of the integer, where 0 is positive and 1 is negative.
|
||||
//
|
||||
// The rest of the bits are unchanged, meaning the range is from `-(2^(n - 1))`
|
||||
// to `2^(n - 1) - 1`.
|
||||
//
|
||||
// For example, a two's complement i8 would be able to represent `-128` (`0b11111111`),
|
||||
// `0` (`0b00000000`) to `127` (`0b01111111`). for positive integers, the representation is
|
||||
// the same as unsigned integers, meaning we simply need to make sure we don't accidentally
|
||||
// interpret the first bit as part of the integer.
|
||||
//
|
||||
// Here we assume whoever is responsible for BigInteger made sure that it's positive,
|
||||
// so converting the big-endian two's complement int
|
||||
e.get_byte_array_region(&barr, 0, &mut bytes).unwrap();
|
||||
|
||||
// https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/math/BigInteger.html#toByteArray()
|
||||
//
|
||||
// BigInt.toByteArray actually returns the raw byte representation of the integer, NOT
|
||||
// two's complement `byte`s as the type signature would lead you to believe.
|
||||
//
|
||||
// To interpret these bytes as i8 is incorrect.
|
||||
let bytes = bytes.map(|i| i8::to_be_bytes(i)[0]);
|
||||
|
||||
u64::from_be_bytes(bytes)
|
||||
fn to_java(self) -> GlobalRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u32<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u32 {
|
||||
e.call_method(o, "longValue", Sig::new().returning(Sig::LONG), &[]).unwrap().j().unwrap() as u32
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct u32(GlobalRef);
|
||||
impl u32 {
|
||||
pub const PATH: &'static str = package!(dev.toad.ffi.u32);
|
||||
pub const CTOR: Sig = Sig::new().arg(Sig::LONG).returning(Sig::VOID);
|
||||
pub const LONG_VALUE: Sig = Sig::new().returning(Sig::LONG);
|
||||
|
||||
pub fn to_rust<'a>(&self, e: &mut JNIEnv<'a>) -> rust::u32 {
|
||||
let long = e.call_method(self.0.as_obj(), "longValue", Self::LONG_VALUE, &[])
|
||||
.unwrap()
|
||||
.j()
|
||||
.unwrap();
|
||||
long as rust::u32
|
||||
}
|
||||
|
||||
pub fn from_rust<'a>(e: &mut JNIEnv<'a>, u: rust::u32) -> Self {
|
||||
let bi = e.new_object(Self::PATH, Self::CTOR, &[rust::i64::from(u).into()])
|
||||
.unwrap();
|
||||
Self(e.new_global_ref(bi).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u16<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u16 {
|
||||
e.call_method(o, "intValue", Sig::new().returning(Sig::INT), &[]).unwrap().i().unwrap() as u16
|
||||
impl Object for u32 {
|
||||
fn from_java(jobj: GlobalRef) -> Self {
|
||||
Self(jobj)
|
||||
}
|
||||
|
||||
fn to_java(self) -> GlobalRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u8<'a>(e: &mut JNIEnv<'a>, o: JObject<'a>) -> u8 {
|
||||
e.call_method(o, "shortValue", Sig::new().returning(Sig::SHORT), &[]).unwrap().s().unwrap() as u8
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct u16(GlobalRef);
|
||||
impl u16 {
|
||||
pub const PATH: &'static str = package!(dev.toad.ffi.u16);
|
||||
pub const CTOR: Sig = Sig::new().arg(Sig::INT).returning(Sig::VOID);
|
||||
pub const INT_VALUE: Sig = Sig::new().returning(Sig::INT);
|
||||
|
||||
pub fn to_rust<'a>(&self, e: &mut JNIEnv<'a>) -> rust::u16 {
|
||||
let int = e.call_method(self.0.as_obj(), "intValue", Self::INT_VALUE, &[])
|
||||
.unwrap()
|
||||
.i()
|
||||
.unwrap();
|
||||
int as rust::u16
|
||||
}
|
||||
|
||||
pub fn from_rust<'a>(e: &mut JNIEnv<'a>, u: rust::u16) -> Self {
|
||||
let bi = e.new_object(Self::PATH, Self::CTOR, &[rust::i32::from(u).into()])
|
||||
.unwrap();
|
||||
Self(e.new_global_ref(bi).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for u16 {
|
||||
fn from_java(jobj: GlobalRef) -> Self {
|
||||
Self(jobj)
|
||||
}
|
||||
|
||||
fn to_java(self) -> GlobalRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct u8(GlobalRef);
|
||||
impl u8 {
|
||||
pub const PATH: &'static str = package!(dev.toad.ffi.u8);
|
||||
pub const CTOR: Sig = Sig::new().arg(Sig::SHORT).returning(Sig::VOID);
|
||||
pub const SHORT_VALUE: Sig = Sig::new().returning(Sig::SHORT);
|
||||
|
||||
pub fn to_rust<'a>(&self, e: &mut JNIEnv<'a>) -> rust::u8 {
|
||||
let int = e.call_method(self.0.as_obj(), "shortValue", Self::SHORT_VALUE, &[])
|
||||
.unwrap()
|
||||
.s()
|
||||
.unwrap();
|
||||
int as rust::u8
|
||||
}
|
||||
|
||||
pub fn from_rust<'a>(e: &mut JNIEnv<'a>, u: rust::u8) -> Self {
|
||||
let bi = e.new_object(Self::PATH, Self::CTOR, &[rust::i16::from(u).into()])
|
||||
.unwrap();
|
||||
Self(e.new_global_ref(bi).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Object for u8 {
|
||||
fn from_java(jobj: GlobalRef) -> Self {
|
||||
Self(jobj)
|
||||
}
|
||||
|
||||
fn to_java(self) -> GlobalRef {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
package dev.toad;
|
||||
|
||||
public abstract sealed class RetryStrategy permits RetryStrategyDelay, RetryStrategyExponential { }
|
||||
public abstract sealed class RetryStrategy
|
||||
permits RetryStrategyDelay, RetryStrategyExponential {}
|
||||
|
@ -1,20 +1,31 @@
|
||||
package dev.toad;
|
||||
|
||||
import dev.toad.ffi.*;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class RetryStrategyDelay extends RetryStrategy {
|
||||
|
||||
public final Duration min;
|
||||
public final Duration max;
|
||||
public final u64 min;
|
||||
public final u64 max;
|
||||
|
||||
private static native RetryStrategyDelay fromRust(byte[] mem);
|
||||
|
||||
private native byte[] toRust();
|
||||
|
||||
public RetryStrategyDelay(Duration min, Duration max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
if (min.isNegative() || max.isNegative()) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"{min: %, max: %} neither field may be negative",
|
||||
min.toMillis(),
|
||||
max.toMillis()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.min = new u64(min.toMillis());
|
||||
this.max = new u64(max.toMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,27 +1,39 @@
|
||||
package dev.toad;
|
||||
|
||||
import dev.toad.ffi.*;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class RetryStrategyExponential extends RetryStrategy {
|
||||
public final class RetryStrategyExponential extends RetryStrategy {
|
||||
|
||||
public final Duration initMin;
|
||||
public final Duration initMax;
|
||||
public final u64 initMin;
|
||||
public final u64 initMax;
|
||||
|
||||
private static native RetryStrategyExponential fromRust(byte[] mem);
|
||||
private static native RetryStrategyExponential fromRust(byte[] mem);
|
||||
|
||||
private native byte[] toRust();
|
||||
private native byte[] toRust();
|
||||
|
||||
public RetryStrategyExponential(Duration initMin, Duration initMax) {
|
||||
this.initMin = initMin;
|
||||
this.initMax = initMax;
|
||||
public RetryStrategyExponential(Duration initMin, Duration initMax) {
|
||||
if (initMin.isNegative() || initMax.isNegative()) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"{initMin: %, initMax: %} neither field may be negative",
|
||||
initMin.toMillis(),
|
||||
initMax.toMillis()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.initMin = new u64(initMin.toMillis());
|
||||
this.initMax = new u64(initMax.toMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return switch (other) {
|
||||
case RetryStrategyExponential e -> e.initMin == this.initMin && e.initMax == this.initMax;
|
||||
case RetryStrategyExponential e -> e.initMin == this.initMin &&
|
||||
e.initMax == this.initMax;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public final class RuntimeOptions implements Cloneable {
|
||||
|
||||
public Net() {
|
||||
this.port = new u16(5683);
|
||||
this.concurrency = new u8((short)1);
|
||||
this.concurrency = new u8((short) 1);
|
||||
this.msg = new Msg();
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
package dev.toad.ffi;
|
||||
|
||||
public final class u16 {
|
||||
public static final int MAX = (int)(Math.pow(2, 16) - 1);
|
||||
|
||||
public static final int MAX = (int) (Math.pow(2, 16) - 1);
|
||||
private final int l;
|
||||
|
||||
public u16(int l) {
|
||||
|
@ -1,7 +1,8 @@
|
||||
package dev.toad.ffi;
|
||||
|
||||
public final class u32 {
|
||||
public static final long MAX = (long)(Math.pow(2, 32) - 1);
|
||||
|
||||
public static final long MAX = (long) (Math.pow(2, 32) - 1);
|
||||
private final long l;
|
||||
|
||||
public u32(long l) {
|
||||
|
@ -1,17 +1,24 @@
|
||||
package dev.toad.ffi;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public final class u64 {
|
||||
public static final double MAX = Math.pow(2, 64) - 1;
|
||||
private final double l;
|
||||
|
||||
public u64(double l) {
|
||||
public static final BigInteger MAX = BigInteger.TWO
|
||||
.pow(64)
|
||||
.subtract(BigInteger.ONE);
|
||||
private final BigInteger l;
|
||||
|
||||
public u64(BigInteger l) {
|
||||
uint.assertWithinRange(this.MAX, l);
|
||||
uint.assertNatural(l);
|
||||
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
public double doubleValue() {
|
||||
public u64(long l) {
|
||||
this(BigInteger.valueOf(l));
|
||||
}
|
||||
|
||||
public BigInteger bigintValue() {
|
||||
return this.l;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package dev.toad.ffi;
|
||||
|
||||
public final class u8 {
|
||||
public static final short MAX = (short)(Math.pow(2, 8) - 1);
|
||||
|
||||
public static final short MAX = (short) (Math.pow(2, 8) - 1);
|
||||
private final short l;
|
||||
|
||||
public u8(short l) {
|
||||
|
@ -1,15 +1,18 @@
|
||||
package dev.toad.ffi;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class uint {
|
||||
public static void assertWithinRange(double max, double n) {
|
||||
if (n < 0 || n > max) {
|
||||
throw new IllegalArgumentException(String.format("% must be between 0 and %", n, max));
|
||||
}
|
||||
|
||||
public static void assertWithinRange(long max, long n) {
|
||||
uint.assertWithinRange(BigInteger.valueOf(max), BigInteger.valueOf(n));
|
||||
}
|
||||
|
||||
public static void assertNatural(double n) {
|
||||
if (n % 1 > 0.0) {
|
||||
throw new IllegalArgumentException(String.format("% must be a whole integer", n));
|
||||
public static void assertWithinRange(BigInteger max, BigInteger n) {
|
||||
if (n.compareTo(BigInteger.ZERO) < 0 || n.compareTo(max) > 0) {
|
||||
throw new IllegalArgumentException(
|
||||
n.toString() + " must be between 0 and " + max.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user