mod jni;
mod types;
use crate::common::{
self, append_output, check_no_mangle, is_array_arg, is_array_arg_barefn, is_user_data_arg,
is_user_data_arg_barefn, parse_attr, retrieve_docstring, take_out_pat,
transform_fnarg_to_argcap, FilterMode, Outputs,
};
use crate::java::types::{callback_name, java_type_to_str, rust_to_java, struct_to_java_classname};
use crate::struct_field::{transform_struct_fields, StructField};
use crate::{Error, Level};
use ::jni::signature::JavaType;
use ::rustfmt::{self, format_input};
use inflector::Inflector;
use quote::*;
use std::collections::{BTreeSet, HashMap, HashSet};
use unwrap::unwrap;
pub struct LangJava {
context: Context,
filter: HashSet<String>,
filter_mode: FilterMode,
}
pub struct Context {
lib_name: String,
namespace: String,
namespace_model: String,
type_map: HashMap<&'static str, JavaType>,
generated_jni_cbs: BTreeSet<String>,
}
impl Default for Context {
fn default() -> Self {
Self {
lib_name: "safe".to_string(),
namespace: "net.maidsafe.dummy".to_string(),
namespace_model: "net.maidsafe.dummy".to_string(),
type_map: Default::default(),
generated_jni_cbs: Default::default(),
}
}
}
impl LangJava {
pub fn new(type_map: HashMap<&'static str, JavaType>) -> Self {
LangJava {
filter: Default::default(),
filter_mode: FilterMode::Blacklist,
context: Context {
type_map,
lib_name: "backend".to_owned(),
namespace: "net.maidsafe.bindings".to_owned(),
namespace_model: "net.maidsafe.model".to_owned(),
generated_jni_cbs: BTreeSet::new(),
},
}
}
pub fn set_lib_name<T: Into<String>>(&mut self, name: T) {
self.context.lib_name = name.into();
}
pub fn set_namespace<T: Into<String>>(&mut self, namespace: T) {
self.context.namespace = namespace.into();
}
pub fn set_model_namespace<T: Into<String>>(&mut self, namespace: T) {
self.context.namespace_model = namespace.into();
}
fn format_jni_output(&self, input: &mut String) {
let mut output: Vec<u8> = Vec::with_capacity(input.len() * 2);
let mut cfg = rustfmt::config::Config::default();
cfg.set().write_mode(rustfmt::config::WriteMode::Plain);
let (_summary, _, report) =
format_input(rustfmt::Input::Text(input.clone()), &cfg, Some(&mut output)).unwrap();
*input = String::from_utf8(output)
.unwrap_or_else(|_| panic!("Invalid Rustfmt output found: {}", report));
}
fn format_native_functions(&self, funcs: &mut String) {
let lines = funcs.lines().fold(String::new(), |mut output, line| {
output.push_str(&format!("\t{}\n", line));
output
});
*funcs = format!(
"package {namespace};\n\n
public class NativeBindings {{\n
{lines}\n
}}",
namespace = self.context.namespace,
lines = lines
);
}
pub fn filter<T: Into<String>>(&mut self, ident: T) {
let _ = self.filter.insert(ident.into());
}
pub fn reset_filter(&mut self, filter_mode: FilterMode) {
self.filter.clear();
self.filter_mode = filter_mode;
}
fn is_ignored(&self, ident: &str) -> bool {
match self.filter_mode {
FilterMode::Blacklist => self.filter.contains(ident),
FilterMode::Whitelist => !self.filter.contains(ident),
}
}
}
impl common::Lang for LangJava {
fn parse_const(
&mut self,
_item: &syn::ItemConst,
_module: &[String],
_outputs: &mut Outputs,
) -> Result<(), Error> {
Ok(())
}
fn parse_ty(
&mut self,
_item: &syn::ItemType,
_module: &[String],
_outputs: &mut Outputs,
) -> Result<(), Error> {
Ok(())
}
fn parse_enum(
&mut self,
_item: &syn::ItemEnum,
_module: &[String],
_outputs: &mut Outputs,
) -> Result<(), Error> {
Ok(())
}
fn parse_fn(
&mut self,
item: &syn::ItemFn,
_module: &[String],
outputs: &mut Outputs,
) -> Result<(), Error> {
let ident = &item.ident;
let name = format!("{}", quote!(#ident));
if self.is_ignored(name.as_str()) {
return Ok(());
}
let (no_mangle, docs) = parse_attr(&item.attrs[..], check_no_mangle, |attr| {
retrieve_docstring(attr, "")
});
if !no_mangle {
return Ok(());
}
if !common::is_extern(unwrap!(item.to_owned().abi)) {
return Ok(());
}
if !item.decl.generics.params.is_empty() {
return Err(Error {
level: Level::Error,
span: None,
message: "cheddar cannot handle parameterized extern functions".into(),
});
}
transform_native_fn(
*item.to_owned().decl,
&item.attrs[..],
&docs,
&name,
outputs,
&mut self.context,
)?;
Ok(())
}
fn parse_struct(
&mut self,
item: &syn::ItemStruct,
_module: &[String],
outputs: &mut Outputs,
) -> Result<(), Error> {
let name = item.ident.to_string();
if self.is_ignored(&name) {
return Ok(());
}
let (repr_c, docs) = parse_attr(&item.attrs, common::check_repr_c, |attr| {
retrieve_docstring(attr, "")
});
if !repr_c {
return Ok(());
}
let mut buffer = String::new();
buffer.push_str(&format!("package {};\n\n", self.context.namespace));
buffer.push_str(&docs);
let orig_name = item.ident.to_owned().to_string();
let name = struct_to_java_classname(&*orig_name);
buffer.push_str(&format!("public class {}", name));
if !item.generics.params.is_empty() {
return Err(Error {
level: Level::Error,
span: None,
message: "cheddar cannot handle parameterized `#[repr(C)]` structs".into(),
});
}
let mut vec = vec![];
for x in item.fields.iter() {
vec.push(x.clone());
}
let struct_fields = transform_struct_fields(vec.as_slice());
let fields = transform_struct_into_class_fields(&struct_fields, &self.context)?;
buffer.push_str(" {\n");
buffer.push_str(&generate_class_fields(&fields)?);
buffer.push_str("\n");
buffer.push_str(&generate_default_constructor(&name, &fields)?);
buffer.push_str(&generate_parametrised_constructor(&name, &fields)?);
buffer.push_str(&generate_getters_setters(&fields)?);
buffer.push_str("}");
let jni = jni::generate_struct(&struct_fields, &orig_name, &name, &self.context);
append_output(jni, "jni.rs", outputs);
buffer.push_str("\n\n");
outputs.insert(format!("{}.java", name), buffer);
Ok(())
}
fn finalise_output(&mut self, outputs: &mut Outputs) -> Result<(), Error> {
match outputs.get_mut("jni.rs") {
Some(input) => {
self.format_jni_output(input);
}
None => {
return Err(Error {
level: Level::Error,
span: None,
message: "no jni bindings generated?".to_owned(),
});
}
}
match outputs.get_mut("NativeBindings.java") {
Some(input) => {
self.format_native_functions(input);
Ok(())
}
None => Err(Error {
level: Level::Error,
span: None,
message: "no native bindings generated?".to_owned(),
}),
}
}
}
struct JavaClassField {
name: String,
ty: JavaType,
ty_str: String,
}
fn transform_struct_into_class_fields(
fields: &[StructField],
context: &Context,
) -> Result<Vec<JavaClassField>, Error> {
let mut class_fields = Vec::new();
for field in fields {
let name = field.name().to_camel_case();
let struct_field = field.struct_field().clone();
let mut ty = rust_to_java(&struct_field.ty, context)?;
if let StructField::Array { .. } = *field {
ty = JavaType::Array(Box::new(ty));
}
let ty_str = java_type_to_str(&ty)?;
class_fields.push(JavaClassField { name, ty, ty_str });
}
Ok(class_fields)
}
fn generate_getters_setters(fields: &[JavaClassField]) -> Result<String, Error> {
let mut buffer = String::new();
for field in fields {
buffer.push_str(&format!(
"\tpublic {ty} get{capitalized}() {{\n\t\treturn {name};\n\t}}\n\n",
ty = field.ty_str,
name = field.name,
capitalized = field.name.to_class_case(),
));
buffer.push_str(&format!(
"\tpublic void set{capitalized}(final {ty} val) {{\n\t\tthis.{name} \
= val;\n\t}}\n\n",
ty = field.ty_str,
name = field.name,
capitalized = field.name.to_class_case(),
));
}
Ok(buffer)
}
fn generate_class_fields(fields: &[JavaClassField]) -> Result<String, Error> {
let mut buffer = String::new();
for field in fields {
buffer.push_str(&format!("\tprivate {} {};\n", field.ty_str, field.name));
}
Ok(buffer)
}
fn generate_default_constructor(
class_name: &str,
fields: &[JavaClassField],
) -> Result<String, Error> {
let mut default_obj_fields = Vec::new();
for field in fields {
match field.ty {
JavaType::Array(..) => {
default_obj_fields.push(format!(
"\t\tthis.{name} = new {ty} {{}};",
name = field.name,
ty = field.ty_str
));
}
JavaType::Object(ref obj) => {
default_obj_fields.push(format!(
"\t\tthis.{name} = new {obj}();",
name = field.name,
obj = obj
));
}
_ => (),
}
}
Ok(format!(
"\tpublic {name}() {{\n{default_obj_fields}\n\t}}\n",
name = class_name,
default_obj_fields = default_obj_fields.join("\n")
))
}
fn generate_parametrised_constructor(
class_name: &str,
fields: &[JavaClassField],
) -> Result<String, Error> {
let mut constructor_fields = Vec::new();
let mut constructor_assignments = Vec::new();
for field in fields {
constructor_fields.push(format!("{} {}", field.ty_str, field.name));
constructor_assignments.push(format!("\t\tthis.{name} = {name};", name = field.name));
}
Ok(format!(
"\tpublic {name}({constructor_fields}) {{\n{constructor_assignments}\n\t}}\n",
name = class_name,
constructor_fields = constructor_fields.join(", "),
constructor_assignments = constructor_assignments.join("\n")
))
}
pub fn transform_native_fn(
fn_decl: syn::FnDecl,
attrs: &[syn::Attribute],
docs: &str,
name: &str,
outputs: &mut Outputs,
context: &mut Context,
) -> Result<(), Error> {
let mut args_str = Vec::new();
let mut fn_args = fn_decl
.inputs
.iter()
.filter(|arg| !is_user_data_arg(&unwrap!(transform_fnarg_to_argcap(*arg))))
.peekable();
while let Some(arg) = fn_args.next() {
let pat = take_out_pat(&unwrap!(transform_fnarg_to_argcap(arg)).pat);
let arg_name = unwrap!(pat).ident.to_string();
let mut java_type = rust_to_java(&unwrap!(transform_fnarg_to_argcap(arg)).ty, context)?;
let next_arg: Option<&syn::ArgCaptured> = if fn_args.peek().is_some() {
Some(unwrap!(transform_fnarg_to_argcap(unwrap!(fn_args.peek()))))
} else {
None
};
if is_array_arg(unwrap!(transform_fnarg_to_argcap(arg)), next_arg) {
java_type = JavaType::Array(Box::new(java_type));
fn_args.next();
}
let java_type = java_type_to_str(&java_type)?;
args_str.push(format!("{} {}", java_type, arg_name.to_camel_case()));
let argcap = unwrap!(transform_fnarg_to_argcap(arg));
if let syn::Type::BareFn(ref bare_fn) = argcap.ty {
let mut vec = vec![];
for input in bare_fn.inputs.to_owned() {
vec.push(input);
}
let cb_class = callback_name(&vec.as_slice(), context)?;
let cb_file = format!("{}.java", cb_class);
if outputs.get(&cb_file).is_none() {
eprintln!("Generating CB {}", cb_class);
let cb_output = transform_callback(&argcap.ty, &cb_class, context)?;
let _ = outputs.insert(cb_file, cb_output);
let jni_cb_name = format!("call_{}", cb_class);
if !context.generated_jni_cbs.contains(&jni_cb_name) {
let mut jni = jni::generate_jni_callback(bare_fn, &jni_cb_name, context);
jni.push_str("\n");
append_output(jni, "jni.rs", outputs);
context.generated_jni_cbs.insert(jni_cb_name);
}
}
}
}
let output_type = &fn_decl.output;
let return_type = match &*output_type {
syn::ReturnType::Type(_, ref ty) if check_type_never(&*ty) => {
return Err(Error {
level: Level::Error,
span: None,
message: "panics across a C boundary are naughty!".into(),
});
}
syn::ReturnType::Default => String::from("public static native void"),
syn::ReturnType::Type(_, ref ty) => {
java_type_to_str(&unwrap!(rust_to_java(&*ty, context)))?
}
};
let java_name = name.to_camel_case();
let func_decl = format!(
"{} {}({})",
return_type,
&java_name,
args_str.as_slice().join(", ")
);
let mut buffer = String::new();
buffer.push_str("/**\n");
buffer.push_str(&docs.replace("///", " *"));
buffer.push_str(" */\n");
buffer.push_str(&func_decl);
buffer.push_str(";\n\n");
append_output(buffer, "NativeBindings.java", outputs);
let mut fn_attrs = String::new();
for attr in attrs {
if attr.into_token_stream().to_owned().to_string() == "cfg" {
fn_attrs.push_str(&attr.into_token_stream().to_owned().to_string());
}
}
let args: Vec<_> = fn_decl
.inputs
.iter()
.map(|arg| {
unwrap!(transform_fnarg_to_argcap(arg))
.into_token_stream()
.to_string()
})
.collect();
let fn_declaration = format!("fn {}({})", name, args.join(", "));
let mut jni = format!(
"\n{attrs}#[link(name = \"{libname}\")]\nextern {{ {fndecl}; }}\n",
attrs = fn_attrs,
libname = context.lib_name,
fndecl = fn_declaration,
);
let vec: Vec<_> = fn_decl.inputs.iter().cloned().collect();
jni.push_str(&jni::generate_jni_function(
&vec, attrs, name, &java_name, context, outputs,
));
jni.push_str("\n");
append_output(jni, "jni.rs", outputs);
Ok(())
}
fn check_type_never(ty: &syn::Type) -> bool {
matches!(ty, syn::Type::Never(ref _never))
}
pub fn transform_callback<S: AsRef<str>>(
ty: &syn::Type,
class_name: S,
context: &Context,
) -> Result<String, Error> {
match ty {
syn::Type::BareFn(ref bare_fn) => Ok(format!(
"package {namespace};\n\n\
public interface {name} {{\n\
\tpublic void call({types});\n}}\n",
namespace = context.namespace_model,
name = class_name.as_ref(),
types = callback_to_java(bare_fn, context)?,
)),
_ => Err(Error {
level: Level::Error,
span: None,
message: "Invalid callback type".into(),
}),
}
}
fn callback_to_java(fn_ty: &syn::TypeBareFn, context: &Context) -> Result<String, Error> {
match unwrap!(unwrap!(fn_ty.to_owned().abi).name).value().as_str() {
"C" | "Cdecl" | "Stdcall" | "Fastcall" | "System" => {}
_ => {
return Err(Error {
level: Level::Error,
span: None,
message: "callbacks that don't have C ABI are not supported".into(),
});
}
}
if fn_ty.to_owned().lifetimes.is_some() {
return Err(Error {
level: Level::Error,
span: None,
message: "cannot handle lifetimes".into(),
});
}
let mut args = Vec::new();
let mut args_iter = fn_ty
.inputs
.iter()
.filter(|arg| !is_user_data_arg_barefn(arg))
.peekable();
while let Some(arg) = args_iter.next() {
let arg_name = unwrap!(arg.to_owned().name)
.0
.into_token_stream()
.to_string();
let mut java_type = rust_to_java(&arg.ty, context)?;
if is_array_arg_barefn(arg, args_iter.peek().cloned()) {
java_type = JavaType::Array(Box::new(java_type));
args_iter.next();
}
let java_type = java_type_to_str(&java_type)?;
args.push(format!("{} {}", java_type, arg_name.to_camel_case()));
}
Ok(args.join(", "))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cb_names() {
fn get_inputs(source: &str) -> Vec<syn::BareFnArg> {
let item: syn::TypeBareFn = unwrap!(syn::parse_str(source));
item.inputs.into_iter().collect()
}
let context = Context {
type_map: HashMap::new(),
lib_name: "backend".to_owned(),
namespace: "net.maidsafe.bindings".to_owned(),
namespace_model: "net.maidsafe.model".to_owned(),
generated_jni_cbs: BTreeSet::new(),
};
let inputs = get_inputs("fn ()");
assert_eq!("CallbackVoid", unwrap!(callback_name(&inputs, &context)));
let inputs = get_inputs("fn (user_data: *mut c_void, result: *const FfiResult)");
assert_eq!("CallbackResult", unwrap!(callback_name(&inputs, &context)));
let inputs =
get_inputs("fn (user_data: *mut c_void, result: *const FfiResult, b: u64, c: u32)");
assert_eq!(
"CallbackResultLongInt",
unwrap!(callback_name(&inputs, &context))
);
let inputs =
get_inputs("fn (user_data: *mut c_void, result: *const FfiResult, b: *const MyStruct)");
assert_eq!(
"CallbackResultMyStruct",
unwrap!(callback_name(&inputs, &context))
);
let inputs =
get_inputs("fn (user_data: *mut c_void, result: *const FfiResult, b: *const c_char)");
assert_eq!(
"CallbackResultString",
unwrap!(callback_name(&inputs, &context))
);
let inputs = get_inputs(
"fn (user_data: *mut c_void, result: *const FfiResult, a: *const Foo, a_len: usize)",
);
assert_eq!(
"CallbackResultFooArrayLen",
unwrap!(callback_name(&inputs, &context))
);
}
}