1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use dsnp_graph_config::errors::DsnpGraphError;
use jni::{objects::JThrowable, JNIEnv};
use std::fmt;

#[derive(Debug)]
pub enum SdkJniError {
	DsnpGraph(DsnpGraphError),
	Jni(jni::errors::Error),
	BadJniParameter(&'static str),
	InvalidProto(protobuf::Error),
	InvalidRequest(&'static str),
	UnexpectedResponse(&'static str),
	LockError,
	InvalidHandle(&'static str),
	UnexpectedPanic(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
}

pub fn throw_exception(env: &mut JNIEnv, error: SdkJniError) {
	let (class, signature) = error.get_java_class_and_constructor_signature();
	let throwable = env
		.new_string(error.to_string())
		.and_then(|message| env.new_object(class, signature, &[(&message).into()]));
	match throwable {
		Ok(o) => {
			let result = env.throw(JThrowable::from(o));
			if let Err(failure) = result {
				log::error!("failed to throw exception for {}: {}", error, failure);
			}
		},
		Err(e) => {
			log::error!("failed to create exception for {}", SdkJniError::from(e));
		},
	}
}

impl From<DsnpGraphError> for SdkJniError {
	fn from(e: DsnpGraphError) -> SdkJniError {
		SdkJniError::DsnpGraph(e)
	}
}

impl From<jni::errors::Error> for SdkJniError {
	fn from(e: jni::errors::Error) -> SdkJniError {
		SdkJniError::Jni(e)
	}
}

impl From<protobuf::Error> for SdkJniError {
	fn from(e: protobuf::Error) -> SdkJniError {
		SdkJniError::InvalidProto(e)
	}
}

impl fmt::Display for SdkJniError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		match self {
			SdkJniError::DsnpGraph(s) => write!(f, "ErrorCode({}) {}", s.error_code(), s),
			SdkJniError::Jni(s) => write!(f, "JNI error {}", s),
			SdkJniError::InvalidProto(s) => write!(f, "invalid proto {}", s),
			SdkJniError::InvalidRequest(s) => write!(f, "invalid request {}", s),
			SdkJniError::UnexpectedResponse(s) => write!(f, "unexpected response {}", s),
			SdkJniError::LockError => write!(f, "unable to acquire lock"),
			SdkJniError::InvalidHandle(s) => write!(f, "invalid handle {}", s),
			SdkJniError::BadJniParameter(m) => write!(f, "bad parameter type {}", m),
			SdkJniError::UnexpectedPanic(e) => {
				write!(f, "unexpected panic: {}", describe_panic(e))
			},
		}
	}
}

// Related to https://github.com/rust-lang/rfcs/issues/1389
pub fn describe_panic(any: &Box<dyn std::any::Any + Send>) -> String {
	if let Some(msg) = any.downcast_ref::<&str>() {
		msg.to_string()
	} else if let Some(msg) = any.downcast_ref::<String>() {
		msg.to_string()
	} else {
		"(break on rust_panic to debug)".to_string()
	}
}

const JAVA_ERROR_PATH: &'static str = "io/projectliberty/graphsdk/exceptions/";

impl SdkJniError {
	fn get_java_class_and_constructor_signature(&self) -> (String, String) {
		let string_message_signature = "(Ljava/lang/String;)V".to_string();
		match self {
			SdkJniError::DsnpGraph(_) =>
				(format!("{}{}", JAVA_ERROR_PATH, "GraphSdkException"), string_message_signature),
			SdkJniError::Jni(_) =>
				(format!("{}{}", JAVA_ERROR_PATH, "JniException"), string_message_signature),
			SdkJniError::InvalidProto(_) => (
				format!("{}{}", JAVA_ERROR_PATH, "InvalidProtoException"),
				string_message_signature,
			),
			SdkJniError::InvalidRequest(_) | SdkJniError::BadJniParameter(_) => (
				format!("{}{}", JAVA_ERROR_PATH, "InvalidRequestException"),
				string_message_signature,
			),
			SdkJniError::UnexpectedResponse(_) => (
				format!("{}{}", JAVA_ERROR_PATH, "UnexpectedResponseException"),
				string_message_signature,
			),
			SdkJniError::LockError => (
				format!("{}{}", JAVA_ERROR_PATH, "AcquiringLockException"),
				string_message_signature,
			),
			SdkJniError::InvalidHandle(_) => (
				format!("{}{}", JAVA_ERROR_PATH, "InvalidHandleException"),
				string_message_signature,
			),
			SdkJniError::UnexpectedPanic(_) =>
				(format!("{}{}", JAVA_ERROR_PATH, "UnknownException"), string_message_signature),
		}
	}
}