auto_jni/
call.rs

1#[macro_export]
2macro_rules! call_static {
3    ($path:tt, $method:tt, $sig:tt, $args:expr, $ret:expr) => {
4        {
5        use auto_jni::once_cell::sync::OnceCell;
6        use auto_jni::jni::objects::{JClass, JStaticMethodID};
7        use crate::java;
8        static FNPTR: OnceCell<JStaticMethodID> = OnceCell::new();
9        static CLASS: OnceCell<JClass> = OnceCell::new();
10        let mut java = java();
11        let fnptr = FNPTR.get_or_init(|| {
12            java.get_static_method_id($path, $method, $sig).unwrap()
13        });
14        let class = CLASS.get_or_init(|| {
15            java.find_class($path).unwrap()
16        });
17
18        unsafe {
19            java.call_static_method_unchecked(class, fnptr, $ret, $args).unwrap()
20        }
21        }
22    };
23}
24
25
26#[macro_export]
27macro_rules! call {
28    ($obj:expr, $path:tt, $method:tt, $sig:tt, $args:expr, $ret:expr) => {
29        {
30        use once_cell::sync::OnceCell;
31        use jni::objects::{JClass, JMethodID};
32        use crate::java;
33        static FNPTR: OnceCell<JMethodID> = OnceCell::new();
34        let mut java = java();
35        let fnptr = FNPTR.get_or_init(|| {
36            let class = java.find_class($path).unwrap();
37            java.get_method_id(class, $method, $sig).unwrap()
38        });
39
40        unsafe {
41            java.call_method_unchecked($obj, fnptr, $ret, $args).unwrap()
42        }
43        }
44    };
45}
46
47// this one only offers a performance benefit if you construct in a loop,
48// the intent is just to homogenize the api
49#[macro_export]
50macro_rules! create {
51    ($path:tt, $sig:tt, $args:expr) => {
52        {
53        use once_cell::sync::OnceCell;
54        use jni::objects::{JClass, JMethodID};
55        use crate::java;
56        static FNPTR: OnceCell<JMethodID> = OnceCell::new();
57        static CLASS: OnceCell<JClass> = OnceCell::new();
58        let mut java = java();
59        let class = CLASS.get_or_init(|| {
60            java.find_class($path).unwrap()
61        });
62        let fnptr = FNPTR.get_or_init(|| {
63            java.get_method_id(class, "<init>", $sig).unwrap()
64        });
65
66        let obj = unsafe {
67            java.new_object_unchecked(class, *fnptr, $args).unwrap()
68        };
69        java.new_global_ref(obj).unwrap()
70        }
71    };
72}
73
74#[macro_export]
75macro_rules! once {
76    ($code:expr) => {
77        {
78            static ONCE: OnceCell<JObject> = OnceCell::new();
79
80            ONCE.get_or_init(|| {$code})
81        }
82
83    };
84}
85
86use std::collections::HashMap;
87use std::io::Write;
88use std::fs::File;
89use std::path::Path;
90use jni::signature::{Primitive, ReturnType};
91use crate::parse_javap_output;
92
93pub fn generate_bindings_file(class_name: Vec<&str>, class_path: Option<String>, output_path: &Path, jvm_options: Option<Vec<String>>) -> std::io::Result<()> {
94    let mut file = File::create(output_path)?;
95
96    // Write header imports
97    writeln!(file, "use auto_jni::jni::objects::{{JObject, GlobalRef}};")?;
98    writeln!(file, "use auto_jni::errors::JNIError;")?;
99    writeln!(file, "use auto_jni::{{call, call_static, create}};")?;
100    writeln!(file, "use auto_jni::jni::objects::JValue;")?;
101    writeln!(file, "use auto_jni::jni::signature::{{Primitive, ReturnType}};")?;
102    writeln!(file, "use auto_jni::jni;")?;
103    writeln!(file, "use auto_jni::once_cell;")?;
104    writeln!(file, "use auto_jni::lazy_static::lazy_static;")?;
105    writeln!(file, "use auto_jni::jni::{{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM}};")?;
106    writeln!(file, "use auto_jni::jni::objects::JObjectArray;")?;
107    writeln!(file)?;
108
109    // Create java functions
110    writeln!(file, "lazy_static! {{ static ref JAVA: JavaVM = create_jvm(); }}")?;
111    writeln!(file)?;
112    writeln!(file, "fn create_jvm() -> JavaVM {{")?;
113    writeln!(file, "    let jvm_args = InitArgsBuilder::new()")?;
114    writeln!(file, "        .version(JNIVersion::V8)")?;
115    if let Some(jvm_options) = jvm_options {
116        for option in jvm_options {
117            writeln!(file, "        .option(\"{}\")", option.replace("\\", "\\\\"))?;
118        }
119    }
120
121    writeln!(file, "        .build().unwrap();")?;
122    writeln!(file, "    JavaVM::new(jvm_args).unwrap()")?;
123    writeln!(file, "}}")?;
124    writeln!(file)?;
125
126    writeln!(file, "pub fn java() -> JNIEnv<'static> {{")?;
127    writeln!(file, "    JAVA.attach_current_thread_permanently().unwrap()")?;
128    writeln!(file, "}}")?;
129    writeln!(file)?;
130
131    // Extract struct name from class_name (last part after dot)
132    // let struct_name = class_name.split('.').last().unwrap_or(class_name);
133    for class in class_name {
134        let bindings = parse_javap_output(class, class_path.clone());
135        let struct_name = class.replace('.', "_");
136
137        // Write struct definition
138        writeln!(file, "#[allow(non_snake_case)]")?;
139        writeln!(file, "#[allow(non_camel_case_types)]")?;
140        writeln!(file, "pub struct {} {{", struct_name)?;
141        writeln!(file, "    inner: GlobalRef,")?;
142        writeln!(file, "}}")?;
143        writeln!(file)?;
144
145        // Write implementation
146        writeln!(file, "#[allow(non_snake_case)]")?;
147        writeln!(file, "#[allow(non_camel_case_types)]")?;
148        writeln!(file, "impl<'a> {} {{", struct_name)?;
149
150        println!("Length: {}", bindings.len());
151
152        let mut methods: HashMap<String, i32> = HashMap::new();
153        let mut enums: Vec<String> = Vec::new();
154
155        // Generate methods for each binding
156        for mut binding in bindings {
157            println!("Creating binding for: {}", binding.name);
158
159            let mut enums_to_add: Vec<String> = binding.args.iter()
160                .filter(|arg| arg.contains('$'))
161                .map(|arg| arg.to_string())
162                .collect::<Vec<String>>();
163
164            for mut enum_name in enums_to_add {
165                enum_name.remove(0);
166                if !enums.iter().any(|e| e == &enum_name) {
167                    enums.push(enum_name.clone());
168                    writeln!(file, "    #[allow(non_snake_case)]")?;
169                    writeln!(file, "    pub fn {}_from_str(s: &str) -> JObject {{", enum_name.replace("/", "_").replace("$", "_"))?;
170                    writeln!(file, "        call_static!(")?;
171                    writeln!(file, "            \"{}\",", enum_name)?;
172                    writeln!(file, "            \"valueOf\",")?;
173                    writeln!(file, "            \"(Ljava/lang/String;)L{};\",", enum_name)?;
174                    writeln!(file, "            &[JValue::Object(&java().new_string(s).unwrap()).as_jni()],")?;
175                    writeln!(file, "            ReturnType::Object")?;
176                    writeln!(file, "        ).l().unwrap()")?;
177                    writeln!(file, "    }}")?;
178                }
179            }
180
181            // Filter names to remove $ from lambda (lambda$takeSnapshot$1)
182            if binding.name.contains('$') {
183                let mut split = binding.name.split('$');
184                split.next();
185                binding.name = split.next().unwrap().to_string();
186            }
187
188            // Convert Java types to Rust types for arguments
189            let args: Vec<(String, String)> = binding.args.iter().enumerate()
190                .map(|(i, arg_type)| {
191                    (format!("arg_{}", i), arg_type.to_string())
192                })
193                .collect();
194
195            // Convert return type
196            let return_type = match binding.return_type.as_str() {
197                "I" => "i32",
198                "J" => "i64",
199                "D" => "f64",
200                "F" => "f32",
201                "Z" => "bool",
202                "B" => "i8",
203                "C" => "u16",
204                "S" => "i16",
205                "V" => "()",
206                _ => "JObject<'static>"
207            };
208
209            let mut method_name = if binding.name.to_ascii_lowercase() == "x" {
210                "new".to_string()
211            } else {
212                binding.name.clone()
213            };
214
215            if methods.contains_key(&method_name) {
216                methods.insert(method_name.clone(), methods.get(&method_name.clone()).unwrap() + 1);
217                method_name.push_str(format!("_{}", &methods.get(&method_name.clone()).unwrap().to_string()).as_str());
218            } else {
219                methods.insert(method_name.clone(), 1);
220            }
221
222            // Write method signature
223            writeln!(file, "    #[allow(non_snake_case)]")?;
224            write!(file, "    pub fn {}(", method_name)?;
225
226            // Write method body
227            if binding.is_constructor {
228                // Write arguments
229                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
230                    write!(file, "{}: {}", arg_name, java_type_to_rust(arg_type))?;
231                    if i < args.len() - 1 {
232                        write!(file, ", ")?;
233                    }
234                }
235                writeln!(file, ") -> Result<Self, JNIError> {{")?;
236
237                writeln!(file, "        Ok(Self {{")?;
238                write!(file, "            inner: create!(\"{}\", \"{}\", &[",
239                       binding.path,
240                       binding.signature)?;
241                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
242                    write!(file, "{}", get_input_type(arg_name, arg_type))?;
243                    if i < args.len() - 1 {
244                        write!(file, ", ")?;
245                    }
246                }
247                writeln!(file, "])")?;
248                writeln!(file, "        }})")?;
249            } else if binding.is_static {
250                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
251                    write!(file, "{}: {}", arg_name, java_type_to_rust(arg_type))?;
252                    if i < args.len() - 1 {
253                        write!(file, ", ")?;
254                    }
255                }
256                writeln!(file, ") -> Result<{}, JNIError> {{", return_type)?;
257                let return_type = get_return_type(&*binding.return_type);
258                writeln!(file, "        {} call_static!(", if return_type == ReturnType::Primitive(Primitive::Void) {
259                    "".to_string()
260                } else {
261                    "let result =".to_string()
262                })?;
263                writeln!(file, "            \"{}\",", binding.path)?;
264                writeln!(file, "            \"{}\",", binding.name)?;
265                writeln!(file, "            \"{}\",", binding.signature)?;
266                write!(file, "            &[")?;
267                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
268                    write!(file, "{}", get_input_type(arg_name, arg_type))?;
269                    if i < args.len() - 1 {
270                        write!(file, ", ")?;
271                    }
272                }
273                writeln!(file, "],")?;
274                writeln!(file, "            {}", convert_return_type_to_string(return_type.clone()))?;
275                writeln!(file, "        );")?;
276                writeln!(file, "        Ok({})", return_type_to_function(return_type.clone()))?;
277            } else {
278                write!(file, "instance: &'a GlobalRef, ")?;
279                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
280                    write!(file, "{}: {}", arg_name, java_type_to_rust(arg_type))?;
281                    if i < args.len() - 1 {
282                        write!(file, ", ")?;
283                    }
284                }
285                writeln!(file, ") -> Result<{}, JNIError> {{", return_type)?;
286                let return_type = get_return_type(&*binding.return_type);
287                writeln!(file, "        {} call!(", if return_type == ReturnType::Primitive(Primitive::Void) {
288                    "".to_string()
289                } else {
290                    "let result =".to_string()
291                })?;
292                writeln!(file, "            instance.as_obj(),")?;
293                writeln!(file, "            \"{}\",", binding.path)?;
294                writeln!(file, "            \"{}\",", binding.name)?;
295                writeln!(file, "            \"{}\",", binding.signature)?;
296                write!(file, "            &[")?;
297                for (i, (arg_name, arg_type)) in args.iter().enumerate() {
298                    write!(file, "{}", get_input_type(arg_name, arg_type))?;
299                    if i < args.len() - 1 {
300                        write!(file, ", ")?;
301                    }
302                }
303                let return_type = get_return_type(&*binding.return_type);
304                writeln!(file, "],")?;
305                writeln!(file, "            {}", convert_return_type_to_string(return_type.clone()))?;
306                writeln!(file, "        );")?;
307                writeln!(file, "        Ok({})", return_type_to_function(return_type.clone()))?;
308            }
309            writeln!(file, "    }}")?;
310        }
311
312        // Expose inner
313        writeln!(file, "    pub fn inner(&self) -> GlobalRef {{")?;
314        writeln!(file, "        self.inner.clone()")?;
315        writeln!(file, "    }}")?;
316
317        writeln!(file, "}}")?;
318        writeln!(file)?;
319    }
320
321    Ok(())
322}
323
324/// Convert java type to rust type
325fn java_type_to_rust(java_type: &str) -> &str {
326    match java_type {
327        "I" => "i32",
328        "J" => "i64",
329        "D" => "f64",
330        "F" => "f32",
331        "Z" => "bool",
332        "B" => "i8",
333        "C" => "u16",
334        "S" => "i16",
335        "V" => "()",
336        t if t.starts_with("L") => "&JObject",
337        t if t.starts_with("[") => "&JObjectArray",
338        _ => "JObject"
339    }
340}
341
342/// Convert return type to function to get type
343/// ex. ReturnType::Primitive(Primitive::Int) => ".i().unwrap()"
344fn return_type_to_function(return_type: ReturnType) -> String {
345    match return_type {
346        ReturnType::Primitive(Primitive::Int) => "result.i().unwrap()".to_string(),
347        ReturnType::Primitive(Primitive::Long) => "result.j().unwrap()".to_string(),
348        ReturnType::Primitive(Primitive::Double) => "result.d().unwrap()".to_string(),
349        ReturnType::Primitive(Primitive::Float) => "result.f().unwrap()".to_string(),
350        ReturnType::Primitive(Primitive::Boolean) => "result.z().unwrap()".to_string(),
351        ReturnType::Primitive(Primitive::Byte) => "result.b().unwrap()".to_string(),
352        ReturnType::Primitive(Primitive::Char) => "result.c().unwrap()".to_string(),
353        ReturnType::Primitive(Primitive::Short) => "result.s().unwrap()".to_string(),
354        ReturnType::Primitive(Primitive::Void) => "()".to_string(),
355        ReturnType::Object => "result.l().unwrap()".to_string(),
356        _ => "".to_string()
357    }
358}
359
360/// Get the input types from a string
361fn get_input_type(arg_name: &str, arg_type: &str) -> String {
362    match arg_type {
363        "I" => format!("JValue::Int({}).as_jni()", arg_name),
364        "J" => format!("JValue::Long({}).as_jni()", arg_name),
365        "D" => format!("JValue::Double({}).as_jni()", arg_name),
366        "F" => format!("JValue::Float({}).as_jni()", arg_name),
367        "Z" => format!("JValue::Bool({} as u8).as_jni()", arg_name),
368        "B" => format!("JValue::Byte({}).as_jni()", arg_name),
369        "C" => format!("JValue::Char({}).as_jni()", arg_name),
370        "S" => format!("JValue::Short({}).as_jni()", arg_name),
371        t => format!("JValue::Object({}).as_jni()", arg_name),
372        _ => arg_type.to_string()
373    }
374}
375
376/// Convert string return type to ReturnType enum
377fn get_return_type(return_type: &str) -> ReturnType {
378    match return_type {
379        "I" => ReturnType::Primitive(Primitive::Int),
380        "J" => ReturnType::Primitive(Primitive::Long),
381        "D" => ReturnType::Primitive(Primitive::Double),
382        "F" => ReturnType::Primitive(Primitive::Float),
383        "Z" => ReturnType::Primitive(Primitive::Boolean),
384        "B" => ReturnType::Primitive(Primitive::Byte),
385        "C" => ReturnType::Primitive(Primitive::Char),
386        "S" => ReturnType::Primitive(Primitive::Short),
387        "V" => ReturnType::Primitive(Primitive::Void),
388        t if t.starts_with("L") => ReturnType::Object,
389        t if t.starts_with("[") => ReturnType::Object,
390        _ => ReturnType::Object
391    }
392}
393
394/// Convert ReturnType to string to be added to file
395fn convert_return_type_to_string(return_type: ReturnType) -> String {
396    match return_type {
397        ReturnType::Primitive(Primitive::Int) => "ReturnType::Primitive(Primitive::Int)".to_string(),
398        ReturnType::Primitive(Primitive::Long) => "ReturnType::Primitive(Primitive::Long)".to_string(),
399        ReturnType::Primitive(Primitive::Double) => "ReturnType::Primitive(Primitive::Double)".to_string(),
400        ReturnType::Primitive(Primitive::Float) => "ReturnType::Primitive(Primitive::Float)".to_string(),
401        ReturnType::Primitive(Primitive::Boolean) => "ReturnType::Primitive(Primitive::Boolean)".to_string(),
402        ReturnType::Primitive(Primitive::Byte) => "ReturnType::Primitive(Primitive::Byte)".to_string(),
403        ReturnType::Primitive(Primitive::Char) => "ReturnType::Primitive(Primitive::Char)".to_string(),
404        ReturnType::Primitive(Primitive::Short) => "ReturnType::Primitive(Primitive::Short)".to_string(),
405        ReturnType::Primitive(Primitive::Void) => "ReturnType::Primitive(Primitive::Void)".to_string(),
406        ReturnType::Object => "ReturnType::Object".to_string(),
407        _ => "".to_string()
408    }
409}
410
411pub use {call, create, call_static, once};