rust_jni/jni/
string.rs

1use java_string::*;
2use jni::method_calls::call_static_method;
3use jni::*;
4use jni_sys;
5use std;
6use std::fmt;
7use std::os::raw::c_char;
8use std::ptr;
9
10include!("call_jni_method.rs");
11include!("generate_class.rs");
12
13/// A type representing a Java
14/// [`String`](https://docs.oracle.com/javase/10/docs/api/java/lang/String.html).
15// TODO: examples.
16// TODO: custom debug.
17#[derive(Debug)]
18pub struct String<'env> {
19    object: Object<'env>,
20}
21
22impl<'env> String<'env> {
23    /// Create a new empty string.
24    ///
25    /// [JNI documentation](https://docs.oracle.com/javase/10/docs/specs/jni/functions.html#newstring)
26    pub fn empty<'a>(env: &'a JniEnv<'a>, token: &NoException<'a>) -> JavaResult<'a, String<'a>> {
27        // Safe because arguments are ensured to be the correct by construction and because
28        // `NewString` throws an exception before returning `null`.
29        let raw_string = unsafe {
30            call_nullable_jni_method!(env, NewString, token, ptr::null(), 0 as jni_sys::jsize)?
31        };
32        // Safe because the argument is a valid string reference.
33        Ok(unsafe { Self::from_raw(env, raw_string) })
34    }
35
36    /// Create a new Java string from a Rust string.
37    ///
38    /// [JNI documentation](https://docs.oracle.com/javase/10/docs/specs/jni/functions.html#newstringutf)
39    pub fn new<'a>(
40        env: &'a JniEnv<'a>,
41        string: &str,
42        token: &NoException<'a>,
43    ) -> JavaResult<'a, String<'a>> {
44        if string.is_empty() {
45            return Self::empty(env, token);
46        }
47
48        let buffer = to_java_string(string);
49        // Safe because arguments are ensured to be the correct by construction and because
50        // `NewStringUTF` throws an exception before returning `null`.
51        let raw_string = unsafe {
52            call_nullable_jni_method!(env, NewStringUTF, token, buffer.as_ptr() as *const c_char)?
53        };
54        // Safe because the argument is a valid string reference.
55        Ok(unsafe { Self::from_raw(env, raw_string) })
56    }
57
58    /// String length (the number of unicode characters).
59    ///
60    /// [JNI documentation](https://docs.oracle.com/javase/10/docs/specs/jni/functions.html#getstringlength)
61    pub fn len(&self, _token: &NoException) -> usize {
62        // Safe because arguments are ensured to be the correct by construction.
63        let length = unsafe {
64            call_jni_method!(
65                self.env(),
66                GetStringLength,
67                self.raw_object() as jni_sys::jstring
68            )
69        };
70        length as usize
71    }
72
73    /// String size (the number of bytes in modified UTF-8).
74    ///
75    /// [JNI documentation](https://docs.oracle.com/javase/10/docs/specs/jni/functions.html#getstringutflength)
76    pub fn size(&self, _token: &NoException) -> usize {
77        // Safe because arguments are ensured to be the correct by construction.
78        let size = unsafe {
79            call_jni_method!(
80                self.env(),
81                GetStringUTFLength,
82                self.raw_object() as jni_sys::jstring
83            )
84        };
85        size as usize
86    }
87
88    /// Convert the Java `String` into a Rust `String`.
89    ///
90    /// This method has a different signature from the one in the `ToString` trait because
91    /// extracting bytes from `String` is only safe when there is no pending exception.
92    ///
93    /// [JNI documentation](https://docs.oracle.com/javase/10/docs/specs/jni/functions.html#getstringutfregion)
94    pub fn as_string(&self, token: &NoException) -> std::string::String {
95        let length = self.len(token);
96        if length == 0 {
97            return "".to_owned();
98        }
99
100        let size = self.size(token) + 1; // +1 for the '\0' byte.
101        let mut buffer: Vec<u8> = Vec::with_capacity(size);
102        // Safe because arguments are ensured to be the correct by construction.
103        unsafe {
104            call_jni_method!(
105                self.env(),
106                GetStringUTFRegion,
107                self.raw_object() as jni_sys::jstring,
108                0 as jni_sys::jsize,
109                length as jni_sys::jsize,
110                buffer.as_mut_ptr() as *mut c_char
111            );
112            buffer.set_len(size);
113        }
114        from_java_string(buffer.as_slice()).unwrap().into_owned()
115    }
116
117    /// Unsafe because an incorrect object reference can be passed.
118    unsafe fn from_raw<'a>(env: &'a JniEnv<'a>, raw_string: jni_sys::jstring) -> String<'a> {
119        String {
120            object: Object::__from_jni(env, raw_string as jni_sys::jobject),
121        }
122    }
123}
124
125java_class!(
126    String,
127    "[`String`](struct.String.html)",
128    constructors = (),
129    methods = (),
130    static_methods = (
131        doc = "Get the string value of an integer.",
132        link = "[`String::valueOf(int)` javadoc](https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#valueOf(int))",
133        java_name = "valueOf",
134        value_of_int(value: i32) -> String<'env>,
135    ),
136);
137
138#[cfg(test)]
139mod string_tests {
140    use super::*;
141    use jni::testing::*;
142    use std::mem;
143    use std::ops::Deref;
144
145    fn test_value<'env>(env: &'env JniEnv<'env>, raw_object: jni_sys::jobject) -> String<'env> {
146        String {
147            object: test_object(env, raw_object),
148        }
149    }
150
151    generate_tests!(String, "Ljava/lang/String;");
152
153    #[test]
154    fn empty() {
155        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
156        let calls = test_raw_jni_env!(vec![JniCall::NewString(NewString {
157            name: ptr::null(),
158            size: 0,
159            result: RAW_STRING,
160        })]);
161        let vm = test_vm(ptr::null_mut());
162        let env = test_env(&vm, calls.env);
163        let string = String::empty(&env, &NoException::test()).unwrap();
164        calls.assert_eq(&string, RAW_STRING);
165    }
166
167    #[test]
168    fn empty_exception() {
169        const EXCEPTION: jni_sys::jobject = 0x2835 as jni_sys::jobject;
170        let calls = test_raw_jni_env!(vec![
171            JniCall::NewString(NewString {
172                name: ptr::null(),
173                size: 0,
174                result: ptr::null_mut(),
175            }),
176            JniCall::ExceptionOccurred(ExceptionOccurred { result: EXCEPTION }),
177            JniCall::ExceptionClear(ExceptionClear {}),
178        ]);
179        let vm = test_vm(ptr::null_mut());
180        let env = test_env(&vm, calls.env);
181        let exception = String::empty(&env, &NoException::test()).unwrap_err();
182        calls.assert_eq(&exception, EXCEPTION);
183    }
184
185    #[test]
186    fn new_empty() {
187        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
188        let calls = test_raw_jni_env!(vec![JniCall::NewString(NewString {
189            name: ptr::null(),
190            size: 0,
191            result: RAW_STRING,
192        })]);
193        let vm = test_vm(ptr::null_mut());
194        let env = test_env(&vm, calls.env);
195        let string = String::new(&env, "", &NoException::test()).unwrap();
196        calls.assert_eq(&string, RAW_STRING);
197    }
198
199    #[test]
200    fn new_empty_exception() {
201        const EXCEPTION: jni_sys::jobject = 0x2835 as jni_sys::jobject;
202        let calls = test_raw_jni_env!(vec![
203            JniCall::NewString(NewString {
204                name: ptr::null(),
205                size: 0,
206                result: ptr::null_mut(),
207            }),
208            JniCall::ExceptionOccurred(ExceptionOccurred { result: EXCEPTION }),
209            JniCall::ExceptionClear(ExceptionClear {}),
210        ]);
211        let vm = test_vm(ptr::null_mut());
212        let env = test_env(&vm, calls.env);
213        let exception = String::new(&env, "", &NoException::test()).unwrap_err();
214        calls.assert_eq(&exception, EXCEPTION);
215    }
216
217    #[test]
218    fn new() {
219        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
220        let calls = test_raw_jni_env!(vec![JniCall::NewStringUTF(NewStringUTF {
221            string: "test-string".to_owned(),
222            result: RAW_STRING,
223        })]);
224        let vm = test_vm(ptr::null_mut());
225        let env = test_env(&vm, calls.env);
226        let string = String::new(&env, "test-string", &NoException::test()).unwrap();
227        calls.assert_eq(&string, RAW_STRING);
228    }
229
230    #[test]
231    fn new_exception() {
232        const EXCEPTION: jni_sys::jobject = 0x2835 as jni_sys::jobject;
233        let calls = test_raw_jni_env!(vec![
234            JniCall::NewStringUTF(NewStringUTF {
235                string: "test-string".to_owned(),
236                result: ptr::null_mut(),
237            }),
238            JniCall::ExceptionOccurred(ExceptionOccurred { result: EXCEPTION }),
239            JniCall::ExceptionClear(ExceptionClear {}),
240        ]);
241        let vm = test_vm(ptr::null_mut());
242        let env = test_env(&vm, calls.env);
243        let exception = String::new(&env, "test-string", &NoException::test()).unwrap_err();
244        calls.assert_eq(&exception, EXCEPTION);
245    }
246
247    #[test]
248    fn len() {
249        const LENGTH: usize = 17;
250        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
251        let calls = test_raw_jni_env!(vec![JniCall::GetStringLength(GetStringLength {
252            string: RAW_STRING,
253            result: 17 as jni_sys::jsize,
254        })]);
255        let vm = test_vm(ptr::null_mut());
256        let env = test_env(&vm, calls.env);
257        let string = unsafe { String::from_raw(&env, RAW_STRING) };
258        assert_eq!(string.len(&NoException::test()), LENGTH);
259    }
260
261    #[test]
262    fn size() {
263        const LENGTH: usize = 17;
264        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
265        let calls = test_raw_jni_env!(vec![JniCall::GetStringUTFLength(GetStringUTFLength {
266            string: RAW_STRING,
267            result: 17 as jni_sys::jsize,
268        })]);
269        let vm = test_vm(ptr::null_mut());
270        let env = test_env(&vm, calls.env);
271        let string = unsafe { String::from_raw(&env, RAW_STRING) };
272        assert_eq!(string.size(&NoException::test()), LENGTH);
273    }
274
275    #[test]
276    fn as_string() {
277        const LENGTH: usize = 5;
278        const SIZE: usize = 11; // `"test-string".len()`.
279        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
280        let calls = test_raw_jni_env!(vec![
281            JniCall::GetStringLength(GetStringLength {
282                string: RAW_STRING,
283                result: LENGTH as jni_sys::jsize,
284            }),
285            JniCall::GetStringUTFLength(GetStringUTFLength {
286                string: RAW_STRING,
287                result: SIZE as jni_sys::jsize,
288            }),
289            JniCall::GetStringUTFRegion(GetStringUTFRegion {
290                string: RAW_STRING,
291                start: 0,
292                len: LENGTH as jni_sys::jsize,
293                buffer: "test-string".to_owned(),
294            }),
295        ]);
296        let vm = test_vm(ptr::null_mut());
297        let env = test_env(&vm, calls.env);
298        let string = unsafe { String::from_raw(&env, RAW_STRING) };
299        assert_eq!(string.as_string(&NoException::test()), "test-string");
300    }
301
302    #[test]
303    fn as_string_empty() {
304        const RAW_STRING: jni_sys::jobject = 0x2835 as jni_sys::jobject;
305        let calls = test_raw_jni_env!(vec![JniCall::GetStringLength(GetStringLength {
306            string: RAW_STRING,
307            result: 0,
308        })]);
309        let vm = test_vm(ptr::null_mut());
310        let env = test_env(&vm, calls.env);
311        let string = unsafe { String::from_raw(&env, RAW_STRING) };
312        assert_eq!(string.as_string(&NoException::test()), "");
313    }
314}