risingwave_jni_core/
macros.rs

1// Copyright 2025 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Utils
16/// A macro that splits the input by comma and calls the callback `$macro` with the split result.
17/// The each part of the split result is within a bracket, and the callback `$macro` can have its
18/// first parameter as `{$({$($args:tt)+})*}` to match the split result.
19///
20/// ```
21/// macro_rules! call_split_by_comma {
22///     ({$({$($args:tt)+})*}) => {
23///         [$(
24///            stringify! {$($args)+}
25///         ),*]
26///     };
27///     ($($input:tt)*) => {{
28///         risingwave_jni_core::split_by_comma! {
29///             {$($input)*},
30///             call_split_by_comma
31///         }
32///     }};
33/// }
34///
35/// let expected_result = [
36///     "hello",
37///     "my friend",
38/// ];
39///
40/// assert_eq!(expected_result, {call_split_by_comma!(hello, my friend)});
41/// ```
42#[macro_export]
43macro_rules! split_by_comma {
44    // Entry of macro. Put input as the first parameter and initialize empty
45    // second and third parameter.
46    (
47        {$($input:tt)*},
48        $macro:path $(,$args:tt)*
49    ) => {
50        $crate::split_by_comma! {
51            {$($input)*},
52            {}, // previous splits
53            {}, // current split
54            $macro $(,$args)*
55        }
56    };
57    // When the first token is comma, move the tokens of current split to the
58    // list of previous splits with a bracket wrapped, and then clear the current
59    // split.
60    (
61        {
62            ,
63            $($rest:tt)*
64        },
65        {
66            $(
67                {$($prev_split:tt)*}
68            )*
69        },
70        {
71            $($current_split:tt)*
72        },
73        $macro:path $(,$args:tt)*
74    ) => {
75        $crate::split_by_comma! {
76            {
77                $($rest)*
78            },
79            {
80                $(
81                    {$($prev_split)*}
82                )*
83                {
84                    $($current_split)*
85                }
86            },
87            {},
88            $macro $(,$args)*
89        }
90    };
91    // Do the same as the previous match when we just reach the end of input,
92    // with the content of last split not added to the list of previous split yet.
93    (
94        {},
95        {
96            $(
97                {$($prev_split:tt)*}
98            )*
99        },
100        {
101            $($current_split:tt)+
102        },
103        $macro:path $(,$args:tt)*
104    ) => {
105        $crate::split_by_comma! {
106            {},
107            {
108                $(
109                    {$($prev_split)*}
110                )*
111                {
112                    $($current_split)+
113                }
114            },
115            {},
116            $macro $(,$args)*
117        }
118    };
119    // For a token that is not comma, add to the list of current staging split.
120    (
121        {
122            $first:tt
123            $($rest:tt)*
124        },
125        {
126            $(
127                {$($prev_split:tt)*}
128            )*
129        },
130        {
131            $($current_split:tt)*
132        },
133        $macro:path $(,$args:tt)*
134    ) => {
135        $crate::split_by_comma! {
136            {
137                $($rest)*
138            },
139            {
140                $(
141                    {$($prev_split)*}
142                )*
143            },
144            {
145                $($current_split)* $first
146            },
147            $macro $(,$args)*
148        }
149    };
150    // When all split result are added to the list, call the callback `$macro` with the result.
151    (
152        {},
153        {
154            $(
155                {$($prev_split:tt)*}
156            )*
157        },
158        {},
159        $macro:path $(,$args:tt)*
160    ) => {
161        $macro! {
162            {
163                $(
164                    {$($prev_split)*}
165                )*
166            }
167            $(,$args)*
168        }
169    };
170}
171// End of utils part
172
173/// Generate the dot separated java class name to the slash separated name.
174///
175/// ```
176/// assert_eq!(
177///     "java/lang/String",
178///     risingwave_jni_core::gen_class_name!(java.lang.String)
179/// );
180/// assert_eq!(
181///     "java/lang/String",
182///     risingwave_jni_core::gen_class_name!(String)
183/// );
184/// ```
185#[macro_export]
186macro_rules! gen_class_name {
187    // A single part class name will be prefixed with `java.lang.`
188    ($single_part_class:ident $($param_name:ident)?) => {
189        $crate::gen_class_name! { @inner java.lang.$single_part_class }
190    };
191    ($($class:ident).+ $($param_name:ident)?) => {
192        $crate::gen_class_name! { @inner $($class).+ }
193    };
194    (@inner $last:ident) => {
195        stringify! {$last}
196    };
197    (@inner $first:ident . $($rest:ident).+) => {
198        concat! {stringify! {$first}, "/", $crate::gen_class_name! {@inner $($rest).+} }
199    }
200}
201
202/// Generate the type signature of a single type
203/// ```
204/// use risingwave_jni_core::gen_jni_type_sig;
205/// assert_eq!("Z", gen_jni_type_sig!(boolean));
206/// assert_eq!("B", gen_jni_type_sig!(byte));
207/// assert_eq!("C", gen_jni_type_sig!(char));
208/// assert_eq!("S", gen_jni_type_sig!(short));
209/// assert_eq!("I", gen_jni_type_sig!(int));
210/// assert_eq!("J", gen_jni_type_sig!(long));
211/// assert_eq!("F", gen_jni_type_sig!(float));
212/// assert_eq!("D", gen_jni_type_sig!(double));
213/// assert_eq!("V", gen_jni_type_sig!(void));
214/// assert_eq!("Ljava/lang/Class;", gen_jni_type_sig!(Class<?>));
215/// assert_eq!("Ljava/lang/String;", gen_jni_type_sig!(String));
216/// assert_eq!("[B", gen_jni_type_sig!(byte[]));
217/// ```
218#[macro_export]
219macro_rules! gen_jni_type_sig {
220    (boolean $($param_name:ident)?) => {
221        "Z"
222    };
223    (byte $($param_name:ident)?) => {
224        "B"
225    };
226    (char $($param_name:ident)?) => {
227        "C"
228    };
229    (short $($param_name:ident)?) => {
230        "S"
231    };
232    (int $($param_name:ident)?) => {
233        "I"
234    };
235    (long $($param_name:ident)?) => {
236        "J"
237    };
238    (float $($param_name:ident)?) => {
239        "F"
240    };
241    (double $($param_name:ident)?) => {
242        "D"
243    };
244    (void) => {
245        "V"
246    };
247    (Class $(< ? >)? $($param_name:ident)?) => {
248        $crate::gen_jni_type_sig! { java.lang.Class }
249    };
250    ($($class_part:ident).+ $($param_name:ident)?) => {
251        concat! {"L", $crate::gen_class_name! {$($class_part).+}, ";"}
252    };
253    ($($class_part:ident).+ [] $($param_name:ident)?) => {
254        concat! { "[", $crate::gen_jni_type_sig! {$($class_part).+}}
255    };
256    ($($invalid:tt)*) => {
257        compile_error!(concat!("unsupported type `", stringify!($($invalid)*), "`"))
258    };
259}
260
261/// Cast a `JValueGen` to a concrete type by the given type
262///
263/// ```
264/// use jni::objects::JValue;
265/// use jni::sys::JNI_TRUE;
266/// use risingwave_jni_core::cast_jvalue;
267/// assert_eq!(cast_jvalue!({boolean}, JValue::Bool(JNI_TRUE)), true);
268/// assert_eq!(cast_jvalue!({byte}, JValue::Byte(10 as i8)), 10);
269/// assert_eq!(cast_jvalue!({char}, JValue::Char('c' as u16)), 'c' as u16);
270/// assert_eq!(cast_jvalue!({double}, JValue::Double(3.14)), 3.14);
271/// assert_eq!(cast_jvalue!({float}, JValue::Float(3.14)), 3.14);
272/// assert_eq!(cast_jvalue!({int}, JValue::Int(10)), 10);
273/// assert_eq!(cast_jvalue!({long}, JValue::Long(10)), 10);
274/// assert_eq!(cast_jvalue!({short}, JValue::Short(10)), 10);
275/// cast_jvalue!({void}, JValue::Void);
276/// let null = jni::objects::JObject::null();
277/// let _: &jni::objects::JObject<'_> = cast_jvalue!({String}, JValue::Object(&null));
278/// let _: jni::objects::JByteArray<'_> = cast_jvalue!({byte[]}, jni::objects::JValueOwned::Object(null));
279/// ```
280#[macro_export]
281macro_rules! cast_jvalue {
282    ({ boolean }, $value:expr) => {{ $value.z().expect("should be bool") }};
283    ({ byte }, $value:expr) => {{ $value.b().expect("should be byte") }};
284    ({ char }, $value:expr) => {{ $value.c().expect("should be char") }};
285    ({ double }, $value:expr) => {{ $value.d().expect("should be double") }};
286    ({ float }, $value:expr) => {{ $value.f().expect("should be float") }};
287    ({ int }, $value:expr) => {{ $value.i().expect("should be int") }};
288    ({ long }, $value:expr) => {{ $value.j().expect("should be long") }};
289    ({ short }, $value:expr) => {{ $value.s().expect("should be short") }};
290    ({ void }, $value:expr) => {{ $value.v().expect("should be void") }};
291    ({ byte[] }, $value:expr) => {{
292        let obj = $value.l().expect("should be object");
293        unsafe { jni::objects::JByteArray::from_raw(obj.into_raw()) }
294    }};
295    ({ $($class:tt)+ }, $value:expr) => {{ $value.l().expect("should be object") }};
296}
297
298/// Cast a `JValueGen` to a concrete type by the given type
299///
300/// ```
301/// use jni::sys::JNI_TRUE;
302/// use risingwave_jni_core::{cast_jvalue, to_jvalue};
303/// assert_eq!(
304///     cast_jvalue!({ boolean }, to_jvalue!({ boolean }, JNI_TRUE)),
305///     true
306/// );
307/// assert_eq!(cast_jvalue!({ byte }, to_jvalue!({ byte }, 10)), 10);
308/// assert_eq!(
309///     cast_jvalue!({ char }, to_jvalue!({ char }, 'c')),
310///     'c' as u16
311/// );
312/// assert_eq!(cast_jvalue!({ double }, to_jvalue!({ double }, 3.14)), 3.14);
313/// assert_eq!(cast_jvalue!({ float }, to_jvalue!({ float }, 3.14)), 3.14);
314/// assert_eq!(cast_jvalue!({ int }, to_jvalue!({ int }, 10)), 10);
315/// assert_eq!(cast_jvalue!({ long }, to_jvalue!({ long }, 10)), 10);
316/// assert_eq!(cast_jvalue!({ short }, to_jvalue!({ short }, 10)), 10);
317/// let obj = jni::objects::JObject::null();
318/// cast_jvalue!({ String }, to_jvalue!({ String }, &obj));
319/// ```
320#[macro_export]
321macro_rules! to_jvalue {
322    ({ boolean $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Bool($value as _) }};
323    ({ byte $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Byte($value as _) }};
324    ({ char $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Char($value as _) }};
325    ({ double $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Double($value as _) }};
326    ({ float $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Float($value as _) }};
327    ({ int $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Int($value as _) }};
328    ({ long $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Long($value as _) }};
329    ({ short $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Short($value as _) }};
330    ({ void }, $value:expr) => {{
331        compile_error! {concat! {"unlike to pass void value: ", stringify! {$value} }}
332    }};
333    ({ $($class:ident)+ $([])? $($param_name:ident)? }, $value:expr) => {{ jni::objects::JValue::Object($value as _) }};
334}
335
336/// Generate the jni signature of a given function
337/// ```
338/// use risingwave_jni_core::gen_jni_sig;
339/// assert_eq!(gen_jni_sig!(boolean f(int, short, byte[])), "(IS[B)Z");
340/// assert_eq!(
341///     gen_jni_sig!(boolean f(int, short, byte[], java.lang.String)),
342///     "(IS[BLjava/lang/String;)Z"
343/// );
344/// assert_eq!(
345///     gen_jni_sig!(boolean f(int, java.lang.String)),
346///     "(ILjava/lang/String;)Z"
347/// );
348/// assert_eq!(gen_jni_sig!(public static native int defaultVnodeCount()), "()I");
349/// assert_eq!(
350///     gen_jni_sig!(long hummockIteratorNew(byte[] readPlan)),
351///     "([B)J"
352/// );
353/// assert_eq!(gen_jni_sig!(long hummockIteratorNext(long pointer)), "(J)J");
354/// assert_eq!(
355///     gen_jni_sig!(void hummockIteratorClose(long pointer)),
356///     "(J)V"
357/// );
358/// assert_eq!(gen_jni_sig!(byte[] rowGetKey(long pointer)), "(J)[B");
359/// assert_eq!(
360///     gen_jni_sig!(java.sql.Timestamp rowGetTimestampValue(long pointer, int index)),
361///     "(JI)Ljava/sql/Timestamp;"
362/// );
363/// assert_eq!(
364///     gen_jni_sig!(String rowGetStringValue(long pointer, int index)),
365///     "(JI)Ljava/lang/String;"
366/// );
367/// assert_eq!(
368///     gen_jni_sig!(static native Object rowGetArrayValue(long pointer, int index, Class clazz)),
369///     "(JILjava/lang/Class;)Ljava/lang/Object;"
370/// );
371/// ```
372#[macro_export]
373macro_rules! gen_jni_sig {
374    // handle the result of `split_by_comma`
375    ({$({$($args:tt)+})*}, {return {$($ret:tt)*}}) => {{
376        concat! {
377            "(", $($crate::gen_jni_type_sig!{ $($args)+ },)* ")",
378            $crate::gen_jni_type_sig! {$($ret)+}
379        }
380    }};
381    ({$($ret:tt)*}, {$($args:tt)*}) => {{
382        $crate::split_by_comma! {
383            {$($args)*},
384            $crate::gen_jni_sig,
385            {return {$($ret)*}}
386        }
387    }};
388    // handle the result of `split_extract_plain_native_methods`
389    ({{$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}}}) => {{
390        $crate::gen_jni_sig! {
391            {$($ret)*}, {$($args)*}
392        }
393    }};
394    ($($input:tt)*) => {{
395        $crate::split_extract_plain_native_methods! {{$($input)*;}, $crate::gen_jni_sig}
396    }}
397}
398
399#[macro_export]
400macro_rules! for_all_plain_native_methods {
401    ($macro:path $(,$args:tt)*) => {
402        $macro! {
403            {
404                public static native void tracingSlf4jEvent(String threadName, String name, int level, String message, String stackTrace);
405
406                public static native boolean tracingSlf4jEventEnabled(int level);
407
408                public static native int defaultVnodeCount();
409
410                static native long iteratorNewStreamChunk(long pointer);
411
412                static native boolean iteratorNext(long pointer);
413
414                public static native void initObjectStoreForTest(String stateStoreUrl, String dataDirectory);
415
416                public static native void putObject(String objectName, byte[] data);
417
418                public static native String getObjectStoreType();
419
420                public static native void deleteObjects(String dir);
421
422                public static native byte[] getObject(String objectName);
423
424                public static native String[] listObject(String dir);
425
426                static native void iteratorClose(long pointer);
427
428                static native long newStreamChunkFromPayload(byte[] streamChunkPayload);
429
430                static native long newStreamChunkFromPretty(String str);
431
432                static native void streamChunkClose(long pointer);
433
434                static native byte[] iteratorGetKey(long pointer);
435
436                static native int iteratorGetOp(long pointer);
437
438                static native boolean iteratorIsNull(long pointer, int index);
439
440                static native short iteratorGetInt16Value(long pointer, int index);
441
442                static native int iteratorGetInt32Value(long pointer, int index);
443
444                static native long iteratorGetInt64Value(long pointer, int index);
445
446                static native float iteratorGetFloatValue(long pointer, int index);
447
448                static native double iteratorGetDoubleValue(long pointer, int index);
449
450                static native boolean iteratorGetBooleanValue(long pointer, int index);
451
452                static native String iteratorGetStringValue(long pointer, int index);
453
454                static native java.time.LocalDateTime iteratorGetTimestampValue(long pointer, int index);
455
456                static native java.time.OffsetDateTime iteratorGetTimestamptzValue(long pointer, int index);
457
458                static native java.math.BigDecimal iteratorGetDecimalValue(long pointer, int index);
459
460                static native java.time.LocalTime iteratorGetTimeValue(long pointer, int index);
461
462                static native java.time.LocalDate iteratorGetDateValue(long pointer, int index);
463
464                static native String iteratorGetIntervalValue(long pointer, int index);
465
466                static native String iteratorGetJsonbValue(long pointer, int index);
467
468                static native byte[] iteratorGetByteaValue(long pointer, int index);
469
470                // TODO: object or object array?
471                static native Object iteratorGetArrayValue(long pointer, int index, Class<?> clazz);
472
473                public static native boolean sendCdcSourceMsgToChannel(long channelPtr, byte[] msg);
474
475                public static native boolean sendCdcSourceErrorToChannel(long channelPtr, String errorMsg);
476
477                public static native void cdcSourceSenderClose(long channelPtr);
478
479                public static native com.risingwave.java.binding.JniSinkWriterStreamRequest
480                    recvSinkWriterRequestFromChannel(long channelPtr);
481
482                public static native boolean sendSinkWriterResponseToChannel(long channelPtr, byte[] msg);
483
484                public static native boolean sendSinkWriterErrorToChannel(long channelPtr, String msg);
485
486                public static native byte[] recvSinkCoordinatorRequestFromChannel(long channelPtr);
487
488                public static native boolean sendSinkCoordinatorResponseToChannel(long channelPtr, byte[] msg);
489            }
490            $(,$args)*
491        }
492    };
493}
494
495/// Given the plain text of a list native methods, split the methods by semicolon (;), extract
496/// the return type, argument list and name of the methods and pass the result to the callback
497/// `$macro` with the extracted result as the first parameter. The result can be matched with
498/// pattern `{$({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*}`
499///
500/// ```
501/// macro_rules! call_split_extract_plain_native_methods {
502///     ({$({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*}) => {
503///         [$(
504///             (stringify! {$func_name}, stringify!{$($ret)*}, stringify!{($($args)*)})
505///         ),*]
506///     };
507///     ($($input:tt)*) => {{
508///         risingwave_jni_core::split_extract_plain_native_methods! {
509///             {$($input)*},
510///             call_split_extract_plain_native_methods
511///         }
512///     }}
513/// }
514/// assert_eq!([
515///     ("f", "int", "(int param1, boolean param2)"),
516///     ("f2", "boolean[]", "()"),
517///     ("f3", "java.lang.String", "(byte[] param)")
518/// ], call_split_extract_plain_native_methods!(
519///     int f(int param1, boolean param2);
520///     boolean[] f2();
521///     java.lang.String f3(byte[] param);
522/// ))
523/// ```
524#[macro_export]
525macro_rules! split_extract_plain_native_methods {
526    (
527        {$($input:tt)*},
528        $macro:path
529        $(,$extra_args:tt)*
530    ) => {{
531        $crate::split_extract_plain_native_methods! {
532            {$($input)*},
533            {},
534            $macro
535            $(,$extra_args)*
536        }
537    }};
538    (
539        {
540            $(public)? static native $($first:tt)*
541        },
542        {
543            $($second:tt)*
544        },
545        $macro:path
546        $(,$extra_args:tt)*
547    ) => {
548        $crate::split_extract_plain_native_methods! {
549            {
550                $($first)*
551            },
552            {
553                $($second)*
554            },
555            $macro
556            $(,$extra_args)*
557        }
558    };
559    (
560        {
561            $($ret:tt).+ $func_name:ident($($args:tt)*); $($rest:tt)*
562        },
563        {
564            $({$prev_func_name:ident, {$($prev_ret:tt)*}, {$($prev_args:tt)*}})*
565        },
566        $macro:path
567        $(,$extra_args:tt)*
568    ) => {
569        $crate::split_extract_plain_native_methods! {
570            {$($rest)*},
571            {
572                $({$prev_func_name, {$($prev_ret)*}, {$($prev_args)*}})*
573                {$func_name, {$($ret).+}, {$($args)*}}
574            },
575            $macro
576            $(,$extra_args)*
577        }
578    };
579    (
580        {
581            $($ret:tt).+ [] $func_name:ident($($args:tt)*); $($rest:tt)*
582        },
583        {
584            $({$prev_func_name:ident, {$($prev_ret:tt)*}, {$($prev_args:tt)*}})*
585        },
586        $macro:path
587        $(,$extra_args:tt)*
588    ) => {
589        $crate::split_extract_plain_native_methods! {
590            {$($rest)*},
591            {
592                $({$prev_func_name, {$($prev_ret)*}, {$($prev_args)*}})*
593                {$func_name, {$($ret).+ []}, {$($args)*}}
594            },
595            $macro
596            $(,$extra_args)*
597        }
598    };
599    (
600        {},
601        {
602            $({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*
603        },
604        $macro:path
605        $(,$extra_args:tt)*
606    ) => {
607        $macro! {
608            {
609                $({$func_name, {$($ret)*}, {$($args)*}})*
610            }
611            $(,$extra_args)*
612        }
613    };
614    ($($invalid:tt)*) => {
615        compile_error!(concat!("unable to split extract `", stringify!($($invalid)*), "`"))
616    };
617}
618
619/// Pass the information of all native methods to the callback `$macro`. The input can be matched
620/// with pattern `{$({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*}`
621#[macro_export]
622macro_rules! for_all_native_methods {
623    ($macro:path $(,$args:tt)*) => {{
624        $crate::for_all_plain_native_methods! {
625            $crate::for_all_native_methods,
626            $macro
627            $(,$args)*
628        }
629    }};
630    (
631        {
632            $({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*
633        },
634        $macro:path
635        $(,$extra_args:tt)*
636    ) => {{
637        $macro! {
638            {$({$func_name, {$($ret)*}, {$($args)*}})*}
639            $(,$extra_args)*
640        }
641    }};
642    (
643        {$($input:tt)*},
644        $macro:path
645        $(,$extra_args:tt)*
646    ) => {{
647        $crate::split_extract_plain_native_methods! {
648            {$($input)*},
649            $crate::for_all_native_methods,
650            $macro
651            $(,$extra_args)*
652        }
653    }};
654}
655
656/// Convert the argument value list when invoking a method to a list of `JValue` by the argument type list in the signature
657/// ```
658/// use risingwave_jni_core::convert_args_list;
659/// use jni::objects::{JObject, JValue};
660/// use jni::sys::JNI_TRUE;
661/// let list: [JValue<'static, 'static>; 3] = convert_args_list!(
662///     {boolean first, int second, byte third},
663///     {true, 10, 20}
664/// );
665/// match &list[0] {
666///     JValue::Bool(b) => assert_eq!(*b, JNI_TRUE),
667///     value => unreachable!("wrong value: {:?}", value),
668/// }
669/// match &list[1] {
670///     JValue::Int(v) => assert_eq!(*v, 10),
671///     value => unreachable!("wrong value: {:?}", value),
672/// }
673/// match &list[2] {
674///     JValue::Byte(v) => assert_eq!(*v, 20),
675///     value => unreachable!("wrong value: {:?}", value),
676/// }
677/// ```
678#[macro_export]
679macro_rules! convert_args_list {
680    (
681        {
682            {$($first_args:tt)+}
683            $({$($args:tt)+})*
684        },
685        {
686            {$first_value:expr}
687            $({$value:expr})*
688        },
689        {
690            $({$converted:expr})*
691        }) => {
692        $crate::convert_args_list! {
693            {$({$($args)+})*},
694            {$({$value})*},
695            {
696                $({$converted})*
697                {
698                    $crate::to_jvalue! {
699                        {$($first_args)+},
700                        {$first_value}
701                    }
702                }
703            }
704        }
705    };
706    ({$($args:tt)+}, {}, {$({$converted:expr})*}) => {
707        compile_error! {concat!{"trailing argument not passed: ", stringify!{$($args)+}}}
708    };
709    ({}, {$($value:tt)+}, {$({$converted:expr})*}) => {
710        compile_error! {concat!{"trailing extra value passed: ", stringify!{$($value)+}}}
711    };
712    ({}, {}, {$({$converted:expr})*}) => {
713        [$(
714            $converted
715        ),*]
716    };
717    ({$($args:tt)*}, {$($value:expr),*}) => {{
718        $crate::split_by_comma! {
719            {$($args)*},
720            $crate::convert_args_list,
721            {$({$value})*},
722            {}
723        }
724    }};
725    ($($invalid:tt)*) => {
726        compile_error!(concat!("failed to convert `", stringify!($($invalid)*), "`"))
727    };
728}
729
730#[macro_export]
731macro_rules! call_static_method {
732    (
733        {{$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}}},
734        {$($class:ident).+},
735        {$env:expr} $(, $method_args:expr)*
736    ) => {{
737        $crate::call_static_method! {
738            $env,
739            $crate::gen_class_name!($($class).+),
740            stringify! {$func_name},
741            {{$($ret)*}, {$($args)*}}
742            $(, $method_args)*
743        }
744    }};
745    ($env:expr, {$($class:ident).+}, {$($method:tt)*} $(, $args:expr)*) => {{
746        $crate::split_extract_plain_native_methods! {
747            {$($method)*;},
748            $crate::call_static_method,
749            {$($class).+},
750            {$env} $(, $args)*
751        }
752    }};
753    (
754        $env:expr,
755        $class_name:expr,
756        $func_name:expr,
757        {{$($ret:tt)*}, {$($args:tt)*}}
758        $(, $method_args:expr)*
759    ) => {{
760        $env.call_static_method(
761            $class_name,
762            $func_name,
763            $crate::gen_jni_sig! { {$($ret)+}, {$($args)*}},
764            &{
765                $crate::convert_args_list! {
766                    {$($args)*},
767                    {$($method_args),*}
768                }
769            },
770        ).map(|jvalue| {
771            $crate::cast_jvalue! {
772                {$($ret)*},
773                jvalue
774            }
775        })
776    }};
777}
778
779#[macro_export]
780macro_rules! call_method {
781    (
782        {{$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}}},
783        $env:expr, $obj:expr $(, $method_args:expr)*
784    ) => {{
785        $env.call_method(
786            $obj,
787            stringify!{$func_name},
788            $crate::gen_jni_sig! { {$($ret)+}, {$($args)*}},
789            &{
790                $crate::convert_args_list! {
791                    {$($args)*},
792                    {$($method_args),*}
793                }
794            },
795        ).map(|jvalue| {
796            $crate::cast_jvalue! {
797                {$($ret)*},
798                jvalue
799            }
800        })
801    }};
802    ($env:expr, $obj:expr, {$($method:tt)*} $(, $args:expr)*) => {{
803        $crate::split_extract_plain_native_methods! {
804            {$($method)*;},
805            $crate::call_method,
806            $env, $obj $(, $args)*
807        }
808    }};
809}
810
811#[macro_export]
812macro_rules! gen_native_method_entry {
813    (
814        $class_prefix:ident, $func_name:ident, {$($ret:tt)+}, {$($args:tt)*}
815    ) => {{
816        {
817            let fn_ptr = $crate::paste! {[<$class_prefix $func_name> ]} as *mut c_void;
818            let sig = $crate::gen_jni_sig! { {$($ret)+}, {$($args)*}};
819            jni::NativeMethod {
820                name: jni::strings::JNIString::from(stringify! {$func_name}),
821                sig: jni::strings::JNIString::from(sig),
822                fn_ptr,
823            }
824        }
825    }};
826}
827
828#[cfg(test)]
829mod tests {
830    use std::fmt::Formatter;
831
832    #[test]
833    fn test_for_all_gen() {
834        macro_rules! gen_array {
835            (test) => {{
836                for_all_native_methods! {
837                    {
838                        public static native int defaultVnodeCount();
839                        static native long hummockIteratorNew(byte[] readPlan);
840                        public static native byte[] rowGetKey(long pointer);
841                    },
842                    gen_array
843                }
844            }};
845            (all) => {{
846                for_all_native_methods! {
847                    gen_array
848                }
849            }};
850            ({$({ $func_name:ident, {$($ret:tt)+}, {$($args:tt)*} })*}) => {{
851                [
852                    $(
853                        (stringify! {$func_name}, gen_jni_sig! { {$($ret)+}, {$($args)*}}),
854                    )*
855                ]
856            }};
857        }
858        let sig: [(_, _); 3] = gen_array!(test);
859        assert_eq!(
860            sig,
861            [
862                ("defaultVnodeCount", "()I"),
863                ("hummockIteratorNew", "([B)J"),
864                ("rowGetKey", "(J)[B")
865            ]
866        );
867
868        let sig = gen_array!(all);
869        assert!(!sig.is_empty());
870    }
871
872    #[test]
873    fn test_all_native_methods() {
874        // This test shows the signature of all native methods
875        let expected = expect_test::expect![[r#"
876            [
877                tracingSlf4jEvent                        (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V,
878                tracingSlf4jEventEnabled                 (I)Z,
879                defaultVnodeCount                        ()I,
880                iteratorNewStreamChunk                   (J)J,
881                iteratorNext                             (J)Z,
882                initObjectStoreForTest                   (Ljava/lang/String;Ljava/lang/String;)V,
883                putObject                                (Ljava/lang/String;[B)V,
884                getObjectStoreType                       ()Ljava/lang/String;,
885                deleteObjects                            (Ljava/lang/String;)V,
886                getObject                                (Ljava/lang/String;)[B,
887                listObject                               (Ljava/lang/String;)[Ljava/lang/String;,
888                iteratorClose                            (J)V,
889                newStreamChunkFromPayload                ([B)J,
890                newStreamChunkFromPretty                 (Ljava/lang/String;)J,
891                streamChunkClose                         (J)V,
892                iteratorGetKey                           (J)[B,
893                iteratorGetOp                            (J)I,
894                iteratorIsNull                           (JI)Z,
895                iteratorGetInt16Value                    (JI)S,
896                iteratorGetInt32Value                    (JI)I,
897                iteratorGetInt64Value                    (JI)J,
898                iteratorGetFloatValue                    (JI)F,
899                iteratorGetDoubleValue                   (JI)D,
900                iteratorGetBooleanValue                  (JI)Z,
901                iteratorGetStringValue                   (JI)Ljava/lang/String;,
902                iteratorGetTimestampValue                (JI)Ljava/time/LocalDateTime;,
903                iteratorGetTimestamptzValue              (JI)Ljava/time/OffsetDateTime;,
904                iteratorGetDecimalValue                  (JI)Ljava/math/BigDecimal;,
905                iteratorGetTimeValue                     (JI)Ljava/time/LocalTime;,
906                iteratorGetDateValue                     (JI)Ljava/time/LocalDate;,
907                iteratorGetIntervalValue                 (JI)Ljava/lang/String;,
908                iteratorGetJsonbValue                    (JI)Ljava/lang/String;,
909                iteratorGetByteaValue                    (JI)[B,
910                iteratorGetArrayValue                    (JILjava/lang/Class;)Ljava/lang/Object;,
911                sendCdcSourceMsgToChannel                (J[B)Z,
912                sendCdcSourceErrorToChannel              (JLjava/lang/String;)Z,
913                cdcSourceSenderClose                     (J)V,
914                recvSinkWriterRequestFromChannel         (J)Lcom/risingwave/java/binding/JniSinkWriterStreamRequest;,
915                sendSinkWriterResponseToChannel          (J[B)Z,
916                sendSinkWriterErrorToChannel             (JLjava/lang/String;)Z,
917                recvSinkCoordinatorRequestFromChannel    (J)[B,
918                sendSinkCoordinatorResponseToChannel     (J[B)Z,
919            ]
920        "#]];
921
922        struct MethodInfo {
923            name: &'static str,
924            sig: &'static str,
925        }
926
927        impl std::fmt::Debug for MethodInfo {
928            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
929                write!(f, "{:40} {}", self.name, self.sig)
930            }
931        }
932
933        macro_rules! gen_all_native_method_info {
934            () => {{
935                $crate::for_all_native_methods! {
936                    gen_all_native_method_info
937                }
938            }};
939            ({$({$func_name:ident, {$($ret:tt)*}, {$($args:tt)*}})*}) => {
940                [$(
941                    (
942                        MethodInfo {
943                            name: stringify! {$func_name},
944                            sig: $crate::gen_jni_sig! {
945                                {$($ret)*}, {$($args)*}
946                            },
947                        }
948                    )
949                ),*]
950            }
951        }
952        let info = gen_all_native_method_info!();
953        expected.assert_debug_eq(&info);
954    }
955}