use crate::Error::InvalidInput;
use jdescriptor::{DescriptorError, MethodDescriptor};
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidInput(String),
}
pub fn c_name(class: &str, method: &str, descriptor: &str) -> Result<(String, String), Error> {
if class.is_empty() {
return Err(InvalidInput("Class name is empty".to_string()));
}
if method.is_empty() {
return Err(InvalidInput("Method name is empty".to_string()));
}
let parsed: MethodDescriptor = descriptor
.parse()
.map_err(|e: DescriptorError| InvalidInput(e.to_string()))?;
let params = parsed
.parameter_types()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("");
let class_part = encode_jni(class);
let method_part = encode_jni(method);
let params_part = encode_jni(¶ms);
let short_name = format!("Java_{}_{}", class_part, method_part);
let long_name = format!("Java_{}_{}__{}", class_part, method_part, params_part);
Ok((short_name, long_name))
}
fn encode_jni(s: &str) -> String {
const SLASH: u16 = b'/' as u16;
const UNDERSCORE: u16 = b'_' as u16;
const SEMICOLON: u16 = b';' as u16;
const LBRACKET: u16 = b'[' as u16;
const A: u16 = b'A' as u16;
const Z: u16 = b'Z' as u16;
const AA: u16 = b'a' as u16;
const ZZ: u16 = b'z' as u16;
const DIGIT_0: u16 = b'0' as u16;
const DIGIT_9: u16 = b'9' as u16;
let mut out = String::with_capacity(s.len() * 2);
for unit in s.encode_utf16() {
match unit {
SLASH => out.push('_'),
UNDERSCORE => out.push_str("_1"),
SEMICOLON => out.push_str("_2"),
LBRACKET => out.push_str("_3"),
A..=Z | AA..=ZZ | DIGIT_0..=DIGIT_9 => {
out.push(unit as u8 as char);
}
_ => {
use std::fmt::Write;
let _ = write!(out, "_0{:04x}", unit as u32);
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_primitives() {
assert_eq!(
c_name("Test", "foo", "()V"),
ok("Java_Test_foo", "Java_Test_foo__")
);
assert_eq!(
c_name("Test", "foo", "(I)V"),
ok("Java_Test_foo", "Java_Test_foo__I")
);
assert_eq!(
c_name("Test", "foo", "(IJ)V"),
ok("Java_Test_foo", "Java_Test_foo__IJ")
);
assert_eq!(
c_name("Test", "foo", "(ZBCSFD)V"),
ok("Java_Test_foo", "Java_Test_foo__ZBCSFD")
);
}
#[test]
fn test_object_types() {
assert_eq!(
c_name("Test", "bar", "(Ljava/lang/String;)V"),
ok("Java_Test_bar", "Java_Test_bar__Ljava_lang_String_2")
);
assert_eq!(
c_name(
"com/example/Test",
"baz",
"(Ljava/lang/Object;Ljava/lang/String;)V"
),
ok(
"Java_com_example_Test_baz",
"Java_com_example_Test_baz__Ljava_lang_Object_2Ljava_lang_String_2"
)
);
}
#[test]
fn test_arrays() {
assert_eq!(
c_name("Test", "foo", "([I)V"),
ok("Java_Test_foo", "Java_Test_foo___3I")
);
assert_eq!(
c_name("Test", "foo", "([[I)V"),
ok("Java_Test_foo", "Java_Test_foo___3_3I")
);
assert_eq!(
c_name("Test", "foo", "([Ljava/lang/String;)V"),
ok("Java_Test_foo", "Java_Test_foo___3Ljava_lang_String_2")
);
assert_eq!(
c_name("Test", "foo", "([[Ljava/lang/Object;)V"),
ok("Java_Test_foo", "Java_Test_foo___3_3Ljava_lang_Object_2")
);
}
#[test]
fn test_tricky_characters() {
assert_eq!(
c_name("my/package_name/Class_", "foo", "(Ljava/lang/Object;[I)V"),
ok(
"Java_my_package_1name_Class_1_foo",
"Java_my_package_1name_Class_1_foo__Ljava_lang_Object_2_3I"
)
);
assert_eq!(
c_name(
"my/package/with_underscore_",
"bar",
"([[Ljava/lang/String;)V"
),
ok(
"Java_my_package_with_1underscore_1_bar",
"Java_my_package_with_1underscore_1_bar___3_3Ljava_lang_String_2"
)
);
}
#[test]
fn test_long_mixed_signature() {
assert_eq!(
c_name(
"Test",
"foo",
"(I[Ljava/lang/String;[[D[[[Ljava/lang/Object;)V"
),
ok(
"Java_Test_foo",
"Java_Test_foo__I_3Ljava_lang_String_2_3_3D_3_3_3Ljava_lang_Object_2"
)
);
}
#[test]
fn test_nested_edge_cases() {
assert_eq!(
c_name("a/b_c/d_", "m_1", "([[Ljava/lang/String;[I[[[J)V"),
ok(
"Java_a_b_1c_d_1_m_11",
"Java_a_b_1c_d_1_m_11___3_3Ljava_lang_String_2_3I_3_3_3J"
)
);
}
#[test]
fn special_characters_in_names() {
assert_eq!(
c_name("com/example/Outer$Inner", "method", "(I)V"),
ok(
"Java_com_example_Outer_00024Inner_method",
"Java_com_example_Outer_00024Inner_method__I"
)
);
assert_eq!(
c_name("Test", "method€", "()V"),
ok("Java_Test_method_020ac", "Java_Test_method_020ac__")
);
}
#[test]
fn invalid_inputs() {
assert_eq!(c_name("", "foo", "()V"), err("Class name is empty"));
assert_eq!(c_name("Test", "", "()V"), err("Method name is empty"));
assert_eq!(
c_name("Test", "foo", ""),
err("Invalid descriptor: Method descriptor must start with '('.")
);
assert_eq!(
c_name("Test", "foo", "(III"),
err("Unexpected end of input.")
);
assert_eq!(
c_name("Test", "foo", "(IM)V"),
err("Invalid descriptor: Unrecognized type descriptor.")
);
}
fn ok(a: &str, b: &str) -> Result<(String, String), Error> {
Ok((a.to_string(), b.to_string()))
}
fn err(msg: &str) -> Result<(String, String), Error> {
Err(InvalidInput(msg.to_string()))
}
}