j4rs/api/
instance.rs

1// Copyright 2022 astonbitecode
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::logger::debug;
16use crate::{cache, errors, jni_utils, InvocationArg, Jvm};
17use jni_sys::jobject;
18use serde::de::DeserializeOwned;
19use serde::Serialize;
20use std::any::Any;
21use std::convert::TryFrom;
22use std::sync::mpsc::{Receiver, Sender};
23
24/// A Java instance
25/// Instances contain global Java references and can be sent to other threads
26#[derive(Serialize)]
27pub struct Instance {
28    /// The name of the class of this instance
29    pub(crate) class_name: String,
30    /// The JNI jobject that manipulates this instance.
31    ///
32    /// This object is an instance of `org/astonbitecode/j4rs/api/Instance`
33    #[serde(skip)]
34    pub(crate) jinstance: jobject,
35    #[serde(skip)]
36    pub(crate) skip_deleting_jobject: bool,
37}
38
39impl Instance {
40    /// Creates a new Instance, leaving the passed jobject as is.
41    /// In most cases, the jobject is already transformed to a global reference.
42    pub(crate) fn new(obj: jobject, classname: &str) -> errors::Result<Instance> {
43        Ok(Instance {
44            jinstance: obj,
45            class_name: classname.to_string(),
46            skip_deleting_jobject: false,
47        })
48    }
49
50    /// Returns the class name of this instance
51    pub fn class_name(&self) -> &str {
52        self.class_name.as_ref()
53    }
54
55    /// Consumes the Instance and returns its jobject
56    pub fn java_object(mut self) -> jobject {
57        self.skip_deleting_jobject = true;
58        self.jinstance
59    }
60
61    #[deprecated(
62        since = "0.12.0",
63        note = "Please use Instance::from_jobject or Instance::from_jobject_with_global_ref instead"
64    )]
65    pub fn from(obj: jobject) -> errors::Result<Instance> {
66        let _jvm = cache::get_thread_local_env().map_err(|_| Jvm::attach_thread());
67
68        let global =
69            jni_utils::create_global_ref_from_local_ref(obj, cache::get_thread_local_env()?)?;
70        Ok(Instance {
71            jinstance: global,
72            class_name: cache::UNKNOWN_FOR_RUST.to_string(),
73            skip_deleting_jobject: false,
74        })
75    }
76
77    pub fn from_jobject(obj: jobject) -> errors::Result<Instance> {
78        let _jvm = cache::get_thread_local_env().map_err(|_| Jvm::attach_thread());
79
80        Ok(Instance {
81            jinstance: obj,
82            class_name: cache::UNKNOWN_FOR_RUST.to_string(),
83            skip_deleting_jobject: false,
84        })
85    }
86
87    pub fn from_jobject_with_global_ref(obj: jobject) -> errors::Result<Instance> {
88        let _jvm = cache::get_thread_local_env().map_err(|_| Jvm::attach_thread());
89
90        let global =
91            jni_utils::create_global_ref_from_local_ref(obj, cache::get_thread_local_env()?)?;
92        Ok(Instance {
93            jinstance: global,
94            class_name: cache::UNKNOWN_FOR_RUST.to_string(),
95            skip_deleting_jobject: false,
96        })
97    }
98
99    /// Creates a weak reference of this Instance.
100    fn _weak_ref(&self) -> errors::Result<Instance> {
101        Ok(Instance {
102            class_name: self.class_name.clone(),
103            jinstance: jni_utils::_create_weak_global_ref_from_global_ref(
104                self.jinstance,
105                cache::get_thread_local_env()?,
106            )?,
107            skip_deleting_jobject: false,
108        })
109    }
110}
111
112impl TryFrom<InvocationArg> for Instance {
113    type Error = errors::J4RsError;
114    fn try_from(invocation_arg: InvocationArg) -> errors::Result<Instance> {
115        let obj = invocation_arg.as_java_ptr_with_local_ref(cache::get_thread_local_env()?)?;
116        Instance::new(obj, invocation_arg.class_name())
117    }
118}
119
120impl TryFrom<jobject> for Instance {
121    type Error = errors::J4RsError;
122    fn try_from(obj: jobject) -> errors::Result<Instance> {
123        Instance::from_jobject_with_global_ref(obj)
124    }
125}
126
127impl Drop for Instance {
128    fn drop(&mut self) {
129        debug(&format!("Dropping an instance of {}", self.class_name));
130        if !self.skip_deleting_jobject {
131            if let Some(j_env) = cache::get_thread_local_env_opt() {
132                jni_utils::delete_java_ref(j_env, self.jinstance);
133            }
134        }
135    }
136}
137
138/// Instances contain global Java references and can be sent to other threads
139unsafe impl Send for Instance {}
140
141/// A receiver for Java Instances.
142///
143/// It keeps a channel Receiver to get callback Instances from the Java world
144/// and the address of a `Box<Sender<Instance>>` Box in the heap. This Box is used by Java to communicate
145/// asynchronously Instances to Rust.
146///
147/// On Drop, the InstanceReceiver removes the Box from the heap.
148pub struct InstanceReceiver {
149    pub(crate) rx: Box<Receiver<Instance>>,
150    tx_address: u64,
151}
152
153impl InstanceReceiver {
154    pub(crate) fn new(rx: Receiver<Instance>, tx_address: u64) -> InstanceReceiver {
155        InstanceReceiver {
156            rx: Box::new(rx),
157            tx_address,
158        }
159    }
160
161    pub fn rx(&self) -> &Receiver<Instance> {
162        &self.rx
163    }
164}
165
166impl Drop for InstanceReceiver {
167    fn drop(&mut self) {
168        if self.tx_address > 0 {
169            debug("Dropping an InstanceReceiver");
170            let p = self.tx_address as *mut Sender<Instance>;
171            unsafe {
172                let tx = Box::from_raw(p);
173                drop(tx);
174            }
175        }
176    }
177}
178
179/// Allows chained Jvm calls to created Instances
180pub struct ChainableInstance<'a> {
181    instance: Instance,
182    jvm: &'a Jvm,
183}
184
185impl<'a> ChainableInstance<'a> {
186    pub(crate) fn new(instance: Instance, jvm: &'a Jvm) -> ChainableInstance<'a> {
187        ChainableInstance { instance, jvm }
188    }
189
190    pub(crate) fn new_with_instance_ref(
191        instance: &Instance,
192        jvm: &'a Jvm,
193    ) -> errors::Result<ChainableInstance<'a>> {
194        let cloned = jvm.clone_instance(instance)?;
195        Ok(ChainableInstance {
196            instance: cloned,
197            jvm,
198        })
199    }
200
201    pub fn collect(self) -> Instance {
202        self.instance
203    }
204
205    /// Invokes the method `method_name` of a this `Instance`, passing an array of `InvocationArg`s. It returns an `Instance` as the result of the invocation.
206    pub fn invoke(
207        &self,
208        method_name: &str,
209        inv_args: &[InvocationArg],
210    ) -> errors::Result<ChainableInstance> {
211        let instance = self.jvm.invoke(&self.instance, method_name, inv_args)?;
212        Ok(ChainableInstance::new(instance, self.jvm))
213    }
214
215    /// Creates a clone of the Instance
216    pub fn clone_instance(&self) -> errors::Result<ChainableInstance> {
217        let instance = self.jvm.clone_instance(&self.instance)?;
218        Ok(ChainableInstance::new(instance, self.jvm))
219    }
220
221    /// Invokes the static method `method_name` of the class `class_name`, passing an array of `InvocationArg`s. It returns an `Instance` as the result of the invocation.
222    pub fn cast(&self, to_class: &str) -> errors::Result<ChainableInstance> {
223        let instance = self.jvm.cast(&self.instance, to_class)?;
224        Ok(ChainableInstance::new(instance, self.jvm))
225    }
226
227    /// Retrieves the field `field_name` of the `Instance`.
228    pub fn field(&self, field_name: &str) -> errors::Result<ChainableInstance> {
229        let instance = self.jvm.field(&self.instance, field_name)?;
230        Ok(ChainableInstance::new(instance, self.jvm))
231    }
232
233    /// Returns the Rust representation of the provided instance
234    pub fn to_rust<T: Any>(self) -> errors::Result<T>
235    where
236        T: DeserializeOwned,
237    {
238        self.jvm.to_rust(self.instance)
239    }
240
241    /// Returns the Rust representation of the provided instance, boxed
242    pub fn to_rust_boxed<T: Any>(self) -> errors::Result<Box<T>>
243    where
244        T: DeserializeOwned,
245    {
246        self.jvm.to_rust_boxed(self.instance)
247    }
248}
249
250#[cfg(test)]
251mod instance_unit_tests {
252    use crate::*;
253    use crate::lib_unit_tests::create_tests_jvm;
254
255    #[test]
256    fn is_null() -> errors::Result<()> {
257        let jvm = create_tests_jvm()?;
258        let test_instance = jvm
259            .create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())
260            ?;
261        let maybe_null = jvm.invoke(&test_instance, "getNullInteger", InvocationArg::empty())?;
262        let is_null =
263            jvm.invoke_static(
264                "java.util.Objects", 
265                "isNull", 
266                &[InvocationArg::try_from(maybe_null)?])?;
267        let is_null: bool = jvm.to_rust(is_null)?;
268        assert!(is_null);
269        Ok(())
270    }
271
272    #[test]
273    fn try_from_jobject() -> errors::Result<()> {
274        let c = std::ptr::null_mut();
275        let instance = Instance::try_from(c)?;
276        assert!(instance.java_object() == std::ptr::null_mut());
277        Ok(())
278    }
279}