java_bindgen/
exception.rs

1use std::{backtrace::Backtrace, fmt::Debug, rc::Rc};
2
3use crate::prelude::IntoJavaType;
4
5#[derive(Debug, Clone)]
6pub enum JExceptionClass {
7    RuntimeException,
8    ArithmeticException,
9    ArrayIndexOutOfBoundsException,
10    ArrayStoreException,
11    ClassCastException,
12    IllegalArgumentException,
13    IllegalMonitorStateException,
14    IllegalStateException,
15    IllegalThreadStateException,
16    IndexOutOfBoundsException,
17    NegativeArraySizeException,
18    NullPointerException,
19    NumberFormatException,
20    SecurityException,
21    StringIndexOutOfBounds,
22    UnsupportedOperationException,
23    ClassNotFoundException,
24    CloneNotSupportedException,
25    IllegalAccessException,
26    InstantiationException,
27    InterruptedException,
28    NoSuchFieldException,
29    NoSuchMethodException,
30}
31
32impl JExceptionClass {
33    pub fn get_class_path(&self) -> String {
34        format!("java/lang/{:?}", self)
35    }
36}
37
38// Convert JNI Error to JavaException (class)
39impl From<&jni::errors::Error> for JExceptionClass {
40    fn from(value: &jni::errors::Error) -> Self {
41        match value {
42            jni::errors::Error::WrongJValueType(_, _) => JExceptionClass::ClassCastException,
43            jni::errors::Error::InvalidCtorReturn => JExceptionClass::IllegalArgumentException,
44            jni::errors::Error::InvalidArgList(_) => JExceptionClass::IllegalArgumentException,
45            jni::errors::Error::MethodNotFound { name: _, sig: _ } => {
46                JExceptionClass::NoSuchMethodException
47            }
48            jni::errors::Error::FieldNotFound { name: _, sig: _ } => {
49                JExceptionClass::NoSuchFieldException
50            }
51            jni::errors::Error::JavaException => JExceptionClass::RuntimeException,
52            jni::errors::Error::JNIEnvMethodNotFound(_) => JExceptionClass::NoSuchMethodException,
53            jni::errors::Error::NullPtr(_) => JExceptionClass::NullPointerException,
54            jni::errors::Error::NullDeref(_) => JExceptionClass::NullPointerException,
55            jni::errors::Error::TryLock => JExceptionClass::IllegalStateException,
56            jni::errors::Error::JavaVMMethodNotFound(_) => JExceptionClass::NoSuchMethodException,
57            jni::errors::Error::FieldAlreadySet(_) => JExceptionClass::IllegalStateException,
58            jni::errors::Error::ThrowFailed(_) => JExceptionClass::IllegalStateException,
59            jni::errors::Error::ParseFailed(_, _) => JExceptionClass::IllegalStateException,
60            jni::errors::Error::JniCall(_) => JExceptionClass::UnsupportedOperationException,
61        }
62    }
63}
64
65impl std::fmt::Display for JExceptionClass {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(f, "{:?}", self)
68    }
69}
70
71impl From<JExceptionClass> for JException {
72    fn from(val: JExceptionClass) -> Self {
73        let msg = &format!("{}", val);
74        JException::from_class_and_msg(val, msg)
75    }
76}
77
78#[derive(Clone)]
79pub struct JException {
80    pub class: JExceptionClass,
81    pub error: Rc<dyn std::error::Error>,
82}
83
84impl JException {
85    pub fn from_class_and_msg(class: JExceptionClass, msg: &str) -> Self {
86        let error: Box<dyn std::error::Error> = msg.to_string().into();
87        let error = Rc::<dyn std::error::Error>::from(error);
88        Self { class, error }
89    }
90
91    pub fn from_std<E: std::error::Error + 'static>(error: E) -> Self {
92        Self {
93            class: JExceptionClass::RuntimeException,
94            error: Rc::new(error),
95        }
96    }
97
98    pub fn from_std_with_class<E: std::error::Error + 'static>(
99        error: E,
100        j_class: JExceptionClass,
101    ) -> Self {
102        Self {
103            class: j_class,
104            error: Rc::new(error),
105        }
106    }
107}
108
109impl jni::errors::ToException for JException {
110    fn to_exception(&self) -> jni::errors::Exception {
111        jni::errors::Exception {
112            class: self.class.get_class_path(),
113            msg: format!("{}", self.error),
114        }
115    }
116}
117
118impl<E> From<E> for JException
119where
120    E: std::error::Error + 'static,
121{
122    fn from(error: E) -> Self {
123        JException::from_std(error)
124    }
125}
126
127impl std::fmt::Display for JException {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        write!(f, "{}", self.error)
130    }
131}
132
133impl std::fmt::Debug for JException {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        write!(f, "{}", self.error)
136    }
137}
138
139pub type JResult<T, E = JException> = core::result::Result<T, E>;
140
141pub fn j_result_handler<'a, T, R: Default>(
142    result: JResult<T, JException>,
143    env: &mut jni::JNIEnv<'a>,
144) -> R
145where
146    T: IntoJavaType<'a, R> + Default,
147{
148    match result {
149        Ok(ok) => match ok.into_java(env) {
150            Ok(ok) => ok,
151            Err(err) => {
152                env.j_throw_exception(err);
153                Default::default()
154            }
155        },
156        Err(err) => {
157            env.j_throw_exception(err);
158            Default::default()
159        }
160    }
161}
162
163pub fn option_handler<'a, T, R: Default>(
164    result: Option<T>,
165    env: &mut jni::JNIEnv<'a>,
166) -> R
167where
168    T: IntoJavaType<'a, R> + Default,
169{
170    match result {
171        Some(ok) => match ok.into_java(env) {
172            Ok(ok) => ok,
173            Err(err) => {
174                env.j_throw_exception(err);
175                Default::default()
176            }
177        }
178        None => Default::default()
179    }
180}
181
182// JNIEnv Util
183
184macro_rules! jthrow {
185    ( $env:expr => $j_class:expr , $message:tt) => {
186        let error = $env.exception_occurred().unwrap_or_default();
187        if error.is_null() {
188            let message = format!(
189                "\nRust Error:  {}\nRust Backtrace:\n{}\n",
190                &$message,
191                Backtrace::force_capture()
192            );
193            $env.throw_new(($j_class).get_class_path(), &message).ok();
194        }
195    };
196}
197
198#[allow(non_snake_case)]
199pub trait JNIEnvUtils {
200    fn j_throw_cause(&mut self, j_class: JExceptionClass, cause: &impl std::error::Error);
201    fn j_throw(&mut self, j_class: JExceptionClass);
202    fn j_throw_msg(&mut self, message: &str);
203    fn j_throw_exception(&mut self, ex: JException);
204
205    fn get_string_owned(
206        &mut self,
207        jstring: &jni::objects::JString<'_>,
208    ) -> jni::errors::Result<String>;
209}
210
211impl<'local> JNIEnvUtils for jni::JNIEnv<'local> {
212    fn j_throw_msg(&mut self, message: &str) {
213        jthrow!( self => &JExceptionClass::RuntimeException, message);
214    }
215    fn j_throw_cause(&mut self, j_class: JExceptionClass, cause: &impl std::error::Error) {
216        jthrow!( self => j_class, cause);
217    }
218    fn j_throw(&mut self, j_class: JExceptionClass) {
219        jthrow!( self => j_class, j_class);
220    }
221    fn j_throw_exception(&mut self, ex: JException) {
222        let c = ex.class;
223        let e = ex.error;
224        jthrow!( self => c, e);
225    }
226
227    fn get_string_owned(
228        &mut self,
229        jstring: &jni::objects::JString<'_>,
230    ) -> jni::errors::Result<String> {
231        let string = self.get_string(jstring)?;
232        string
233            .to_str()
234            .map(|s| s.to_string())
235            .map_err(|_| jni::errors::Error::WrongJValueType("JString", "-"))
236    }
237}
238
239/// Propagate error to Java
240pub trait JavaCatch<'local, T> {
241    fn j_catch(self, env: &mut jni::JNIEnv<'local>) -> crate::JResult<T>;
242}
243
244impl<'local, T, E: std::error::Error + 'static> JavaCatch<'local, T> for JResult<T, E> {
245    fn j_catch(self, env: &mut jni::JNIEnv<'local>) -> crate::JResult<T> {
246        match self {
247            Ok(ok) => Ok(ok),
248            Err(err) => {
249                jthrow!(env => JExceptionClass::RuntimeException, err);
250                Err(JException::from_std(err))
251            }
252        }
253    }
254}
255
256pub trait JavaCatchINI<'local, T> {
257    fn j_catch_ini(self, env: &mut jni::JNIEnv<'local>, msg: &str) -> crate::JResult<T>;
258}
259
260impl<'local, T> JavaCatchINI<'local, T> for Result<T, jni::errors::Error> {
261    fn j_catch_ini(self, env: &mut jni::JNIEnv<'local>, msg: &str) -> crate::JResult<T> {
262        match self {
263            Ok(ok) => Ok(ok),
264            Err(err) => {
265                let exception = JExceptionClass::from(&err);
266                let message = format!("{err}\n   Cause: {msg}");
267                jthrow!(env => exception, message);
268                Err(JException::from_class_and_msg(exception, &message))
269            }
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use crate as java_bindgen;
277    use crate::prelude::*;
278
279    #[allow(non_snake_case)]
280    fn user<'a>(_: &mut JNIEnv<'a>, _class: JClass<'_>) -> JResult<String> {
281        Ok("ok".to_string())
282    }
283
284    #[allow(unused_mut, non_snake_case)]
285    pub extern "system" fn Java_com_test_Lib1_user<'a>(
286        mut env: JNIEnv<'a>,
287        _class: JClass<'_>,
288    ) -> jni::objects::JString<'a> {
289        let r = user(&mut env, _class);
290        j_result_handler(r, &mut env)
291    }
292
293    #[test_jvm]
294    fn should_return_ok<'a>(
295        test_env: &mut JNIEnv<'a>,
296        env: JNIEnv<'a>,
297        class: JClass,
298    ) -> JResult<()> {
299        // Call Java function
300        let result = Java_com_test_Lib1_user(env, class);
301        // Convert result into rust
302        let result: String = result.into_rust(test_env)?;
303        assert_eq!(&result, "ok");
304        Ok(())
305    }
306}