diff --git a/.github/workflows/rust.yml b/.github/workflows/rust_nightly.yml similarity index 60% rename from .github/workflows/rust.yml rename to .github/workflows/rust_nightly.yml index fa6081d..2759c43 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust_nightly.yml @@ -1,4 +1,4 @@ -name: Rust +name: Rust - Nightly on: push: @@ -11,10 +11,12 @@ env: CARGO_TERM_COLOR: always jobs: - build: + build_and_test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - run: rustup update nightly && rustup default nightly + - run: rustup component add rustfmt - name: Check fmt run: cargo fmt -- --check - name: Test diff --git a/README.md b/README.md index 1f8eb29..2762171 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ The full list of differences are: - You can filter which classes are generated in the TOML config. - Generated code uses relative paths (`super::...`) instead of absolute paths (`crate::...`), so it works if you place it in a submodule not at the crate root. - Generated code is a single `.rs` file, there's no support for spltting it in one file per class. You can still run the output through [form](https://github.com/djmcgill/form), if you want. +- Generated code uses cached method IDs and field IDs stored in `OnceLock` to speed up invocations by several times. Used classes are also stored as JNI global references in order to keep the validity of cached IDs. This may not ensure memory safety when class redefinition features (e.g. `java.lang.instrument` which is unavailable on Android) of the JVM is being used. - Generated code doesn't use macros. - No support for generating Cargo features per class. - Modernized rust, updated dependencies. diff --git a/java-spaghetti-gen/Cargo.toml b/java-spaghetti-gen/Cargo.toml index 2136d68..b41e7f0 100644 --- a/java-spaghetti-gen/Cargo.toml +++ b/java-spaghetti-gen/Cargo.toml @@ -9,13 +9,13 @@ categories = ["development-tools::ffi"] license = "MIT OR Apache-2.0" [dependencies] -jreflection = "0.0.11" -clap = { version = "4", features = ["derive"] } -bitflags = "2.4.2" -serde = "1.0.197" -serde_derive = "1.0.197" -toml = "0.8.10" -zip = "0.6.6" +cafebabe = "0.8.0" +clap = { version = "4", features = ["derive"] } +bitflags = "2.8.0" +serde = "1.0.197" +serde_derive = "1.0.197" +toml = "0.8.10" +zip = "2.2.2" [dev-dependencies] -jni-sys = "0.4.0" +jni-sys = "0.4.0" diff --git a/java-spaghetti-gen/src/emit_rust/context.rs b/java-spaghetti-gen/src/emit_rust/context.rs index cb6d213..4fa793d 100644 --- a/java-spaghetti-gen/src/emit_rust/context.rs +++ b/java-spaghetti-gen/src/emit_rust/context.rs @@ -5,12 +5,10 @@ use std::rc::Rc; use std::sync::Mutex; use std::time::Duration; -use jreflection::class; - use super::modules::Module; use super::preamble::write_preamble; use super::structs::Struct; -use crate::{config, util}; +use crate::{config, parser_util, util}; pub struct Context<'a> { pub(crate) config: &'a config::runtime::Config, @@ -32,10 +30,11 @@ impl<'a> Context<'a> { } pub(crate) fn throwable_rust_path(&self, mod_: &str) -> String { - self.java_to_rust_path(class::Id("java/lang/Throwable"), mod_).unwrap() + self.java_to_rust_path(parser_util::Id("java/lang/Throwable"), mod_) + .unwrap() } - pub fn java_to_rust_path(&self, java_class: class::Id, mod_: &str) -> Result> { + pub fn java_to_rust_path(&self, java_class: parser_util::Id, mod_: &str) -> Result> { let m = Struct::mod_for(self, java_class)?; let s = Struct::name_for(self, java_class)?; let fqn = format!("{}::{}", m, s); @@ -92,18 +91,18 @@ impl<'a> Context<'a> { pat.pop(); } - return false; + false } - pub fn add_struct(&mut self, class: jreflection::Class) -> Result<(), Box> { - if self.config.ignore_classes.contains(class.path.as_str()) { + pub fn add_struct(&mut self, class: parser_util::Class) -> Result<(), Box> { + if self.config.ignore_classes.contains(class.path().as_str()) { return Ok(()); } - if !self.struct_included(class.path.as_str()) { + if !self.struct_included(class.path().as_str()) { return Ok(()); } - let java_path = class.path.as_str().to_string(); + let java_path = class.path().as_str().to_string(); let s = Rc::new(Struct::new(self, class)?); self.all_classes.insert(java_path, s.clone()); diff --git a/java-spaghetti-gen/src/emit_rust/fields.rs b/java-spaghetti-gen/src/emit_rust/fields.rs index 233e147..6ac782f 100644 --- a/java-spaghetti-gen/src/emit_rust/fields.rs +++ b/java-spaghetti-gen/src/emit_rust/fields.rs @@ -1,21 +1,26 @@ +use std::borrow::Cow; +use std::fmt::Write; use std::io; -use jreflection::{class, field}; +use cafebabe::constant_pool::LiteralConstant; +use cafebabe::descriptors::{FieldDescriptor, FieldType}; use super::known_docs_url::KnownDocsUrl; +use super::StrEmitter; use crate::emit_rust::Context; use crate::identifiers::{FieldMangling, IdentifierManglingError}; +use crate::parser_util::{Class, ClassName, FieldSigWriter, IdBuf, IterableId, JavaField}; pub struct Field<'a> { - pub class: &'a jreflection::Class, - pub java: &'a jreflection::Field, + pub class: &'a Class, + pub java: JavaField<'a>, pub rust_names: Result, IdentifierManglingError>, pub ignored: bool, } impl<'a> Field<'a> { - pub fn new(context: &Context, class: &'a jreflection::Class, java: &'a jreflection::Field) -> Self { - let java_class_field = format!("{}\x1f{}", class.path.as_str(), &java.name); + pub fn new(context: &Context, class: &'a Class, java: &'a cafebabe::FieldInfo<'a>) -> Self { + let java_class_field = format!("{}\x1f{}", class.path().as_str(), &java.name); let ignored = context.config.ignore_class_fields.contains(&java_class_field); let renamed_to = context .config @@ -23,13 +28,16 @@ impl<'a> Field<'a> { .get(&java_class_field) .map(|s| s.as_str()); - let result = Self { + Self { class, - java, - rust_names: context.config.codegen.field_naming_style.mangle(java, renamed_to), + java: JavaField::from(java), + rust_names: context + .config + .codegen + .field_naming_style + .mangle(JavaField::from(java), renamed_to), ignored, - }; - result + } } pub fn emit(&self, context: &Context, mod_: &str, out: &mut impl io::Write) -> io::Result<()> { @@ -42,107 +50,31 @@ impl<'a> Field<'a> { emit_reject_reasons.push("[[ignore]]d"); } - let descriptor = self.java.descriptor(); - let rust_set_type_buffer; - let rust_get_type_buffer; - let (rust_set_type, rust_get_type) = match descriptor { - field::Descriptor::Single(field::BasicType::Boolean) => ("bool", "bool"), - field::Descriptor::Single(field::BasicType::Byte) => ("i8", "i8"), - field::Descriptor::Single(field::BasicType::Char) => ("u16", "u16"), - field::Descriptor::Single(field::BasicType::Double) => ("f64", "f64"), - field::Descriptor::Single(field::BasicType::Float) => ("f32", "f32"), - field::Descriptor::Single(field::BasicType::Int) => ("i32", "i32"), - field::Descriptor::Single(field::BasicType::Long) => ("i64", "i64"), - field::Descriptor::Single(field::BasicType::Short) => ("i16", "i16"), - field::Descriptor::Single(field::BasicType::Class(class::Id("java/lang/String"))) - if self.java.is_constant() => - { - ("&'static str", "&'static str") - } - field::Descriptor::Single(field::BasicType::Void) => { - emit_reject_reasons.push("ERROR: void is not a valid field type"); - ("()", "()") - } - field::Descriptor::Single(field::BasicType::Class(class)) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for field type"); - } - if let Ok(fqn) = context.java_to_rust_path(class, mod_) { - rust_set_type_buffer = format!("impl ::java_spaghetti::AsArg<{}>", &fqn); - rust_get_type_buffer = format!("::std::option::Option<::java_spaghetti::Local<'env, {}>>", &fqn); - (rust_set_type_buffer.as_str(), rust_get_type_buffer.as_str()) - } else { - emit_reject_reasons.push("ERROR: Unable to resolve class FQN"); - ("???", "???") - } - } - field::Descriptor::Array { levels, inner } => { - let mut buffer = String::new(); - for _ in 0..(levels - 1) { - buffer.push_str("::java_spaghetti::ObjectArray<"); - } - match inner { - field::BasicType::Boolean => buffer.push_str("::java_spaghetti::BooleanArray"), - field::BasicType::Byte => buffer.push_str("::java_spaghetti::ByteArray"), - field::BasicType::Char => buffer.push_str("::java_spaghetti::CharArray"), - field::BasicType::Short => buffer.push_str("::java_spaghetti::ShortArray"), - field::BasicType::Int => buffer.push_str("::java_spaghetti::IntArray"), - field::BasicType::Long => buffer.push_str("::java_spaghetti::LongArray"), - field::BasicType::Float => buffer.push_str("::java_spaghetti::FloatArray"), - field::BasicType::Double => buffer.push_str("::java_spaghetti::DoubleArray"), - field::BasicType::Class(class) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for field type"); - } + let descriptor = &self.java.descriptor(); + let type_emitter = FieldTypeEmitter(descriptor); - buffer.push_str("::java_spaghetti::ObjectArray<"); - match context.java_to_rust_path(class, mod_) { - Ok(path) => buffer.push_str(path.as_str()), - Err(_) => { - emit_reject_reasons - .push("ERROR: Failed to resolve JNI path to Rust path for argument type"); - buffer.push_str("???"); - } - } - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } - field::BasicType::Void => { - emit_reject_reasons.push("ERROR: Arrays of void isn't a thing"); - buffer.push_str("[()]"); - } - } - for _ in 0..(levels - 1) { - // ObjectArray s - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } + let rust_type = type_emitter + .emit_rust_type(context, mod_, &mut emit_reject_reasons) + .map_err(|_| io::Error::other("std::fmt::Error"))?; - rust_set_type_buffer = format!("impl ::java_spaghetti::AsArg<{}>", &buffer); - rust_get_type_buffer = format!("::std::option::Option<::java_spaghetti::Local<'env, {}>>", &buffer); + let (rust_set_type_buffer, rust_get_type_buffer); + // `rust_set_type` and `rust_get_type` are ones used below + let (rust_set_type, rust_get_type) = match (descriptor.dimensions, &descriptor.field_type) { + (0, t) if !matches!(t, FieldType::Object(_)) => (rust_type.as_ref(), rust_type.as_ref()), + (0, FieldType::Object(cls)) if self.java.is_constant() && ClassName::from(cls).is_string_class() => { + ("&'static str", "&'static str") + } + _ => { + rust_set_type_buffer = format!("impl ::java_spaghetti::AsArg<{}>", &rust_type); + rust_get_type_buffer = format!("::std::option::Option<::java_spaghetti::Local<'env, {}>>", &rust_type); (rust_set_type_buffer.as_str(), rust_get_type_buffer.as_str()) } }; - let field_fragment = match self.java.descriptor() { - // Contents of {get,set}_[static_]..._field - field::Descriptor::Single(field::BasicType::Void) => "void", - field::Descriptor::Single(field::BasicType::Boolean) => "boolean", - field::Descriptor::Single(field::BasicType::Byte) => "byte", - field::Descriptor::Single(field::BasicType::Char) => "char", - field::Descriptor::Single(field::BasicType::Short) => "short", - field::Descriptor::Single(field::BasicType::Int) => "int", - field::Descriptor::Single(field::BasicType::Long) => "long", - field::Descriptor::Single(field::BasicType::Float) => "float", - field::Descriptor::Single(field::BasicType::Double) => "double", - field::Descriptor::Single(field::BasicType::Class(_)) => "object", - field::Descriptor::Array { .. } => "object", - }; + let field_fragment = type_emitter.emit_fragment_type(); if self.rust_names.is_err() { - emit_reject_reasons.push(match self.java.name.as_str() { + emit_reject_reasons.push(match self.java.name() { "$VALUES" => "Failed to mangle field name: enum $VALUES", // Expected s if s.starts_with("this$") => "Failed to mangle field name: this$N outer class pointer", // Expected _ => "ERROR: Failed to mangle field name(s)", @@ -167,7 +99,7 @@ impl<'a> Field<'a> { if self.java.is_volatile() { " volatile" } else { "" } ); - let attributes = (if self.java.deprecated { "#[deprecated] " } else { "" }).to_string(); + let attributes = if self.java.deprecated() { "#[deprecated] " } else { "" }; writeln!(out)?; for reason in &emit_reject_reasons { @@ -175,39 +107,34 @@ impl<'a> Field<'a> { } let env_param = if self.java.is_static() { - "env: ::java_spaghetti::Env<'env>" + "__jni_env: ::java_spaghetti::Env<'env>" } else { "self: &::java_spaghetti::Ref<'env, Self>" }; let url = KnownDocsUrl::from_field( context, - self.class.path.as_str(), - self.java.name.as_str(), - self.java.descriptor(), + self.class.path().as_str(), + self.java.name(), + self.java.descriptor().clone(), ); let url = url.as_ref(); match self.rust_names.as_ref() { Ok(FieldMangling::ConstValue(constant, value)) => { - let value = *value; + let value_writer = ConstantWriter(value); if let Some(url) = url { - writeln!(out, "{}/// {} {}", indent, &keywords, url)?; + writeln!(out, "{indent}/// {keywords} {url}")?; } - match descriptor { - field::Descriptor::Single(field::BasicType::Char) => writeln!( + match descriptor.field_type { + FieldType::Char if descriptor.dimensions == 0 => writeln!( out, - "{}{}pub const {} : {} = {}({});", - indent, &attributes, constant, rust_get_type, rust_get_type, value + "{indent}{attributes}pub const {constant} : u16 = {value_writer}u16;", )?, - field::Descriptor::Single(field::BasicType::Boolean) => writeln!( + FieldType::Boolean if descriptor.dimensions == 0 => writeln!( out, - "{}{}pub const {} : {} = {};", - indent, - &attributes, - constant, - rust_get_type, - if value == &field::Constant::Integer(0) { + "{indent}{attributes}pub const {constant} : {rust_get_type} = {};", + if let LiteralConstant::Integer(0) = value { "false" } else { "true" @@ -215,51 +142,57 @@ impl<'a> Field<'a> { )?, _ => writeln!( out, - "{}{}pub const {} : {} = {};", - indent, &attributes, constant, rust_get_type, value + "{indent}{attributes}pub const {constant} : {rust_get_type} = {value_writer};", )?, } } Ok(FieldMangling::GetSet(get, set)) => { // Getter if let Some(url) = url { - writeln!(out, "{}/// **get** {} {}", indent, &keywords, url)?; + writeln!(out, "{indent}/// **get** {keywords} {url}")?; } else { - writeln!(out, "{}/// **get** {} {}", indent, &keywords, self.java.name.as_str())?; + writeln!(out, "{indent}/// **get** {keywords} {}", self.java.name())?; } writeln!( out, - "{}{}pub fn {}<'env>({}) -> {} {{", - indent, &attributes, get, env_param, rust_get_type + "{indent}{attributes}pub fn {get}<'env>({env_param}) -> {rust_get_type} {{", + )?; + writeln!( + out, + "{indent} static __FIELD: ::std::sync::OnceLock<::java_spaghetti::JFieldID> \ + = ::std::sync::OnceLock::new();" )?; - writeln!(out, "{} unsafe {{", indent)?; + writeln!(out, "{indent} unsafe {{")?; if !self.java.is_static() { - writeln!(out, "{} let env = self.env();", indent)?; + writeln!(out, "{indent} let __jni_env = self.env();")?; } writeln!( out, - "{} let (__jni_class, __jni_field) = env.require_class_{}field({}, {}, {});", - indent, + "{indent} let __jni_class = Self::__class_global_ref(__jni_env);" + )?; + writeln!( + out, + "{indent} \ + let __jni_field = __FIELD.get_or_init(|| \ + ::java_spaghetti::JFieldID::from_raw(__jni_env.require_{}field(__jni_class, {}, {}))\ + ).as_raw();", if self.java.is_static() { "static_" } else { "" }, - emit_cstr(self.class.path.as_str()), - emit_cstr(self.java.name.as_str()), - emit_cstr(self.java.descriptor_str()) + StrEmitter(self.java.name()), + StrEmitter(FieldSigWriter(self.java.descriptor())) )?; if self.java.is_static() { writeln!( out, - "{} env.get_static_{}_field(__jni_class, __jni_field)", - indent, field_fragment + "{indent} __jni_env.get_static_{field_fragment}_field(__jni_class, __jni_field)", )?; } else { writeln!( out, - "{} env.get_{}_field(self.as_raw(), __jni_field)", - indent, field_fragment + "{indent} __jni_env.get_{field_fragment}_field(self.as_raw(), __jni_field)", )?; } - writeln!(out, "{} }}", indent)?; - writeln!(out, "{}}}", indent)?; + writeln!(out, "{indent} }}")?; + writeln!(out, "{indent}}}")?; // Setter if !self.java.is_final() { @@ -271,64 +204,65 @@ impl<'a> Field<'a> { writeln!(out)?; if let Some(url) = url { - writeln!(out, "{}/// **set** {} {}", indent, &keywords, url)?; + writeln!(out, "{indent}/// **set** {keywords} {url}")?; } else { - writeln!(out, "{}/// **set** {} {}", indent, &keywords, self.java.name.as_str())?; + writeln!(out, "{indent}/// **set** {keywords} {}", self.java.name())?; } writeln!( out, - "{}{}pub fn {}<{}>({}, value: {}) {{", - indent, &attributes, set, lifetimes, env_param, rust_set_type + "{indent}{attributes}pub fn {set}<{lifetimes}>({env_param}, value: {rust_set_type}) {{", + )?; + writeln!( + out, + "{indent} static __FIELD: ::std::sync::OnceLock<::java_spaghetti::JFieldID> \ + = ::std::sync::OnceLock::new();" )?; - writeln!(out, "{} unsafe {{", indent)?; + writeln!(out, "{indent} unsafe {{")?; if !self.java.is_static() { - writeln!(out, "{} let env = self.env();", indent)?; + writeln!(out, "{indent} let __jni_env = self.env();")?; } writeln!( out, - "{} let (__jni_class, __jni_field) = env.require_class_{}field({}, {}, {});", - indent, + "{indent} let __jni_class = Self::__class_global_ref(__jni_env);" + )?; + writeln!( + out, + "{indent} \ + let __jni_field = __FIELD.get_or_init(|| \ + ::java_spaghetti::JFieldID::from_raw(__jni_env.require_{}field(__jni_class, {}, {}))\ + ).as_raw();", if self.java.is_static() { "static_" } else { "" }, - emit_cstr(self.class.path.as_str()), - emit_cstr(self.java.name.as_str()), - emit_cstr(self.java.descriptor_str()) + StrEmitter(self.java.name()), + StrEmitter(FieldSigWriter(self.java.descriptor())) )?; if self.java.is_static() { writeln!( out, - "{} env.set_static_{}_field(__jni_class, __jni_field, value)", - indent, field_fragment + "{indent} \ + __jni_env.set_static_{field_fragment}_field(__jni_class, __jni_field, value)", )?; } else { writeln!( out, - "{} env.set_{}_field(self.as_raw(), __jni_field, value)", - indent, field_fragment + "{indent} \ + __jni_env.set_{field_fragment}_field(self.as_raw(), __jni_field, value)", )?; } - writeln!(out, "{} }}", indent)?; - writeln!(out, "{}}}", indent)?; + writeln!(out, "{indent} }}")?; + writeln!(out, "{indent}}}")?; } } Err(_) => { writeln!( out, - "{}{}pub fn get_{:?}<'env>({}) -> {} {{ ... }}", - indent, - &attributes, - self.java.name.as_str(), - env_param, - rust_get_type + "{indent}{attributes}pub fn get_{:?}<'env>({env_param}) -> {rust_get_type} {{ ... }}", + self.java.name(), )?; if !self.java.is_final() { writeln!( out, - "{}{}pub fn set_{:?}<'env>({}) -> {} {{ ... }}", - indent, - &attributes, - self.java.name.as_str(), - env_param, - rust_set_type + "{indent}{attributes}pub fn set_{:?}<'env>({env_param}) -> {rust_set_type} {{ ... }}", + self.java.name(), )?; } } @@ -338,8 +272,135 @@ impl<'a> Field<'a> { } } -fn emit_cstr(s: &str) -> String { - let mut s = format!("{:?}", s); // XXX - s.insert_str(s.len() - 1, "\\0"); - s +struct ConstantWriter<'a>(&'a LiteralConstant<'a>); + +// Migrated from , +// which seems like a bug for `java-spaghetti`. +impl std::fmt::Display for ConstantWriter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + LiteralConstant::Integer(value) => write!(f, "{}", value), + LiteralConstant::Long(value) => write!(f, "{}i64", value), + + LiteralConstant::Float(value) if value.is_infinite() && *value < 0.0 => { + write!(f, "::std::f32::NEG_INFINITY") + } + LiteralConstant::Float(value) if value.is_infinite() => write!(f, "::std::f32::INFINITY"), + LiteralConstant::Float(value) if value.is_nan() => write!(f, "::std::f32::NAN"), + LiteralConstant::Float(value) => write!(f, "{}f32", value), + + LiteralConstant::Double(value) if value.is_infinite() && *value < 0.0 => { + write!(f, "::std::f64::NEG_INFINITY") + } + LiteralConstant::Double(value) if value.is_infinite() => write!(f, "::std::f64::INFINITY"), + LiteralConstant::Double(value) if value.is_nan() => write!(f, "::std::f64::NAN"), + LiteralConstant::Double(value) => write!(f, "{}f64", value), + + LiteralConstant::String(value) => std::fmt::Debug::fmt(value, f), + LiteralConstant::StringBytes(_) => { + write!(f, "panic!(\"Java string constant contains invalid 'Modified UTF8'\")") + } + } + } +} + +pub struct FieldTypeEmitter<'a>(pub &'a FieldDescriptor<'a>); + +impl FieldTypeEmitter<'_> { + /// Generates the corresponding Rust type for the Java field type. + pub fn emit_rust_type( + &self, + context: &Context<'_>, + mod_: &str, + reject_reasons: &mut Vec<&'static str>, + ) -> Result, std::fmt::Error> { + use Cow::Borrowed; + let descriptor = self.0; + let cow = if descriptor.dimensions == 0 { + match &descriptor.field_type { + FieldType::Boolean => Borrowed("bool"), + FieldType::Byte => Borrowed("i8"), + FieldType::Char => Borrowed("u16"), + FieldType::Short => Borrowed("i16"), + FieldType::Integer => Borrowed("i32"), + FieldType::Long => Borrowed("i64"), + FieldType::Float => Borrowed("f32"), + FieldType::Double => Borrowed("f64"), + FieldType::Object(class_name) => { + let class = IdBuf::from(class_name); + if !context.all_classes.contains_key(class.as_str()) { + reject_reasons.push("ERROR: missing class for field/argument type"); + } + if let Ok(path) = context.java_to_rust_path(class.as_id(), mod_) { + path + } else { + reject_reasons.push("ERROR: Failed to resolve JNI path to Rust path for class type"); + format!("{:?}", class) // XXX + } + .into() + } + } + } else { + let mut out = String::new(); + for _ in 0..(descriptor.dimensions - 1) { + write!(out, "::java_spaghetti::ObjectArray<")?; + } + match &descriptor.field_type { + FieldType::Boolean => write!(out, "::java_spaghetti::BooleanArray"), + FieldType::Byte => write!(out, "::java_spaghetti::ByteArray"), + FieldType::Char => write!(out, "::java_spaghetti::CharArray"), + FieldType::Short => write!(out, "::java_spaghetti::ShortArray"), + FieldType::Integer => write!(out, "::java_spaghetti::IntArray"), + FieldType::Long => write!(out, "::java_spaghetti::LongArray"), + FieldType::Float => write!(out, "::java_spaghetti::FloatArray"), + FieldType::Double => write!(out, "::java_spaghetti::DoubleArray"), + FieldType::Object(class_name) => { + let class = IdBuf::from(class_name); + + if !context.all_classes.contains_key(class.as_str()) { + reject_reasons.push("ERROR: missing class for field type"); + } + + write!(out, "::java_spaghetti::ObjectArray<")?; + match context.java_to_rust_path(class.as_id(), mod_) { + Ok(path) => write!(out, "{path}"), + Err(_) => { + reject_reasons.push("ERROR: Failed to resolve JNI path to Rust path for class type"); + write!(out, "???") + } + }?; + write!(out, ", ")?; + write!(out, "{}", &context.throwable_rust_path(mod_))?; + write!(out, ">") + } + }?; + for _ in 0..(descriptor.dimensions - 1) { + // ObjectArray s + write!(out, ", ")?; + write!(out, "{}", &context.throwable_rust_path(mod_))?; + write!(out, ">")?; + } + out.into() + }; + Ok(cow) + } + + /// Contents of {get,set}_[static_]..._field, call_..._method_a. + pub fn emit_fragment_type(&self) -> &'static str { + if self.0.dimensions == 0 { + match self.0.field_type { + FieldType::Boolean => "boolean", + FieldType::Byte => "byte", + FieldType::Char => "char", + FieldType::Short => "short", + FieldType::Integer => "int", + FieldType::Long => "long", + FieldType::Float => "float", + FieldType::Double => "double", + FieldType::Object(_) => "object", + } + } else { + "object" + } + } } diff --git a/java-spaghetti-gen/src/emit_rust/known_docs_url.rs b/java-spaghetti-gen/src/emit_rust/known_docs_url.rs index 521e522..aa7ae5d 100644 --- a/java-spaghetti-gen/src/emit_rust/known_docs_url.rs +++ b/java-spaghetti-gen/src/emit_rust/known_docs_url.rs @@ -1,9 +1,10 @@ use std::fmt::{self, Display, Formatter}; -use jreflection::{field, method}; +use cafebabe::descriptors::{FieldDescriptor, FieldType}; use super::methods::Method; use crate::emit_rust::Context; +use crate::parser_util::{Id, IdBuf}; pub(crate) struct KnownDocsUrl { pub(crate) label: String, @@ -17,7 +18,7 @@ impl Display for KnownDocsUrl { } impl KnownDocsUrl { - pub(crate) fn from_class(context: &Context, java_class: jreflection::class::Id) -> Option { + pub(crate) fn from_class(context: &Context, java_class: Id) -> Option { let java_class = java_class.as_str(); let pattern = context .config @@ -35,7 +36,7 @@ impl KnownDocsUrl { } } - let last_slash = java_class.rfind(|ch| ch == '/'); + let last_slash = java_class.rfind('/'); let no_namespace = if let Some(last_slash) = last_slash { &java_class[(last_slash + 1)..] } else { @@ -56,15 +57,13 @@ impl KnownDocsUrl { } pub(crate) fn from_method(context: &Context, method: &Method) -> Option { - use method::{BasicType, Type}; - let is_constructor = method.java.is_constructor(); let pattern = context .config .doc_patterns .iter() - .find(|pattern| method.class.path.as_str().starts_with(pattern.jni_prefix.as_str()))?; + .find(|pattern| method.class.path().as_str().starts_with(pattern.jni_prefix.as_str()))?; let url_pattern = if is_constructor { pattern .constructor_url_pattern @@ -74,7 +73,7 @@ impl KnownDocsUrl { pattern.method_url_pattern.as_ref()? }; - for ch in method.class.path.as_str().chars() { + for ch in method.class.path().as_str().chars() { match ch { 'a'..='z' => {} 'A'..='Z' => {} @@ -86,14 +85,14 @@ impl KnownDocsUrl { let java_class = method .class - .path + .path() .as_str() .replace('/', pattern.class_namespace_separator.as_str()) .replace('$', pattern.class_inner_class_seperator.as_str()); let java_outer_class = method .class - .path + .path() .as_str() .rsplit('/') .next() @@ -102,7 +101,7 @@ impl KnownDocsUrl { let java_inner_class = method .class - .path + .path() .as_str() .rsplit('/') .next() @@ -114,7 +113,7 @@ impl KnownDocsUrl { let label = if is_constructor { java_inner_class } else { - for ch in method.java.name.as_str().chars() { + for ch in method.java.name().chars() { match ch { 'a'..='z' => {} 'A'..='Z' => {} @@ -123,83 +122,46 @@ impl KnownDocsUrl { _ch => return None, } } - method.java.name.as_str() + method.java.name() }; let mut java_args = String::new(); let mut prev_was_array = false; - for arg in method.java.descriptor().arguments() { + for arg in method.java.descriptor().parameters.iter() { if prev_was_array { prev_was_array = false; - java_args.push_str("%5B%5D"); // [] + java_args.push_str("[]"); } if !java_args.is_empty() { java_args.push_str(&pattern.argument_seperator[..]); } - match arg { - Type::Single(BasicType::Void) => { - java_args.push_str("void"); - } - Type::Single(BasicType::Boolean) => { - java_args.push_str("boolean"); - } - Type::Single(BasicType::Byte) => { - java_args.push_str("byte"); - } - Type::Single(BasicType::Char) => { - java_args.push_str("char"); - } - Type::Single(BasicType::Short) => { - java_args.push_str("short"); - } - Type::Single(BasicType::Int) => { - java_args.push_str("int"); - } - Type::Single(BasicType::Long) => { - java_args.push_str("long"); - } - Type::Single(BasicType::Float) => { - java_args.push_str("float"); - } - Type::Single(BasicType::Double) => { - java_args.push_str("double"); - } - Type::Single(BasicType::Class(class)) => { - let class = class + let obj_arg; + java_args.push_str(match arg.field_type { + FieldType::Boolean => "boolean", + FieldType::Byte => "byte", + FieldType::Char => "char", + FieldType::Short => "short", + FieldType::Integer => "int", + FieldType::Long => "long", + FieldType::Float => "float", + FieldType::Double => "double", + FieldType::Object(ref class_name) => { + let class = IdBuf::from(class_name); + obj_arg = class .as_str() .replace('/', pattern.argument_namespace_separator.as_str()) .replace('$', pattern.argument_inner_class_seperator.as_str()); - java_args.push_str(&class); + obj_arg.as_str() } - Type::Array { levels, inner } => { - match inner { - BasicType::Void => { - return None; - } - BasicType::Boolean => java_args.push_str("bool"), - BasicType::Byte => java_args.push_str("byte"), - BasicType::Char => java_args.push_str("char"), - BasicType::Short => java_args.push_str("short"), - BasicType::Int => java_args.push_str("int"), - BasicType::Long => java_args.push_str("long"), - BasicType::Float => java_args.push_str("float"), - BasicType::Double => java_args.push_str("double"), - BasicType::Class(class) => { - let class = class - .as_str() - .replace('/', pattern.argument_namespace_separator.as_str()) - .replace('$', pattern.argument_inner_class_seperator.as_str()); - java_args.push_str(&class); - } - } - for _ in 1..levels { - java_args.push_str("%5B%5D"); // [] - } - prev_was_array = true; // level 0 + }); + if arg.dimensions > 0 { + for _ in 1..arg.dimensions { + java_args.push_str("[]"); } + prev_was_array = true; // level 0 } } @@ -207,7 +169,7 @@ impl KnownDocsUrl { if method.java.is_varargs() { java_args.push_str("..."); } else { - java_args.push_str("%5B%5D"); // [] + java_args.push_str("[]"); } } @@ -229,7 +191,7 @@ impl KnownDocsUrl { context: &Context, java_class: &str, java_field: &str, - _java_descriptor: field::Descriptor, + _java_descriptor: FieldDescriptor, ) -> Option { let pattern = context .config diff --git a/java-spaghetti-gen/src/emit_rust/methods.rs b/java-spaghetti-gen/src/emit_rust/methods.rs index 1ca84ff..4d7d6e6 100644 --- a/java-spaghetti-gen/src/emit_rust/methods.rs +++ b/java-spaghetti-gen/src/emit_rust/methods.rs @@ -1,25 +1,28 @@ use std::io; -use jreflection::method; +use cafebabe::descriptors::{FieldType, ReturnDescriptor}; +use super::fields::FieldTypeEmitter; use super::known_docs_url::KnownDocsUrl; +use super::StrEmitter; use crate::emit_rust::Context; use crate::identifiers::MethodManglingStyle; +use crate::parser_util::{Class, JavaMethod, MethodSigWriter}; pub struct Method<'a> { - pub class: &'a jreflection::Class, - pub java: &'a jreflection::Method, + pub class: &'a Class, + pub java: JavaMethod<'a>, rust_name: Option, mangling_style: MethodManglingStyle, } impl<'a> Method<'a> { - pub fn new(context: &Context, class: &'a jreflection::Class, java: &'a jreflection::Method) -> Self { + pub fn new(context: &Context, class: &'a Class, java: &'a cafebabe::MethodInfo<'a>) -> Self { let mut result = Self { class, - java, + java: JavaMethod::from(java), rust_name: None, - mangling_style: MethodManglingStyle::Java, // Immediately overwritten bellow + mangling_style: MethodManglingStyle::Java, // Immediately overwritten below }; result.set_mangling_style(context.config.codegen.method_naming_style); // rust_name + mangling_style result @@ -31,25 +34,21 @@ impl<'a> Method<'a> { pub fn set_mangling_style(&mut self, style: MethodManglingStyle) { self.mangling_style = style; - self.rust_name = if let Ok(name) = self + self.rust_name = self .mangling_style - .mangle(self.java.name.as_str(), self.java.descriptor()) - { - Some(name) - } else { - None // Failed to mangle - }; + .mangle(self.java.name(), self.java.descriptor()) + .ok() } pub fn emit(&self, context: &Context, mod_: &str, out: &mut impl io::Write) -> io::Result<()> { let mut emit_reject_reasons = Vec::new(); - let java_class_method = format!("{}\x1f{}", self.class.path.as_str(), &self.java.name); + let java_class_method = format!("{}\x1f{}", self.class.path().as_str(), self.java.name()); let java_class_method_sig = format!( "{}\x1f{}\x1f{}", - self.class.path.as_str(), - &self.java.name, - self.java.descriptor_str() + self.class.path().as_str(), + self.java.name(), + MethodSigWriter(self.java.descriptor()) ); let ignored = context.config.ignore_class_methods.contains(&java_class_method) @@ -69,7 +68,7 @@ impl<'a> Method<'a> { name.to_owned() } else { emit_reject_reasons.push("ERROR: Failed to mangle method name"); - self.java.name.to_owned() + self.java.name().to_owned() }; if !self.java.is_public() { @@ -98,85 +97,22 @@ impl<'a> Method<'a> { String::from("self: &::java_spaghetti::Ref<'env, Self>") }; - for (arg_idx, arg) in descriptor.arguments().enumerate() { + for (arg_idx, arg) in descriptor.parameters.iter().enumerate() { let arg_name = format!("arg{}", arg_idx); - let mut param_is_object = false; // XXX - - let arg_type = match arg { - method::Type::Single(method::BasicType::Void) => { - emit_reject_reasons.push("ERROR: Void arguments aren't a thing"); - "()".to_owned() - } - method::Type::Single(method::BasicType::Boolean) => "bool".to_owned(), - method::Type::Single(method::BasicType::Byte) => "i8".to_owned(), - method::Type::Single(method::BasicType::Char) => "u16".to_owned(), - method::Type::Single(method::BasicType::Short) => "i16".to_owned(), - method::Type::Single(method::BasicType::Int) => "i32".to_owned(), - method::Type::Single(method::BasicType::Long) => "i64".to_owned(), - method::Type::Single(method::BasicType::Float) => "f32".to_owned(), - method::Type::Single(method::BasicType::Double) => "f64".to_owned(), - method::Type::Single(method::BasicType::Class(class)) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for argument type"); - } - param_is_object = true; - match context.java_to_rust_path(class, mod_) { - Ok(path) => format!("impl ::java_spaghetti::AsArg<{}>", path), - Err(_) => { - emit_reject_reasons - .push("ERROR: Failed to resolve JNI path to Rust path for argument type"); - format!("{:?}", class) - } - } - } - method::Type::Array { levels, inner } => { - let mut buffer = "impl ::java_spaghetti::AsArg<".to_owned(); - for _ in 0..(levels - 1) { - buffer.push_str("::java_spaghetti::ObjectArray<"); - } - match inner { - method::BasicType::Boolean => buffer.push_str("::java_spaghetti::BooleanArray"), - method::BasicType::Byte => buffer.push_str("::java_spaghetti::ByteArray"), - method::BasicType::Char => buffer.push_str("::java_spaghetti::CharArray"), - method::BasicType::Short => buffer.push_str("::java_spaghetti::ShortArray"), - method::BasicType::Int => buffer.push_str("::java_spaghetti::IntArray"), - method::BasicType::Long => buffer.push_str("::java_spaghetti::LongArray"), - method::BasicType::Float => buffer.push_str("::java_spaghetti::FloatArray"), - method::BasicType::Double => buffer.push_str("::java_spaghetti::DoubleArray"), - method::BasicType::Class(class) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for argument type"); - } - buffer.push_str("::java_spaghetti::ObjectArray<"); - match context.java_to_rust_path(class, mod_) { - Ok(path) => buffer.push_str(path.as_str()), - Err(_) => { - emit_reject_reasons - .push("ERROR: Failed to resolve JNI path to Rust path for argument type"); - buffer.push_str("???"); - } - } - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } - method::BasicType::Void => { - emit_reject_reasons.push("ERROR: Arrays of void isn't a thing"); - buffer.push_str("[()]"); - } - } - for _ in 0..(levels - 1) { - // ObjectArray s - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } - buffer.push_str(">"); // AsArg - - param_is_object = true; - buffer - } + let param_is_object = matches!(arg.field_type, FieldType::Object(_)) || arg.dimensions > 0; + + let rust_type = FieldTypeEmitter(arg) + .emit_rust_type(context, mod_, &mut emit_reject_reasons) + .map_err(|_| io::Error::other("std::fmt::Error"))?; + + let arg_type = if arg.dimensions == 0 && !param_is_object { + rust_type.into_owned() + } else { + let mut rust_type = rust_type.into_owned(); + rust_type.insert_str(0, "impl ::java_spaghetti::AsArg<"); + rust_type.push('>'); + rust_type }; if !params_array.is_empty() { @@ -201,102 +137,34 @@ impl<'a> Method<'a> { params_decl.push_str(arg_type.as_str()); } - let mut ret_decl = match descriptor.return_type() { - // Contents of fn name<'env>() -> Result<...> { - method::Type::Single(method::BasicType::Void) => "()".to_owned(), - method::Type::Single(method::BasicType::Boolean) => "bool".to_owned(), - method::Type::Single(method::BasicType::Byte) => "i8".to_owned(), - method::Type::Single(method::BasicType::Char) => "u16".to_owned(), - method::Type::Single(method::BasicType::Short) => "i16".to_owned(), - method::Type::Single(method::BasicType::Int) => "i32".to_owned(), - method::Type::Single(method::BasicType::Long) => "i64".to_owned(), - method::Type::Single(method::BasicType::Float) => "f32".to_owned(), - method::Type::Single(method::BasicType::Double) => "f64".to_owned(), - method::Type::Single(method::BasicType::Class(class)) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for return type"); - } - match context.java_to_rust_path(class, mod_) { - Ok(path) => format!("::std::option::Option<::java_spaghetti::Local<'env, {}>>", path), - Err(_) => { - emit_reject_reasons.push("ERROR: Failed to resolve JNI path to Rust path for return type"); - format!("{:?}", class) - } - } - } - method::Type::Array { - levels: 1, - inner: method::BasicType::Void, - } => { - emit_reject_reasons.push("ERROR: Returning arrays of void isn't a thing"); - "???".to_owned() - } - method::Type::Array { levels, inner } => { - let mut buffer = "::std::option::Option<::java_spaghetti::Local<'env, ".to_owned(); - for _ in 0..(levels - 1) { - buffer.push_str("::java_spaghetti::ObjectArray<"); - } - match inner { - method::BasicType::Boolean => buffer.push_str("::java_spaghetti::BooleanArray"), - method::BasicType::Byte => buffer.push_str("::java_spaghetti::ByteArray"), - method::BasicType::Char => buffer.push_str("::java_spaghetti::CharArray"), - method::BasicType::Short => buffer.push_str("::java_spaghetti::ShortArray"), - method::BasicType::Int => buffer.push_str("::java_spaghetti::IntArray"), - method::BasicType::Long => buffer.push_str("::java_spaghetti::LongArray"), - method::BasicType::Float => buffer.push_str("::java_spaghetti::FloatArray"), - method::BasicType::Double => buffer.push_str("::java_spaghetti::DoubleArray"), - method::BasicType::Class(class) => { - if !context.all_classes.contains_key(class.as_str()) { - emit_reject_reasons.push("ERROR: missing class for return type"); - } - buffer.push_str("::java_spaghetti::ObjectArray<"); - match context.java_to_rust_path(class, mod_) { - Ok(path) => buffer.push_str(path.as_str()), - Err(_) => { - emit_reject_reasons - .push("ERROR: Failed to resolve JNI path to Rust path for return type"); - buffer.push_str("???"); - } - } - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } - method::BasicType::Void => { - emit_reject_reasons.push("ERROR: Arrays of void isn't a thing"); - buffer.push_str("[()]"); - } - } - for _ in 0..(levels - 1) { - // ObjectArray s - buffer.push_str(", "); - buffer.push_str(&context.throwable_rust_path(mod_)); - buffer.push('>'); - } - buffer.push_str(">>"); // Local, Option - buffer + let mut ret_decl = if let ReturnDescriptor::Return(desc) = &descriptor.return_type { + let rust_type = FieldTypeEmitter(desc) + .emit_rust_type(context, mod_, &mut emit_reject_reasons) + .map_err(|_| io::Error::other("std::fmt::Error"))?; + + let param_is_object = matches!(desc.field_type, FieldType::Object(_)); + if desc.dimensions == 0 && !param_is_object { + rust_type + } else { + let mut rust_type = rust_type.into_owned(); + rust_type.insert_str(0, "::std::option::Option<::java_spaghetti::Local<'env, "); + rust_type.push_str(">>"); + rust_type.into() } + } else { + "()".into() }; - let mut ret_method_fragment = match descriptor.return_type() { - // Contents of call_..._method_a - method::Type::Single(method::BasicType::Void) => "void", - method::Type::Single(method::BasicType::Boolean) => "boolean", - method::Type::Single(method::BasicType::Byte) => "byte", - method::Type::Single(method::BasicType::Char) => "char", - method::Type::Single(method::BasicType::Short) => "short", - method::Type::Single(method::BasicType::Int) => "int", - method::Type::Single(method::BasicType::Long) => "long", - method::Type::Single(method::BasicType::Float) => "float", - method::Type::Single(method::BasicType::Double) => "double", - method::Type::Single(method::BasicType::Class(_)) => "object", - method::Type::Array { .. } => "object", + let mut ret_method_fragment = if let ReturnDescriptor::Return(desc) = &descriptor.return_type { + FieldTypeEmitter(desc).emit_fragment_type() + } else { + "void" }; if self.java.is_constructor() { - if descriptor.return_type() == method::Type::Single(method::BasicType::Void) { + if descriptor.return_type == ReturnDescriptor::Void { ret_method_fragment = "object"; - ret_decl = "::java_spaghetti::Local<'env, Self>".to_string(); + ret_decl = "::java_spaghetti::Local<'env, Self>".into(); } else { emit_reject_reasons.push("ERROR: Constructor should've returned void"); } @@ -312,80 +180,81 @@ impl<'a> Method<'a> { "// " }; let access = if self.java.is_public() { "pub " } else { "" }; - let attributes = (if self.java.deprecated { "#[deprecated] " } else { "" }).to_string(); + let attributes = if self.java.deprecated() { "#[deprecated] " } else { "" }; writeln!(out)?; for reason in &emit_reject_reasons { - writeln!(out, "{}// Not emitting: {}", indent, reason)?; + writeln!(out, "{indent}// Not emitting: {reason}")?; } if let Some(url) = KnownDocsUrl::from_method(context, self) { - writeln!(out, "{}/// {}", indent, url)?; + writeln!(out, "{indent}/// {url}")?; } else { - writeln!(out, "{}/// {}", indent, self.java.name.as_str())?; + writeln!(out, "{indent}/// {}", self.java.name())?; } writeln!( out, - "{}{}{}fn {}<'env>({}) -> ::std::result::Result<{}, ::java_spaghetti::Local<'env, {}>> {{", - indent, - attributes, - access, - method_name, - params_decl, - ret_decl, + "{indent}{attributes}{access}fn {method_name}<'env>({params_decl}) -> \ + ::std::result::Result<{ret_decl}, ::java_spaghetti::Local<'env, {}>> {{", context.throwable_rust_path(mod_) )?; writeln!( out, - "{} // class.path == {:?}, java.flags == {:?}, .name == {:?}, .descriptor == {:?}", + "{} // class.path == {:?}, java.flags == {:?}, .name == {:?}, .descriptor == \"{}\"", indent, - &self.class.path.as_str(), - self.java.flags, - &self.java.name, - &self.java.descriptor_str() + self.class.path().as_str(), + self.java.access_flags, + self.java.name(), + MethodSigWriter(self.java.descriptor()) )?; - writeln!(out, "{} unsafe {{", indent)?; - writeln!(out, "{} let __jni_args = [{}];", indent, params_array)?; + + writeln!( + out, + "{indent} static __METHOD: ::std::sync::OnceLock<::java_spaghetti::JMethodID> \ + = ::std::sync::OnceLock::new();" + )?; + writeln!(out, "{indent} unsafe {{")?; + writeln!(out, "{indent} let __jni_args = [{params_array}];")?; if !self.java.is_constructor() && !self.java.is_static() { - writeln!(out, "{} let __jni_env = self.env();", indent)?; + writeln!(out, "{indent} let __jni_env = self.env();")?; } - writeln!( out, - "{} let (__jni_class, __jni_method) = __jni_env.require_class_{}method({}, {}, {});", - indent, + "{indent} let __jni_class = Self::__class_global_ref(__jni_env);" + )?; + writeln!( + out, + "{indent} \ + let __jni_method = __METHOD.get_or_init(|| \ + ::java_spaghetti::JMethodID::from_raw(__jni_env.require_{}method(__jni_class, {}, {}))\ + ).as_raw();", if self.java.is_static() { "static_" } else { "" }, - emit_cstr(self.class.path.as_str()), - emit_cstr(self.java.name.as_str()), - emit_cstr(self.java.descriptor_str()) + StrEmitter(self.java.name()), + StrEmitter(MethodSigWriter(self.java.descriptor())) )?; if self.java.is_constructor() { writeln!( out, - "{} __jni_env.new_object_a(__jni_class, __jni_method, __jni_args.as_ptr())", - indent + "{indent} \ + __jni_env.new_object_a(__jni_class, __jni_method, __jni_args.as_ptr())", )?; } else if self.java.is_static() { writeln!( out, - "{} __jni_env.call_static_{}_method_a(__jni_class, __jni_method, __jni_args.as_ptr())", - indent, ret_method_fragment + "{indent} \ + __jni_env.call_static_{}_method_a(__jni_class, __jni_method, __jni_args.as_ptr())", + ret_method_fragment )?; } else { writeln!( out, - "{} __jni_env.call_{}_method_a(self.as_raw(), __jni_method, __jni_args.as_ptr())", - indent, ret_method_fragment + "{indent} \ + __jni_env.call_{}_method_a(self.as_raw(), __jni_method, __jni_args.as_ptr())", + ret_method_fragment )?; } - writeln!(out, "{} }}", indent)?; - writeln!(out, "{}}}", indent)?; + writeln!(out, "{indent} }}")?; + writeln!(out, "{indent}}}")?; Ok(()) } } - -fn emit_cstr(s: &str) -> String { - let mut s = format!("{:?}", s); // XXX - s.insert_str(s.len() - 1, "\\0"); - s -} diff --git a/java-spaghetti-gen/src/emit_rust/mod.rs b/java-spaghetti-gen/src/emit_rust/mod.rs index 305c99a..614832d 100644 --- a/java-spaghetti-gen/src/emit_rust/mod.rs +++ b/java-spaghetti-gen/src/emit_rust/mod.rs @@ -9,3 +9,20 @@ mod preamble; mod structs; pub use context::Context; + +/// Writes the string (with "\0" added at the right side) surrounded by double quotes. +/// +/// XXX: This implementation (as well as `Env` methods in `java-spaghetti` crate) +/// should probably use byte slices so that full Unicode support can be made possible: +/// JNI `GetFieldID` and `GetMethodID` expects *modified* UTF-8 string name and signature. +/// Note: `cafebabe` converts modified UTF-8 string to real UTF-8 string at first hand. +struct StrEmitter(pub T); + +impl std::fmt::Display for StrEmitter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use std::fmt::Write; + f.write_char('\"')?; + self.0.fmt(f)?; + f.write_str("\\0\"") + } +} diff --git a/java-spaghetti-gen/src/emit_rust/structs.rs b/java-spaghetti-gen/src/emit_rust/structs.rs index 5dd3884..37d0e11 100644 --- a/java-spaghetti-gen/src/emit_rust/structs.rs +++ b/java-spaghetti-gen/src/emit_rust/structs.rs @@ -3,13 +3,13 @@ use std::error::Error; use std::fmt::Write; use std::io; -use jreflection::class; - use super::fields::Field; use super::known_docs_url::KnownDocsUrl; use super::methods::Method; +use super::StrEmitter; use crate::emit_rust::Context; use crate::identifiers::{FieldMangling, RustIdentifier}; +use crate::parser_util::{Class, Id, IdPart}; #[derive(Debug, Default)] pub(crate) struct StructPaths { @@ -18,7 +18,7 @@ pub(crate) struct StructPaths { } impl StructPaths { - pub(crate) fn new(context: &Context, class: class::Id) -> Result> { + pub(crate) fn new(context: &Context, class: Id) -> Result> { Ok(Self { mod_: Struct::mod_for(context, class)?, struct_name: Struct::name_for(context, class)?, @@ -26,10 +26,10 @@ impl StructPaths { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub(crate) struct Struct { pub rust: StructPaths, - pub java: jreflection::Class, + pub java: Class, } fn rust_id(id: &str) -> Result> { @@ -45,24 +45,24 @@ fn rust_id(id: &str) -> Result> { } impl Struct { - pub(crate) fn mod_for(_context: &Context, class: class::Id) -> Result> { + pub(crate) fn mod_for(_context: &Context, class: Id) -> Result> { let mut buf = String::new(); - for component in class.iter() { + for component in class { match component { - class::IdPart::Namespace(id) => { + IdPart::Namespace(id) => { if !buf.is_empty() { buf.push_str("::"); } buf.push_str(&rust_id(id)?); } - class::IdPart::ContainingClass(_) => {} - class::IdPart::LeafClass(_) => {} + IdPart::ContainingClass(_) => {} + IdPart::LeafClass(_) => {} } } Ok(buf) } - pub(crate) fn name_for(context: &Context, class: class::Id) -> Result> { + pub(crate) fn name_for(context: &Context, class: Id) -> Result> { let rename_to = context .config .rename_classes @@ -72,9 +72,9 @@ impl Struct { let mut buf = String::new(); for component in class.iter() { match component { - class::IdPart::Namespace(_) => {} - class::IdPart::ContainingClass(id) => write!(&mut buf, "{}_", rust_id(id)?)?, - class::IdPart::LeafClass(id) => write!( + IdPart::Namespace(_) => {} + IdPart::ContainingClass(id) => write!(&mut buf, "{}_", rust_id(id)?)?, + IdPart::LeafClass(id) => write!( &mut buf, "{}", rename_to.map(ToString::to_string).or_else(|_| rust_id(id))? @@ -84,8 +84,8 @@ impl Struct { Ok(buf) } - pub(crate) fn new(context: &mut Context, java: jreflection::Class) -> Result> { - let rust = StructPaths::new(context, java.path.as_id())?; + pub(crate) fn new(context: &mut Context, java: Class) -> Result> { + let rust = StructPaths::new(context, java.path())?; Ok(Self { rust, java }) } @@ -108,12 +108,12 @@ impl Struct { }; let visibility = if self.java.is_public() { "pub" } else { "" }; - let attributes = (if self.java.deprecated { "#[deprecated] " } else { "" }).to_string(); + let attributes = (if self.java.deprecated() { "#[deprecated] " } else { "" }).to_string(); - if let Some(url) = KnownDocsUrl::from_class(context, self.java.path.as_id()) { + if let Some(url) = KnownDocsUrl::from_class(context, self.java.path()) { writeln!(out, "/// {} {} {}", visibility, keyword, url)?; } else { - writeln!(out, "/// {} {} {}", visibility, keyword, self.java.path.as_str())?; + writeln!(out, "/// {} {} {}", visibility, keyword, self.java.path().as_str())?; } let rust_name = &self.rust.struct_name; @@ -123,50 +123,59 @@ impl Struct { } writeln!( out, - "unsafe impl ::java_spaghetti::JniType for {rust_name} {{ - fn static_with_jni_type(callback: impl FnOnce(&str) -> R) -> R {{ - callback({:?}) - }} - }}", - self.java.path.as_str().to_string() + "\0", + "unsafe impl ::java_spaghetti::JniType for {rust_name} {{\ + \n fn static_with_jni_type(callback: impl FnOnce(&str) -> R) -> R {{\ + \n callback({})\ + \n }}\ + \n}}", + StrEmitter(self.java.path().as_str()), )?; // recursively visit all superclasses and superinterfaces. let mut queue = Vec::new(); let mut visited = HashSet::new(); - queue.push(self.java.path.clone()); - visited.insert(self.java.path.clone()); + queue.push(self.java.path()); + visited.insert(self.java.path()); while let Some(path) = queue.pop() { let class = context.all_classes.get(path.as_str()).unwrap(); - for path2 in self.java.interfaces.iter().chain(class.java.super_path.as_ref()) { - if context.all_classes.contains_key(path2.as_str()) && !visited.contains(path2) { - let rust_path = context.java_to_rust_path(path2.as_id(), &self.rust.mod_).unwrap(); + for path2 in self.java.interfaces().map(|i| Id(i)).chain(class.java.super_path()) { + if context.all_classes.contains_key(path2.as_str()) && !visited.contains(&path2) { + let rust_path = context.java_to_rust_path(path2, &self.rust.mod_).unwrap(); writeln!( out, "unsafe impl ::java_spaghetti::AssignableTo<{rust_path}> for {rust_name} {{}}" )?; - queue.push(path2.clone()); - visited.insert(path2.clone()); + queue.push(path2); + visited.insert(path2); } } } writeln!(out, "impl {rust_name} {{")?; + writeln!( + out, + "\ + \nfn __class_global_ref(__jni_env: ::java_spaghetti::Env) -> ::java_spaghetti::sys::jobject {{\ + \n static __CLASS: ::std::sync::OnceLock<::java_spaghetti::Global<{}>> = ::std::sync::OnceLock::new();\ + \n __CLASS.get_or_init(|| unsafe {{\ + \n ::java_spaghetti::Local::from_raw(__jni_env, __jni_env.require_class({})).as_global()\ + \n }}).as_raw()\ + \n}}", + context + .java_to_rust_path(Id("java/lang/Object"), &self.rust.mod_) + .unwrap(), + StrEmitter(self.java.path().as_str()), + )?; + let mut id_repeats = HashMap::new(); let mut methods: Vec = self .java - .methods - .iter() + .methods() .map(|m| Method::new(context, &self.java, m)) .collect(); - let mut fields: Vec = self - .java - .fields - .iter() - .map(|f| Field::new(context, &self.java, f)) - .collect(); + let mut fields: Vec = self.java.fields().map(|f| Field::new(context, &self.java, f)).collect(); for method in &methods { if !method.java.is_public() { diff --git a/java-spaghetti-gen/src/identifiers/field_mangling_style.rs b/java-spaghetti-gen/src/identifiers/field_mangling_style.rs index 2e20037..13c78b3 100644 --- a/java-spaghetti-gen/src/identifiers/field_mangling_style.rs +++ b/java-spaghetti-gen/src/identifiers/field_mangling_style.rs @@ -1,10 +1,10 @@ -use jreflection::{field, Field}; use serde_derive::Deserialize; use crate::identifiers::{constify_identifier, javaify_identifier, rustify_identifier, IdentifierManglingError}; +use crate::parser_util::JavaField; pub enum FieldMangling<'a> { - ConstValue(String, &'a field::Constant), + ConstValue(String, cafebabe::constant_pool::LiteralConstant<'a>), GetSet(String, String), } @@ -30,11 +30,11 @@ impl Default for FieldManglingStyle { impl FieldManglingStyle { pub fn mangle<'a>( &self, - field: &'a Field, + field: JavaField<'a>, renamed_to: Option<&str>, ) -> Result, IdentifierManglingError> { - let field_name = renamed_to.unwrap_or(field.name.as_str()); - if let (Some(value), true, true) = (field.constant.as_ref(), field.is_constant(), self.const_finals) { + let field_name = renamed_to.unwrap_or(field.name()); + if let (Some(value), true) = (field.constant().as_ref(), self.const_finals) { let name = if renamed_to.is_some() { Ok(field_name.to_owned()) // Don't remangle renames } else if self.rustify_names { @@ -43,7 +43,7 @@ impl FieldManglingStyle { javaify_identifier(field_name) }?; - Ok(FieldMangling::ConstValue(name, value)) + Ok(FieldMangling::ConstValue(name, value.clone())) } else { Ok(FieldMangling::GetSet( self.mangle_identifier(self.getter_pattern.replace("{NAME}", field_name).as_str())?, diff --git a/java-spaghetti-gen/src/identifiers/method_mangling_style.rs b/java-spaghetti-gen/src/identifiers/method_mangling_style.rs index eb2d511..3f05413 100644 --- a/java-spaghetti-gen/src/identifiers/method_mangling_style.rs +++ b/java-spaghetti-gen/src/identifiers/method_mangling_style.rs @@ -1,8 +1,8 @@ -use jreflection::class::IdPart; -use jreflection::method; +use cafebabe::descriptors::{FieldType, MethodDescriptor}; use serde_derive::Deserialize; use super::rust_identifier::{javaify_identifier, rustify_identifier, IdentifierManglingError}; +use crate::parser_util::{ClassName, IdPart}; #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] @@ -73,13 +73,57 @@ pub enum MethodManglingStyle { #[test] fn method_mangling_style_mangle_test() { + use std::borrow::Cow; + + use cafebabe::descriptors::{FieldDescriptor, ReturnDescriptor, UnqualifiedSegment}; + + let desc_no_arg_ret_v = MethodDescriptor { + parameters: Vec::new(), + return_type: ReturnDescriptor::Void, + }; + + let desc_arg_i_ret_v = MethodDescriptor { + parameters: vec![FieldDescriptor { + dimensions: 0, + field_type: FieldType::Integer, + }], + return_type: ReturnDescriptor::Void, + }; + + let desc_arg_obj_ret_v = MethodDescriptor { + parameters: vec![FieldDescriptor { + dimensions: 0, + field_type: FieldType::Object(cafebabe::descriptors::ClassName { + segments: vec![ + UnqualifiedSegment { + name: Cow::Borrowed("java"), + }, + UnqualifiedSegment { + name: Cow::Borrowed("lang"), + }, + UnqualifiedSegment { + name: Cow::Borrowed("Object"), + }, + ], + }), + }], + return_type: ReturnDescriptor::Void, + }; + for &(name, sig, java, java_short, java_long, rust, rust_short, rust_long) in &[ ( - "getFoo", "()V", "getFoo", "getFoo", "getFoo", "get_foo", "get_foo", "get_foo", + "getFoo", + &desc_no_arg_ret_v, + "getFoo", + "getFoo", + "getFoo", + "get_foo", + "get_foo", + "get_foo", ), ( "getFoo", - "(I)V", + &desc_arg_i_ret_v, "getFoo", "getFoo_int", "getFoo_int", @@ -89,7 +133,7 @@ fn method_mangling_style_mangle_test() { ), ( "getFoo", - "(Ljava/lang/Object;)V", + &desc_arg_obj_ret_v, "getFoo", "getFoo_Object", "getFoo_java_lang_Object", @@ -97,13 +141,20 @@ fn method_mangling_style_mangle_test() { "get_foo_object", "get_foo_java_lang_object", ), - ("", "()V", "new", "new", "new", "new", "new", "new"), + ("", &desc_no_arg_ret_v, "new", "new", "new", "new", "new", "new"), ( - "", "(I)V", "new", "new_int", "new_int", "new", "new_int", "new_int", + "", + &desc_arg_i_ret_v, + "new", + "new_int", + "new_int", + "new", + "new_int", + "new_int", ), ( "", - "(Ljava/lang/Object;)V", + &desc_arg_obj_ret_v, "new", "new_Object", "new_java_lang_Object", @@ -114,8 +165,6 @@ fn method_mangling_style_mangle_test() { // TODO: get1DFoo // TODO: array types (primitive + non-primitive) ] { - let sig = method::Descriptor::new(sig).unwrap(); - assert_eq!(MethodManglingStyle::Java.mangle(name, sig).unwrap(), java); assert_eq!( MethodManglingStyle::JavaShortSignature.mangle(name, sig).unwrap(), @@ -140,28 +189,29 @@ fn method_mangling_style_mangle_test() { #[test] fn mangle_method_name_test() { + use cafebabe::descriptors::{MethodDescriptor, ReturnDescriptor}; + + let desc = MethodDescriptor { + parameters: Vec::new(), + return_type: ReturnDescriptor::Void, + }; + assert_eq!( - MethodManglingStyle::Rustify - .mangle("isFooBar", method::Descriptor::new("()V").unwrap()) - .unwrap(), + MethodManglingStyle::Rustify.mangle("isFooBar", &desc).unwrap(), "is_foo_bar" ); assert_eq!( - MethodManglingStyle::Rustify - .mangle("XMLHttpRequest", method::Descriptor::new("()V").unwrap()) - .unwrap(), + MethodManglingStyle::Rustify.mangle("XMLHttpRequest", &desc).unwrap(), "xml_http_request" ); assert_eq!( - MethodManglingStyle::Rustify - .mangle("getFieldID_Input", method::Descriptor::new("()V").unwrap()) - .unwrap(), + MethodManglingStyle::Rustify.mangle("getFieldID_Input", &desc).unwrap(), "get_field_id_input" ); } impl MethodManglingStyle { - pub fn mangle(&self, name: &str, descriptor: method::Descriptor) -> Result { + pub fn mangle(&self, name: &str, descriptor: &MethodDescriptor) -> Result { let name = match name { "" => { return Err(IdentifierManglingError::EmptyString); @@ -173,201 +223,31 @@ impl MethodManglingStyle { name => name, }; - match self { - MethodManglingStyle::Java => Ok(javaify_identifier(name)?), - MethodManglingStyle::JavaShortSignature => { - Ok(javaify_identifier(&format!("{}{}", name, short_sig(descriptor))[..])?) - } - MethodManglingStyle::JavaLongSignature => { - Ok(javaify_identifier(&format!("{}{}", name, long_sig(descriptor))[..])?) - } - - MethodManglingStyle::Rustify => Ok(rustify_identifier(name)?), - MethodManglingStyle::RustifyShortSignature => { - Ok(rustify_identifier(&format!("{}{}", name, short_sig(descriptor))[..])?) - } - MethodManglingStyle::RustifyLongSignature => { - Ok(rustify_identifier(&format!("{}{}", name, long_sig(descriptor))[..])?) - } - } - } -} - -fn short_sig(descriptor: method::Descriptor) -> String { - use method::{BasicType, Type}; - - let mut buffer = String::new(); - - for arg in descriptor.arguments() { - match arg { - Type::Single(BasicType::Boolean) => { - buffer.push_str("_boolean"); - } - Type::Single(BasicType::Byte) => { - buffer.push_str("_byte"); - } - Type::Single(BasicType::Char) => { - buffer.push_str("_char"); - } - Type::Single(BasicType::Double) => { - buffer.push_str("_double"); - } - Type::Single(BasicType::Float) => { - buffer.push_str("_float"); - } - Type::Single(BasicType::Int) => { - buffer.push_str("_int"); - } - Type::Single(BasicType::Long) => { - buffer.push_str("_long"); - } - Type::Single(BasicType::Short) => { - buffer.push_str("_short"); - } - Type::Single(BasicType::Void) => { - buffer.push_str("_void"); - } - Type::Single(BasicType::Class(class)) => { - if let Some(IdPart::LeafClass(leaf)) = class.iter().last() { - buffer.push('_'); - buffer.push_str(leaf); - } else { - buffer.push_str("_unknown"); - } - } - Type::Array { levels, inner } => { - match inner { - BasicType::Boolean => { - buffer.push_str("_boolean"); - } - BasicType::Byte => { - buffer.push_str("_byte"); - } - BasicType::Char => { - buffer.push_str("_char"); - } - BasicType::Double => { - buffer.push_str("_double"); - } - BasicType::Float => { - buffer.push_str("_float"); - } - BasicType::Int => { - buffer.push_str("_int"); - } - BasicType::Long => { - buffer.push_str("_long"); - } - BasicType::Short => { - buffer.push_str("_short"); - } - BasicType::Void => { - buffer.push_str("_void"); - } - BasicType::Class(class) => { - for component in class.iter() { - match component { - IdPart::Namespace(_) => {} - IdPart::ContainingClass(_) => {} - IdPart::LeafClass(cls) => { - buffer.push('_'); - buffer.push_str(cls); - } - } - } - } - } - - for _ in 0..levels { - buffer.push_str("_array"); - } - } - } - } - - buffer -} + let (rustify, long_sig) = match self { + MethodManglingStyle::Java => return javaify_identifier(name), + MethodManglingStyle::Rustify => return rustify_identifier(name), + MethodManglingStyle::JavaShortSignature => (false, false), + MethodManglingStyle::JavaLongSignature => (false, true), + MethodManglingStyle::RustifyShortSignature => (true, false), + MethodManglingStyle::RustifyLongSignature => (true, true), + }; -fn long_sig(descriptor: method::Descriptor) -> String { - use method::{BasicType, Type}; + let mut buffer = name.to_string(); - let mut buffer = String::new(); + for arg in descriptor.parameters.iter() { + match &arg.field_type { + FieldType::Boolean => buffer.push_str("_boolean"), + FieldType::Byte => buffer.push_str("_byte"), + FieldType::Char => buffer.push_str("_char"), + FieldType::Short => buffer.push_str("_short"), + FieldType::Integer => buffer.push_str("_int"), + FieldType::Long => buffer.push_str("_long"), + FieldType::Float => buffer.push_str("_float"), + FieldType::Double => buffer.push_str("_double"), + FieldType::Object(class_name) => { + let class = ClassName::from(class_name); - for arg in descriptor.arguments() { - match arg { - Type::Single(BasicType::Boolean) => { - buffer.push_str("_boolean"); - } - Type::Single(BasicType::Byte) => { - buffer.push_str("_byte"); - } - Type::Single(BasicType::Char) => { - buffer.push_str("_char"); - } - Type::Single(BasicType::Double) => { - buffer.push_str("_double"); - } - Type::Single(BasicType::Float) => { - buffer.push_str("_float"); - } - Type::Single(BasicType::Int) => { - buffer.push_str("_int"); - } - Type::Single(BasicType::Long) => { - buffer.push_str("_long"); - } - Type::Single(BasicType::Short) => { - buffer.push_str("_short"); - } - Type::Single(BasicType::Void) => { - buffer.push_str("_void"); - } - Type::Single(BasicType::Class(class)) => { - for component in class.iter() { - buffer.push('_'); - match component { - IdPart::Namespace(namespace) => { - buffer.push_str(namespace); - } - IdPart::ContainingClass(cls) => { - buffer.push_str(cls); - } - IdPart::LeafClass(cls) => { - buffer.push_str(cls); - } - } - } - } - Type::Array { levels, inner } => { - match inner { - BasicType::Boolean => { - buffer.push_str("_boolean"); - } - BasicType::Byte => { - buffer.push_str("_byte"); - } - BasicType::Char => { - buffer.push_str("_char"); - } - BasicType::Double => { - buffer.push_str("_double"); - } - BasicType::Float => { - buffer.push_str("_float"); - } - BasicType::Int => { - buffer.push_str("_int"); - } - BasicType::Long => { - buffer.push_str("_long"); - } - BasicType::Short => { - buffer.push_str("_short"); - } - BasicType::Void => { - buffer.push_str("_void"); - } - BasicType::Class(class) => { + if long_sig { for component in class.iter() { buffer.push('_'); match component { @@ -382,15 +262,28 @@ fn long_sig(descriptor: method::Descriptor) -> String { } } } + } else { + // short style + if let Some(IdPart::LeafClass(leaf)) = class.iter().last() { + buffer.push('_'); + buffer.push_str(leaf); + } else if arg.dimensions == 0 { + // XXX: `if arg.dimensions == 0` is just keeping the behaviour + // before porting to cafebabe, is it a bug? + buffer.push_str("_unknown"); + } } } - - for _ in 0..levels { - buffer.push_str("_array"); - } + }; + for _ in 0..arg.dimensions { + buffer.push_str("_array"); } } - } - buffer + if rustify { + rustify_identifier(&buffer) + } else { + javaify_identifier(&buffer) + } + } } diff --git a/java-spaghetti-gen/src/main.rs b/java-spaghetti-gen/src/main.rs index 195af47..6e85e0f 100644 --- a/java-spaghetti-gen/src/main.rs +++ b/java-spaghetti-gen/src/main.rs @@ -1,9 +1,10 @@ -// must go first because macros. +// this must go first because of macros. mod util; mod config; mod emit_rust; mod identifiers; +mod parser_util; mod run; fn main() { @@ -18,7 +19,7 @@ mod entry { use crate::config; use crate::run::run; - /// Autogenerate jni-android-sys, glue code for access Android JVM APIs from Rust + /// Autogenerate glue code for access Android JVM APIs from Rust #[derive(Parser, Debug)] #[command(version, about)] struct Cli { @@ -49,7 +50,11 @@ mod entry { match cli.cmd { Cmd::Generate(cmd) => { let config_file = config::toml::File::from_directory(&cmd.directory).unwrap(); - run(config_file).unwrap(); + let mut config: config::runtime::Config = config_file.into(); + if cmd.verbose { + config.logging_verbose = true; + } + run(config).unwrap(); } } } diff --git a/java-spaghetti-gen/src/parser_util/class.rs b/java-spaghetti-gen/src/parser_util/class.rs new file mode 100644 index 0000000..308ffe5 --- /dev/null +++ b/java-spaghetti-gen/src/parser_util/class.rs @@ -0,0 +1,105 @@ +use std::borrow::Cow; + +use cafebabe::attributes::AttributeData; +pub use cafebabe::ClassAccessFlags; +pub use unsafe_class::Class; + +use super::Id; +mod unsafe_class { + use std::marker::PhantomPinned; + use std::pin::Pin; + + #[derive(Debug)] + pub struct Class { + #[allow(unused)] + raw_bytes: Pin, PhantomPinned)>>, + inner: cafebabe::ClassFile<'static>, + } + + impl Class { + pub fn read(raw_bytes: Vec) -> Result { + let pinned = Box::pin((raw_bytes, PhantomPinned)); + // SAFETY: `get<'a>(&'a self)` restricts the lifetime parameter of + // the returned referenced `ClassFile`. + let fake_static = unsafe { std::slice::from_raw_parts(pinned.0.as_ptr(), pinned.0.len()) }; + let inner = cafebabe::parse_class(fake_static)?; + Ok(Self { + raw_bytes: pinned, + inner, + }) + } + + // It is probably not possible to implement `Deref` safely. + pub fn get<'a>(&'a self) -> &'a cafebabe::ClassFile<'a> { + // SAFETY: casts `self.inner` into `cafebabe::ClassFile<'a>` forcefully. + // `cafebabe::parse_class` takes immutable &'a [u8], why is the returned + // `ClassFile<'a>` invariant over `'a`? + unsafe { &*(&raw const (self.inner)).cast() } + } + } +} + +impl Class { + fn flags(&self) -> ClassAccessFlags { + self.get().access_flags + } + + pub fn is_public(&self) -> bool { + self.flags().contains(ClassAccessFlags::PUBLIC) + } + pub fn is_final(&self) -> bool { + self.flags().contains(ClassAccessFlags::FINAL) + } + pub fn is_static(&self) -> bool { + (self.flags().bits() & 0x0008) != 0 + } + #[allow(unused)] + pub fn is_super(&self) -> bool { + self.flags().contains(ClassAccessFlags::SUPER) + } + pub fn is_interface(&self) -> bool { + self.flags().contains(ClassAccessFlags::INTERFACE) + } + #[allow(unused)] + pub fn is_abstract(&self) -> bool { + self.flags().contains(ClassAccessFlags::ABSTRACT) + } + #[allow(unused)] + pub fn is_synthetic(&self) -> bool { + self.flags().contains(ClassAccessFlags::SYNTHETIC) + } + #[allow(unused)] + pub fn is_annotation(&self) -> bool { + self.flags().contains(ClassAccessFlags::ANNOTATION) + } + pub fn is_enum(&self) -> bool { + self.flags().contains(ClassAccessFlags::ENUM) + } + + pub fn path(&self) -> Id<'_> { + Id(self.get().this_class.as_ref()) + } + + pub fn super_path(&self) -> Option> { + self.get().super_class.as_ref().map(|class| Id(class)) + } + + pub fn interfaces(&self) -> std::slice::Iter<'_, Cow<'_, str>> { + self.get().interfaces.iter() + } + + pub fn fields(&self) -> std::slice::Iter<'_, cafebabe::FieldInfo<'_>> { + self.get().fields.iter() + } + + pub fn methods(&self) -> std::slice::Iter<'_, cafebabe::MethodInfo<'_>> { + self.get().methods.iter() + } + + pub fn deprecated(&self) -> bool { + self.get() + .attributes + .iter() + .any(|attr| matches!(attr.data, AttributeData::Deprecated)) + } +} diff --git a/java-spaghetti-gen/src/parser_util/field.rs b/java-spaghetti-gen/src/parser_util/field.rs new file mode 100644 index 0000000..4965723 --- /dev/null +++ b/java-spaghetti-gen/src/parser_util/field.rs @@ -0,0 +1,138 @@ +use std::fmt::Write; + +use cafebabe::attributes::AttributeData; +use cafebabe::constant_pool::LiteralConstant; +use cafebabe::descriptors::{FieldDescriptor, FieldType}; +use cafebabe::FieldAccessFlags; + +use super::ClassName; + +#[derive(Clone, Copy, Debug)] +pub struct JavaField<'a> { + java: &'a cafebabe::FieldInfo<'a>, +} + +impl<'a> From<&'a cafebabe::FieldInfo<'a>> for JavaField<'a> { + fn from(value: &'a cafebabe::FieldInfo<'a>) -> Self { + Self { java: value } + } +} + +impl<'a> std::ops::Deref for JavaField<'a> { + type Target = cafebabe::FieldInfo<'a>; + fn deref(&self) -> &Self::Target { + self.java + } +} + +impl<'a> JavaField<'a> { + pub fn name<'s>(&'s self) -> &'a str { + self.java.name.as_ref() + } + + pub fn is_public(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::PUBLIC) + } + pub fn is_private(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::PRIVATE) + } + pub fn is_protected(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::PROTECTED) + } + pub fn is_static(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::STATIC) + } + pub fn is_final(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::FINAL) + } + pub fn is_volatile(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::VOLATILE) + } + #[allow(unused)] + pub fn is_transient(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::TRANSIENT) + } + #[allow(unused)] + pub fn is_synthetic(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::SYNTHETIC) + } + #[allow(unused)] + pub fn is_enum(&self) -> bool { + self.access_flags.contains(FieldAccessFlags::ENUM) + } + + pub fn access(&self) -> Option<&'static str> { + if self.is_private() { + Some("private") + } else if self.is_protected() { + Some("protected") + } else if self.is_public() { + Some("public") + } else { + None + } + } + + pub fn is_constant(&self) -> bool { + self.is_static() + && self.is_final() + && self + .attributes + .iter() + .any(|attr| matches!(&attr.data, AttributeData::ConstantValue(_))) + } + + pub fn constant<'s>(&'s self) -> Option> { + if !self.is_static() || !self.is_final() { + return None; + } + self.attributes.iter().find_map(|attr| { + if let AttributeData::ConstantValue(c) = &attr.data { + Some(c.clone()) + } else { + None + } + }) + } + + pub fn deprecated(&self) -> bool { + self.attributes + .iter() + .any(|attr| matches!(attr.data, AttributeData::Deprecated)) + } + + pub fn descriptor<'s>(&'s self) -> &'a FieldDescriptor<'a> { + &self.java.descriptor + } +} + +// XXX: cannot get the original string from `cafebabe::descriptors::FieldDescriptor`. +// +pub struct FieldSigWriter<'a>(pub &'a FieldDescriptor<'a>); + +impl std::fmt::Display for FieldSigWriter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let descriptor = self.0; + for _ in 0..descriptor.dimensions { + f.write_char('[')?; + } + if let FieldType::Object(class_name) = &descriptor.field_type { + f.write_char('L')?; + ClassName::from(class_name).fmt(f)?; + f.write_char(';') + } else { + let ch = match descriptor.field_type { + FieldType::Boolean => 'Z', + FieldType::Byte => 'B', + FieldType::Char => 'C', + FieldType::Short => 'S', + FieldType::Integer => 'I', + FieldType::Long => 'J', + FieldType::Float => 'F', + FieldType::Double => 'D', + _ => unreachable!(), + }; + f.write_char(ch) + } + } +} diff --git a/java-spaghetti-gen/src/parser_util/id.rs b/java-spaghetti-gen/src/parser_util/id.rs new file mode 100644 index 0000000..ef37a3d --- /dev/null +++ b/java-spaghetti-gen/src/parser_util/id.rs @@ -0,0 +1,233 @@ +// Migrated from . + +use std::fmt::Write; + +/// Owned Java class binary name (internal form). +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IdBuf(String); + +impl IdBuf { + pub fn new(s: String) -> Self { + Self(s) + } + pub fn as_str(&self) -> &str { + self.0.as_str() + } + pub fn as_id(&self) -> Id { + Id(self.0.as_str()) + } + #[allow(dead_code)] + pub fn iter(&self) -> IdIter { + IdIter::new(self.0.as_str()) + } +} + +// XXX: This should really be `#[repr(transparent)] pub struct Id(str);`... +// Also, patterns apparently can't handle Id::new(...) even when it's a const fn. + +/// Borrowed Java class binary name (internal form). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id<'a>(pub &'a str); + +impl<'a> Id<'a> { + pub fn as_str(&self) -> &'a str { + self.0 + } + pub fn iter(&self) -> IdIter<'a> { + IdIter::new(self.0) + } +} + +impl<'a> IntoIterator for Id<'a> { + type Item = IdPart<'a>; + type IntoIter = IdIter<'a>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum IdPart<'a> { + Namespace(&'a str), + ContainingClass(&'a str), + LeafClass(&'a str), +} + +/// Iterates through names of namespaces, superclasses and the "leaf" class +/// in the Java class binary name. +pub struct IdIter<'a> { + rest: &'a str, +} + +impl<'a> IdIter<'a> { + pub fn new(path: &'a str) -> Self { + IdIter { rest: path } + } +} + +impl<'a> Iterator for IdIter<'a> { + type Item = IdPart<'a>; + fn next(&mut self) -> Option { + if let Some(slash) = self.rest.find('/') { + let (namespace, rest) = self.rest.split_at(slash); + self.rest = &rest[1..]; + return Some(IdPart::Namespace(namespace)); + } + + if let Some(dollar) = self.rest.find('$') { + let (class, rest) = self.rest.split_at(dollar); + self.rest = &rest[1..]; + return Some(IdPart::ContainingClass(class)); + } + + if !self.rest.is_empty() { + let class = self.rest; + self.rest = ""; + return Some(IdPart::LeafClass(class)); + } + + None + } +} + +#[test] +fn id_iter_test() { + assert_eq!(Id("").iter().collect::>(), &[]); + + assert_eq!(Id("Bar").iter().collect::>(), &[IdPart::LeafClass("Bar"),]); + + assert_eq!( + Id("java/foo/Bar").iter().collect::>(), + &[ + IdPart::Namespace("java"), + IdPart::Namespace("foo"), + IdPart::LeafClass("Bar"), + ] + ); + + assert_eq!( + Id("java/foo/Bar$Inner").iter().collect::>(), + &[ + IdPart::Namespace("java"), + IdPart::Namespace("foo"), + IdPart::ContainingClass("Bar"), + IdPart::LeafClass("Inner"), + ] + ); + + assert_eq!( + Id("java/foo/Bar$Inner$MoreInner").iter().collect::>(), + &[ + IdPart::Namespace("java"), + IdPart::Namespace("foo"), + IdPart::ContainingClass("Bar"), + IdPart::ContainingClass("Inner"), + IdPart::LeafClass("MoreInner"), + ] + ); +} + +/// Newtype for `cafebabe::descriptors::ClassName`. +/// +/// XXX: cannot get the original string from `cafebabe::descriptors::ClassName`; the binary +/// name is split into `UnqualifiedSegment`s, not caring about Java-specific nested classes. +/// See . +#[derive(Clone, Copy, Debug)] +pub struct ClassName<'a> { + inner: &'a cafebabe::descriptors::ClassName<'a>, +} + +impl<'a> From<&'a cafebabe::descriptors::ClassName<'a>> for ClassName<'a> { + fn from(value: &'a cafebabe::descriptors::ClassName<'a>) -> Self { + Self { inner: value } + } +} + +impl<'a> std::ops::Deref for ClassName<'a> { + type Target = cafebabe::descriptors::ClassName<'a>; + fn deref(&self) -> &Self::Target { + self.inner + } +} + +impl std::fmt::Display for ClassName<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut segs = self.segments.iter(); + f.write_str(segs.next().unwrap().name.as_ref())?; + for seg in segs { + f.write_char('/')?; + f.write_str(seg.name.as_ref())?; + } + Ok(()) + } +} + +impl<'a> From<&ClassName<'a>> for IdBuf { + fn from(value: &ClassName<'a>) -> Self { + Self::new(value.to_string()) + } +} + +impl<'a> From<&cafebabe::descriptors::ClassName<'a>> for IdBuf { + fn from(value: &cafebabe::descriptors::ClassName<'a>) -> Self { + Self::new(ClassName::from(value).to_string()) + } +} + +impl<'a> ClassName<'a> { + pub fn iter<'s>(&'s self) -> ClassNameIter<'a> { + let segments = &self.inner.segments; + if segments.len() > 1 { + ClassNameIter::RestPath(segments) + } else { + let classes = segments.last().map(|s| s.name.as_ref()).unwrap_or(""); + ClassNameIter::RestClasses(IdIter::new(classes)) + } + } +} + +impl<'a> IntoIterator for ClassName<'a> { + type Item = IdPart<'a>; + type IntoIter = ClassNameIter<'a>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub enum ClassNameIter<'a> { + RestPath(&'a [cafebabe::descriptors::UnqualifiedSegment<'a>]), + RestClasses(IdIter<'a>), +} + +impl<'a> Iterator for ClassNameIter<'a> { + type Item = IdPart<'a>; + fn next(&mut self) -> Option { + match self { + Self::RestPath(segments) => { + // `segments.len() > 1` must be true at here + let namespace = IdPart::Namespace(&segments[0].name); + *self = if segments.len() - 1 > 1 { + Self::RestPath(&segments[1..]) + } else { + Self::RestClasses(IdIter::new(&segments.last().unwrap().name)) + }; + Some(namespace) + } + Self::RestClasses(ref mut id_iter) => id_iter.next(), + } + } +} + +#[allow(unused)] +pub trait IterableId<'a>: IntoIterator> + Copy { + fn is_string_class(self) -> bool { + let mut iter = self.into_iter(); + iter.next() == Some(IdPart::Namespace("java")) + && iter.next() == Some(IdPart::Namespace("lang")) + && iter.next() == Some(IdPart::LeafClass("String")) + && iter.next().is_none() + } +} + +impl<'a> IterableId<'a> for Id<'a> {} +impl<'a> IterableId<'a> for ClassName<'a> {} diff --git a/java-spaghetti-gen/src/parser_util/method.rs b/java-spaghetti-gen/src/parser_util/method.rs new file mode 100644 index 0000000..dd1b55e --- /dev/null +++ b/java-spaghetti-gen/src/parser_util/method.rs @@ -0,0 +1,125 @@ +use std::fmt::Write; + +use cafebabe::attributes::AttributeData; +use cafebabe::descriptors::{MethodDescriptor, ReturnDescriptor}; +use cafebabe::MethodAccessFlags; + +use super::FieldSigWriter; + +pub struct JavaMethod<'a> { + java: &'a cafebabe::MethodInfo<'a>, +} + +impl<'a> From<&'a cafebabe::MethodInfo<'a>> for JavaMethod<'a> { + fn from(value: &'a cafebabe::MethodInfo<'a>) -> Self { + Self { java: value } + } +} + +impl<'a> std::ops::Deref for JavaMethod<'a> { + type Target = cafebabe::MethodInfo<'a>; + fn deref(&self) -> &Self::Target { + self.java + } +} + +impl<'a> JavaMethod<'a> { + pub fn name<'s>(&'s self) -> &'a str { + self.java.name.as_ref() + } + + pub fn is_public(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::PUBLIC) + } + #[allow(unused)] + pub fn is_private(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::PRIVATE) + } + #[allow(unused)] + pub fn is_protected(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::PROTECTED) + } + pub fn is_static(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::STATIC) + } + #[allow(unused)] + pub fn is_final(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::FINAL) + } + #[allow(unused)] + pub fn is_synchronized(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::SYNCHRONIZED) + } + pub fn is_bridge(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::BRIDGE) + } + pub fn is_varargs(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::VARARGS) + } + #[allow(unused)] + pub fn is_native(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::NATIVE) + } + #[allow(unused)] + pub fn is_abstract(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::ABSTRACT) + } + #[allow(unused)] + pub fn is_strict(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::STRICT) + } + #[allow(unused)] + pub fn is_synthetic(&self) -> bool { + self.access_flags.contains(MethodAccessFlags::SYNTHETIC) + } + + pub fn is_constructor(&self) -> bool { + self.name() == "" + } + pub fn is_static_init(&self) -> bool { + self.name() == "" + } + + #[allow(unused)] + pub fn access(&self) -> Option<&'static str> { + if self.is_private() { + Some("private") + } else if self.is_protected() { + Some("protected") + } else if self.is_public() { + Some("public") + } else { + None + } + } + + pub fn deprecated(&self) -> bool { + self.attributes + .iter() + .any(|attr| matches!(attr.data, AttributeData::Deprecated)) + } + + pub fn descriptor<'s>(&'s self) -> &'a MethodDescriptor<'a> { + &self.java.descriptor + } +} + +// XXX: cannot get the original string from `cafebabe::descriptors::MethodDescriptor`. +// +pub struct MethodSigWriter<'a>(pub &'a MethodDescriptor<'a>); + +impl std::fmt::Display for MethodSigWriter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let descriptor = self.0; + f.write_char('(')?; + for arg in descriptor.parameters.iter() { + FieldSigWriter(arg).fmt(f)?; + } + f.write_char(')')?; + if let ReturnDescriptor::Return(desc) = &descriptor.return_type { + FieldSigWriter(desc).fmt(f) + } else { + f.write_char('V') + } + } +} diff --git a/java-spaghetti-gen/src/parser_util/mod.rs b/java-spaghetti-gen/src/parser_util/mod.rs new file mode 100644 index 0000000..d7d0222 --- /dev/null +++ b/java-spaghetti-gen/src/parser_util/mod.rs @@ -0,0 +1,9 @@ +mod class; +mod field; +mod id; +mod method; + +pub use class::Class; +pub use field::{FieldSigWriter, JavaField}; +pub use id::*; +pub use method::{JavaMethod, MethodSigWriter}; diff --git a/java-spaghetti-gen/src/run/run.rs b/java-spaghetti-gen/src/run/run.rs index a25e263..39536c2 100644 --- a/java-spaghetti-gen/src/run/run.rs +++ b/java-spaghetti-gen/src/run/run.rs @@ -1,11 +1,10 @@ use std::error::Error; use std::fs::File; -use std::io; +use std::io::{self, Read}; use std::path::Path; -use jreflection::Class; - use crate::config::runtime::Config; +use crate::parser_util::Class; use crate::{emit_rust, util}; /// The core function of this library: Generate Rust code to access Java APIs. @@ -28,6 +27,8 @@ pub fn run(config: impl Into) -> Result<(), Box> { } fn gather_file(context: &mut emit_rust::Context, path: &Path) -> Result<(), Box> { + let verbose = context.config.logging_verbose; + context .progress .lock() @@ -45,8 +46,7 @@ fn gather_file(context: &mut emit_rust::Context, path: &Path) -> Result<(), Box< match ext.to_string_lossy().to_ascii_lowercase().as_str() { "class" => { - let mut file = io::BufReader::new(File::open(path)?); - let class = Class::read(&mut file)?; + let class = Class::read(std::fs::read(path)?)?; context.add_struct(class)?; } "jar" => { @@ -58,12 +58,18 @@ fn gather_file(context: &mut emit_rust::Context, path: &Path) -> Result<(), Box< if !file.name().ends_with(".class") { continue; } - context - .progress - .lock() - .unwrap() - .update(format!(" reading {:3}/{}: {}...", i, n, file.name()).as_str()); - let class = Class::read(&mut file)?; + + if verbose { + context + .progress + .lock() + .unwrap() + .update(format!(" reading {:3}/{}: {}...", i, n, file.name()).as_str()); + } + + let mut buf = Vec::new(); + file.read_to_end(&mut buf)?; + let class = Class::read(buf)?; context.add_struct(class)?; } } diff --git a/java-spaghetti-gen/src/util/difference.rs b/java-spaghetti-gen/src/util/difference.rs index e95644c..2e4a307 100644 --- a/java-spaghetti-gen/src/util/difference.rs +++ b/java-spaghetti-gen/src/util/difference.rs @@ -1,8 +1,12 @@ use std::io::{self, *}; pub struct Difference { + // XXX: should they exist here? + #[allow(unused)] pub line_no: u32, + #[allow(unused)] pub original: String, + #[allow(unused)] pub rewrite: String, } diff --git a/java-spaghetti/src/env.rs b/java-spaghetti/src/env.rs index 84a178a..d06301a 100644 --- a/java-spaghetti/src/env.rs +++ b/java-spaghetti/src/env.rs @@ -2,6 +2,7 @@ use std::marker::PhantomData; use std::os::raw::c_char; use std::ptr::{self, null_mut}; use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::OnceLock; use jni_sys::*; @@ -147,9 +148,15 @@ impl<'env> Env<'env> { } unsafe fn exception_to_string(self, exception: jobject) -> String { - // use JNI FindClass to avoid infinte recursion. - let throwable_class = self.require_class_jni("java/lang/Throwable\0"); - let throwable_get_message = self.require_method(throwable_class, "getMessage\0", "()Ljava/lang/String;\0"); + static METHOD_GET_MESSAGE: OnceLock = OnceLock::new(); + let throwable_get_message = *METHOD_GET_MESSAGE.get_or_init(|| { + // use JNI FindClass to avoid infinte recursion. + let throwable_class = self.require_class_jni("java/lang/Throwable\0"); + let method = self.require_method(throwable_class, "getMessage\0", "()Ljava/lang/String;\0"); + ((**self.env).v1_2.DeleteLocalRef)(self.env, throwable_class); + method.addr() + }) as jmethodID; // it is a global ID + let message = ((**self.env).v1_2.CallObjectMethodA)(self.env, exception, throwable_get_message, ptr::null_mut()); let e2: *mut _jobject = ((**self.env).v1_2.ExceptionOccurred)(self.env); @@ -161,6 +168,7 @@ impl<'env> Env<'env> { StringChars::from_env_jstring(self, message).to_string_lossy() } + /// Note: the returned `jclass` is actually a new local reference of the class object. pub unsafe fn require_class(self, class: &str) -> jclass { // First try with JNI FindClass. debug_assert!(class.ends_with('\0')); @@ -183,10 +191,15 @@ impl<'env> Env<'env> { .collect::>(); let string = unsafe { self.new_string(chars.as_ptr(), chars.len() as jsize) }; - // We still use JNI FindClass for this, to avoid a chicken-and-egg situation. - // If the system class loader cannot find java.lang.ClassLoader, things are pretty broken! - let cl_class = self.require_class_jni("java/lang/ClassLoader\0"); - let cl_method = self.require_method(cl_class, "loadClass\0", "(Ljava/lang/String;)Ljava/lang/Class;\0"); + static CL_METHOD: OnceLock = OnceLock::new(); + let cl_method = *CL_METHOD.get_or_init(|| { + // We still use JNI FindClass for this, to avoid a chicken-and-egg situation. + // If the system class loader cannot find java.lang.ClassLoader, things are pretty broken! + let cl_class = self.require_class_jni("java/lang/ClassLoader\0"); + let cl_method = self.require_method(cl_class, "loadClass\0", "(Ljava/lang/String;)Ljava/lang/Class;\0"); + ((**self.env).v1_2.DeleteLocalRef)(self.env, cl_class); + cl_method.addr() + }) as jmethodID; // it is a global ID let args = [jvalue { l: string }]; let result: *mut _jobject = @@ -275,6 +288,7 @@ impl<'env> Env<'env> { } // Multi-Query Methods + // XXX: Remove these unused functions. pub unsafe fn require_class_method(self, class: &str, method: &str, descriptor: &str) -> (jclass, jmethodID) { let class = self.require_class(class); diff --git a/java-spaghetti/src/id_cache.rs b/java-spaghetti/src/id_cache.rs new file mode 100644 index 0000000..8253f23 --- /dev/null +++ b/java-spaghetti/src/id_cache.rs @@ -0,0 +1,62 @@ +//! New types for `jfieldID` and `jmethodID` that implement `Send` and `Sync`. +//! +//! Inspired by: . +//! +//! According to the JNI spec field IDs may be invalidated when the corresponding class is unloaded: +//! +//! +//! You should generally not be interacting with these types directly, but it must be public for codegen. + +use crate::sys::{jfieldID, jmethodID}; + +#[doc(hidden)] +#[repr(transparent)] +pub struct JFieldID { + internal: jfieldID, +} + +// Field IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JFieldID {} +unsafe impl Sync for JFieldID {} + +impl JFieldID { + /// Creates a [`JFieldID`] that wraps the given `raw` [`jfieldID`]. + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID. + pub unsafe fn from_raw(raw: jfieldID) -> Self { + debug_assert!(!raw.is_null(), "from_raw fieldID argument"); + Self { internal: raw } + } + + pub fn as_raw(&self) -> jfieldID { + self.internal + } +} + +#[doc(hidden)] +#[repr(transparent)] +pub struct JMethodID { + internal: jmethodID, +} + +// Method IDs are valid across threads (not tied to a JNIEnv) +unsafe impl Send for JMethodID {} +unsafe impl Sync for JMethodID {} + +impl JMethodID { + /// Creates a [`JMethodID`] that wraps the given `raw` [`jmethodID`]. + /// + /// # Safety + /// + /// Expects a valid, non-`null` ID. + pub unsafe fn from_raw(raw: jmethodID) -> Self { + debug_assert!(!raw.is_null(), "from_raw methodID argument"); + Self { internal: raw } + } + + pub fn as_raw(&self) -> jmethodID { + self.internal + } +} diff --git a/java-spaghetti/src/lib.rs b/java-spaghetti/src/lib.rs index 52c90c9..39ca593 100644 --- a/java-spaghetti/src/lib.rs +++ b/java-spaghetti/src/lib.rs @@ -29,6 +29,7 @@ mod refs { mod array; mod as_jvalue; mod env; +mod id_cache; mod jni_type; mod string_chars; mod vm; @@ -36,6 +37,7 @@ mod vm; pub use array::*; pub use as_jvalue::*; pub use env::*; +pub use id_cache::*; pub use jni_type::JniType; pub use refs::*; pub use string_chars::*; diff --git a/java-spaghetti/src/vm.rs b/java-spaghetti/src/vm.rs index 77c7869..931d145 100644 --- a/java-spaghetti/src/vm.rs +++ b/java-spaghetti/src/vm.rs @@ -29,6 +29,12 @@ impl VM { self.0 } + /// Constructs `VM` with a valid `jni_sys::JavaVM` raw pointer. + /// + /// # Safety + /// + /// - Make sure the corresponding JVM will keep alive within the lifetime of current native library or application. + /// - Do not use any class redefinition feature, which may break the validity of method/field IDs to be cached. pub unsafe fn from_raw(vm: *mut JavaVM) -> Self { Self(vm) }