auto_jni/
lib.rs

1pub mod call;
2pub mod errors;
3
4pub use jni;
5pub use once_cell;
6pub use lazy_static;
7
8use std::process::Command;
9use regex::Regex;
10
11#[derive(Debug, PartialEq)]
12struct MethodBinding {
13    path: String,
14    name: String,
15    signature: String,
16    args: Vec<String>,
17    return_type: String,
18    is_static: bool,
19    is_constructor: bool,
20}
21
22fn parse_javap_output(class_name: &str, class_path: Option<String>) -> Vec<MethodBinding> {
23    let mut command = Command::new("javap");
24    command.args(["-s", "-p"]);
25
26    if let Some(cp) = class_path {
27        command.arg("-classpath").arg(cp);
28    }
29
30    command.arg(class_name);
31
32    let output = command.output().expect("Failed to execute javap");
33    let output_str = String::from_utf8_lossy(&output.stdout);
34
35    let method_regex = Regex::new(r"(?m)^\s*(?:public|private|protected)?\s*(static\s+native|native\s+static|static|native)?\s*([\w<>.\[\]]*)?\s*([\w$<>]+)\s*\(([^)]*)\)\s*(?:throws\s+[\w.]+)?\s*;").unwrap();
36    let descriptor_regex = Regex::new(r"^\s*descriptor:\s*(.+)$").unwrap();
37
38    let mut bindings = Vec::new();
39    let mut lines = output_str.lines().peekable();
40
41    while let Some(line) = lines.next() {
42        if let Some(captures) = method_regex.captures(line) {
43            let is_static = captures.get(1).is_some();
44            let return_type = captures.get(2).map_or("", |m| m.as_str()).to_string();
45            let name = captures.get(3).map_or("", |m| m.as_str()).to_string();
46            let args_str = captures.get(4).map_or("", |m| m.as_str());
47
48            while let Some(next_line) = lines.peek() {
49                if let Some(desc_captures) = descriptor_regex.captures(next_line) {
50                    let signature = desc_captures.get(1).map_or("", |m| m.as_str()).to_string();
51                    let args = parse_descriptor_args(&signature);
52                    let return_type = parse_descriptor_return(&signature);
53
54                    bindings.push(MethodBinding {
55                        path: class_name.replace('.', "/"),
56                        name: name.clone(),
57                        signature,
58                        args,
59                        return_type,
60                        is_static,
61                        is_constructor: name.to_ascii_lowercase() == "x",
62                    });
63                    break;
64                }
65                lines.next();
66            }
67        }
68    }
69
70    bindings
71}
72
73fn parse_descriptor_args(descriptor: &str) -> Vec<String> {
74    let args_section = descriptor
75        .trim_start_matches('(')
76        .split(')')
77        .next()
78        .unwrap_or("");
79
80    let mut args = Vec::new();
81    let mut chars = args_section.chars().peekable();
82
83    while let Some(c) = chars.next() {
84        match c {
85            'L' => {
86                let mut class_name = String::new();
87                while let Some(nc) = chars.next() {
88                    if nc == ';' { break; }
89                    class_name.push(nc);
90                }
91
92                args.push(format!("L{}", class_name));
93            },
94            'I' | 'J' | 'D' | 'F' | 'B' | 'C' | 'S' | 'Z' => args.push(c.to_string()),
95            '[' => {
96                let mut array_type = String::from("[");
97                if let Some(next_char) = chars.next() {
98                    array_type.push(next_char);
99                    if next_char == 'L' {
100                        while let Some(nc) = chars.next() {
101                            array_type.push(nc);
102                            if nc == ';' { break; }
103                        }
104                    }
105                }
106                args.push(array_type);
107            },
108            _ => continue,
109        }
110    }
111
112    args
113}
114
115fn parse_descriptor_return(descriptor: &str) -> String {
116    descriptor.split(')').nth(1).unwrap_or("").to_string()
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_parse_javap() {
125        let class_name = "com.example.EnumTest";
126        let class_path = Some("examples/create_enum/classes".to_string());
127        let bindings = parse_javap_output(class_name, class_path);
128
129        assert!(!bindings.is_empty(), "No bindings were parsed");
130
131        let add_method = bindings.iter().find(|b| b.name == "check")
132            .expect("Could not find check method");
133
134        assert_eq!(add_method.path, "com/example/EnumTest");
135        assert_eq!(add_method.name, "check");
136        assert_eq!(add_method.signature, "(II)I");
137        assert_eq!(add_method.args, vec!["I", "I"]);
138        assert_eq!(add_method.return_type, "I");
139    }
140
141    #[test]
142    fn test_parse_constructor() {
143        let class_name = "com.ctre.phoenix6.hardware.TalonFX";
144        let class_path = Some("Z:\\frcrs\\unwrapped".to_string());
145        let bindings = parse_javap_output(class_name, class_path);
146
147        assert!(!bindings.is_empty(), "No bindings were parsed");
148
149        let add_method = bindings.iter().find(|b| b.name == "X")
150            .expect("Could not find new method");
151    }
152
153    #[test]
154    fn test_parse_descriptor() {
155        assert_eq!(
156            parse_descriptor_args("(II)I"),
157            vec!["I", "I"]
158        );
159        assert_eq!(
160            parse_descriptor_args("(ILjava/lang/String;[I)V"),
161            vec!["I", "java/lang/String", "[I"]
162        );
163        assert_eq!(
164            parse_descriptor_return("(II)I"),
165            "I"
166        );
167        assert_eq!(
168            parse_descriptor_args("(Lcom/example/EnumTest$CountEnum;)I"),
169            vec!["Lcom/example/EnumTest$CountEnum;"]
170        );
171        assert_eq!(
172            parse_descriptor_return("(Lcom/example/EnumTest$CountEnum;)I"),
173            "I"
174        )
175    }
176}