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}