mod tmp_file;
mod objects;
use objects::{IntoJObject, Pair};
pub use objects::{RELATIVE_AREA_CALCULATION_MODE, ABSOLUTE_AREA_CALCULATION_MODE, Rectangle, OutputFormat, ExtractionMethod};
use anyhow::Result;
use jni::{AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, objects::{JObject, JValue}, errors::Error as JError};
pub use jni;
use tmp_file::TempFile;
use std::result::Result as StdResult;
use std::ops::Deref;
use std::path::Path;
pub type JResult<T> = StdResult<T, JError>;
pub struct TabulaVM(JavaVM);
impl <'env> TabulaVM {
pub fn new(libpath: &str, debug: bool) -> Result<Self> {
let opt = format!("-Djava.class.path={}", libpath);
let mut jvm_args = InitArgsBuilder::new()
.version(JNIVersion::V8)
.option(&opt);
if debug {
jvm_args = jvm_args.option("-Xcheck:jni");
}
let jvm_args = jvm_args.build()?;
Ok(Self(JavaVM::new(jvm_args)?))
}
pub fn attach(&'env self) -> Result<TabulaEnv<'env>> {
Ok(TabulaEnv(self.0.attach_current_thread()?))
}
}
pub struct TabulaEnv<'env>(AttachGuard<'env>);
impl <'env> TabulaEnv<'env> {
fn get_pages_jarray(&self, pages: &[i32]) -> JResult<*mut jni::sys::_jobject> {
let null = JObject::null();
let array = self.new_object_array(pages.len() as i32, "java/lang/Integer", null)?;
for (i, pg) in pages.iter().enumerate() {
self.set_object_array_element(array, i as i32, pg.get_jobject(self)?)?;
}
Ok(array)
}
fn get_page_areas_jarray(&self, page_areas: &[(i32, Rectangle)]) -> JResult<*mut jni::sys::_jobject> {
let null = JObject::null();
let array = self.new_object_array(page_areas.len() as i32, "technology/tabula/Pair", null)?;
for (i, (mode, rect)) in page_areas.iter().enumerate() {
let pga = Pair::new(*mode, *rect);
self.set_object_array_element(array, i as i32, pga.get_jobject(self)?)?;
}
Ok(array)
}
#[allow(clippy::too_many_arguments)]
pub fn configure_tabula(&self,
page_areas: Option<&[(i32, Rectangle)]>,
pages: Option<&[i32]>,
output_format: OutputFormat,
guess: bool,
method: ExtractionMethod,
use_returns: bool,
password: Option<&str>
) -> JResult<Tabula> {
let areas = if let Some(page_areas) = page_areas {
JValue::from(self.get_page_areas_jarray(page_areas)?)
} else {
JValue::from(JObject::null())
};
let pages = if let Some(pages) = pages {
JValue::from(self.get_pages_jarray(pages)?)
} else {
JValue::from(JObject::null())
};
let password = password
.and_then(|pw| self.new_string(pw).ok())
.map(JValue::from)
.unwrap_or(JValue::from(JObject::null()));
let tabula = self.new_object("technology/tabula/CommandLineApp", "([Ltechnology/tabula/Pair;[Ljava/lang/Integer;Ltechnology/tabula/CommandLineApp$OutputFormat;ZLtechnology/tabula/CommandLineApp$ExtractionMethod;ZLjava/lang/String;)V", &[
areas,
pages,
JValue::from(output_format.get_jobject(self)?),
JValue::from(guess),
JValue::from(method.get_jobject(self)?),
JValue::from(use_returns),
password
])?;
Ok(Tabula {
env: self,
inner: tabula
})
}
}
impl <'env> Deref for TabulaEnv<'env> {
type Target = JNIEnv<'env>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Tabula<'env> {
env: &'env TabulaEnv<'env>,
inner: JObject<'env>
}
impl Tabula<'_> {
pub fn parse_document(&self, path: &Path, descriptor_name: &str) -> Result<std::fs::File> {
let output = tmp_file::new(descriptor_name)?;
let output_path = output.get_path();
self.parse_document_into(path, &output_path)?;
let file = output.into_file();
Ok(file)
}
pub fn parse_document_into(&self, path: &Path, output: &Path) -> Result<()> {
let file = path.get_jobject(self.env)?;
let outfile = output.get_jobject(self.env)?;
self.env.call_method(*self.deref(), "extractFileInto", "(Ljava/io/File;Ljava/io/File;)V", &[
JValue::Object(file),
JValue::Object(outfile)
])?;
Ok(())
}
}
impl <'env> Deref for Tabula<'env> {
type Target = JObject<'env>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[cfg(test)]
mod tests;