use std::path::{Component, Path, PathBuf};
use boltffi_bindgen::render::java::{JavaEmitter, JavaOptions, JavaVersion};
use boltffi_bindgen::render::jni::{JniEmitter, JniLowerer, JvmBindingStyle};
use crate::cli::{CliError, Result};
use crate::commands::generate::generator::{
GenerateRequest, LanguageGenerator, ScanPointerWidth, SourceCrate,
};
use crate::config::Target;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum JavaGenerationMode {
Jvm,
Android,
}
impl JavaGenerationMode {
fn scan_pointer_width(self) -> ScanPointerWidth {
match self {
Self::Jvm => ScanPointerWidth::Host,
Self::Android => ScanPointerWidth::Flexible,
}
}
}
pub struct JavaGenerator;
impl JavaGenerator {
pub fn generate_from_source_directory(
config: &crate::config::Config,
output_override: Option<PathBuf>,
source_directory: &Path,
crate_name: &str,
) -> Result<()> {
let request = GenerateRequest::new(
config,
output_override,
SourceCrate::new(source_directory, crate_name),
);
Self::generate(&request)
}
fn normalized_output_path(path: &Path) -> PathBuf {
let absolute_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()
.map(|current_directory| current_directory.join(path))
.unwrap_or_else(|_| path.to_path_buf())
};
absolute_path
.components()
.fold(PathBuf::new(), |mut normalized_path, component| {
match component {
Component::CurDir => {}
Component::ParentDir => {
if normalized_path.file_name().is_some() {
normalized_path.pop();
}
}
_ => normalized_path.push(component.as_os_str()),
}
normalized_path
})
}
fn generation_mode(
request: &GenerateRequest<'_>,
output_directory: &Path,
) -> JavaGenerationMode {
let jvm_enabled = request.config().is_java_jvm_enabled();
let android_enabled = request.config().is_java_android_enabled();
let normalized_output_directory = Self::normalized_output_path(output_directory);
let normalized_jvm_output =
Self::normalized_output_path(&request.config().java_jvm_output());
let normalized_android_output =
Self::normalized_output_path(&request.config().java_android_output());
if normalized_output_directory == normalized_jvm_output {
return JavaGenerationMode::Jvm;
}
if normalized_output_directory == normalized_android_output {
return JavaGenerationMode::Android;
}
match (jvm_enabled, android_enabled) {
(true, false) | (true, true) => JavaGenerationMode::Jvm,
(false, true) => JavaGenerationMode::Android,
(false, false) => JavaGenerationMode::Jvm,
}
}
}
impl LanguageGenerator for JavaGenerator {
const TARGET: Target = Target::Java;
fn generate(request: &GenerateRequest<'_>) -> Result<()> {
let jvm_enabled = request.config().is_java_jvm_enabled();
let android_enabled = request.config().is_java_android_enabled();
if !jvm_enabled && !android_enabled {
return Err(CliError::CommandFailed {
command: "both targets.java.jvm.enabled and targets.java.android.enabled are false"
.to_string(),
status: None,
});
}
let package_name = request.config().java_package();
let package_path = package_name.replace('.', "/");
let module_name = request.config().java_module_name();
let output_directory = request
.output_override()
.map(ToOwned::to_owned)
.unwrap_or_else(|| {
if jvm_enabled {
request.config().java_jvm_output()
} else {
request.config().java_android_output()
}
});
let java_directory = output_directory.join(&package_path);
let jni_directory = output_directory.join("jni");
request.ensure_output_directory(&java_directory)?;
request.ensure_output_directory(&jni_directory)?;
let generation_mode = Self::generation_mode(request, &output_directory);
let lowered_crate = request.lowered_crate(generation_mode.scan_pointer_width())?;
let library_name = match generation_mode {
JavaGenerationMode::Jvm => {
boltffi_bindgen::library_name(request.source_crate().crate_name())
}
JavaGenerationMode::Android => {
boltffi_bindgen::load_library_name(request.source_crate().crate_name())
}
};
let java_output = JavaEmitter::emit(
&lowered_crate.ffi_contract,
&lowered_crate.abi_contract,
package_name.clone(),
module_name.clone(),
JavaOptions {
library_name: Some(library_name),
min_java_version: JavaVersion(request.config().java_min_version().unwrap_or(8)),
desktop_loader: matches!(generation_mode, JavaGenerationMode::Jvm),
},
);
java_output.files.iter().try_for_each(|java_file| {
request.write_output(
&java_directory.join(&java_file.file_name),
&java_file.source,
)
})?;
let jni_module = JniLowerer::new(
&lowered_crate.ffi_contract,
&lowered_crate.abi_contract,
package_name,
module_name,
)
.with_jvm_binding_style(JvmBindingStyle::Java)
.lower();
let jni_source = JniEmitter::emit(&jni_module);
let jni_path = jni_directory.join("jni_glue.c");
request.write_output(&jni_path, jni_source)
}
}