aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com>2024-05-14 20:46:42 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-05-14 20:46:42 +0000
commit5a83a3d8042327b2488e263b383957b15a4267ba (patch)
treea97093be32fe3aa90d57fe6c06ced907eac21235
parentd8010e71ab77cdb1e12974fcf7c1bd4c0922fdc1 (diff)
parent27d0b7d2a286a61f3b2fd06fc3c37289c30ee449 (diff)
downloadjni-master.tar.gz
Merge "Create temporary legacy JNI version for migration" into mainHEADmastermain
-rw-r--r--0.20.0/Android.bp24
-rw-r--r--0.20.0/CHANGELOG.md264
-rw-r--r--0.20.0/CODE_OF_CONDUCT.md73
-rw-r--r--0.20.0/CONTRIBUTING.md165
-rw-r--r--0.20.0/Cargo.toml55
-rw-r--r--0.20.0/Cargo.toml.orig38
l---------0.20.0/LICENSE1
-rw-r--r--0.20.0/LICENSE-APACHE201
-rw-r--r--0.20.0/LICENSE-MIT21
-rw-r--r--0.20.0/METADATA23
-rw-r--r--0.20.0/MODULE_LICENSE_APACHE20
-rw-r--r--0.20.0/OWNERS1
-rw-r--r--0.20.0/README.md34
-rw-r--r--0.20.0/benches/api_calls.rs366
-rw-r--r--0.20.0/build.rs109
-rw-r--r--0.20.0/cargo_embargo.json8
-rw-r--r--0.20.0/clippy.toml2
-rw-r--r--0.20.0/example/HelloWorld.h69
-rw-r--r--0.20.0/example/HelloWorld.java49
-rw-r--r--0.20.0/example/Makefile10
-rw-r--r--0.20.0/src/lib.rs231
-rw-r--r--0.20.0/src/sys.rs1
-rw-r--r--0.20.0/src/wrapper/descriptors/class_desc.rs39
-rw-r--r--0.20.0/src/wrapper/descriptors/desc.rs17
-rw-r--r--0.20.0/src/wrapper/descriptors/exception_desc.rs47
-rw-r--r--0.20.0/src/wrapper/descriptors/field_desc.rs29
-rw-r--r--0.20.0/src/wrapper/descriptors/method_desc.rs39
-rw-r--r--0.20.0/src/wrapper/descriptors/mod.rs14
-rw-r--r--0.20.0/src/wrapper/errors.rs89
-rw-r--r--0.20.0/src/wrapper/executor.rs96
-rw-r--r--0.20.0/src/wrapper/java_vm/init_args.rs138
-rw-r--r--0.20.0/src/wrapper/java_vm/mod.rs7
-rw-r--r--0.20.0/src/wrapper/java_vm/vm.rs455
-rw-r--r--0.20.0/src/wrapper/jnienv.rs2334
-rw-r--r--0.20.0/src/wrapper/macros.rs142
-rw-r--r--0.20.0/src/wrapper/objects/auto_array.rs137
-rw-r--r--0.20.0/src/wrapper/objects/auto_local.rs80
-rw-r--r--0.20.0/src/wrapper/objects/auto_primitive_array.rs90
-rw-r--r--0.20.0/src/wrapper/objects/global_ref.rs107
-rw-r--r--0.20.0/src/wrapper/objects/jbytebuffer.rs49
-rw-r--r--0.20.0/src/wrapper/objects/jclass.rs53
-rw-r--r--0.20.0/src/wrapper/objects/jfieldid.rs48
-rw-r--r--0.20.0/src/wrapper/objects/jlist.rs202
-rw-r--r--0.20.0/src/wrapper/objects/jmap.rs246
-rw-r--r--0.20.0/src/wrapper/objects/jmethodid.rs48
-rw-r--r--0.20.0/src/wrapper/objects/jobject.rs56
-rw-r--r--0.20.0/src/wrapper/objects/jstaticfieldid.rs48
-rw-r--r--0.20.0/src/wrapper/objects/jstaticmethodid.rs48
-rw-r--r--0.20.0/src/wrapper/objects/jstring.rs52
-rw-r--r--0.20.0/src/wrapper/objects/jthrowable.rs52
-rw-r--r--0.20.0/src/wrapper/objects/jvalue.rs353
-rw-r--r--0.20.0/src/wrapper/objects/mod.rs56
-rw-r--r--0.20.0/src/wrapper/objects/release_mode.rs15
-rw-r--r--0.20.0/src/wrapper/signature.rs274
-rw-r--r--0.20.0/src/wrapper/strings/ffi_str.rs109
-rw-r--r--0.20.0/src/wrapper/strings/java_str.rs71
-rw-r--r--0.20.0/src/wrapper/strings/mod.rs6
-rw-r--r--0.20.0/src/wrapper/version.rs43
-rw-r--r--0.20.0/test_profile24
-rw-r--r--0.20.0/tests/executor.rs75
-rw-r--r--0.20.0/tests/executor_nested_attach.rs62
-rw-r--r--0.20.0/tests/java_integers.rs41
-rw-r--r--0.20.0/tests/jmap.rs41
-rw-r--r--0.20.0/tests/jni_api.rs958
-rw-r--r--0.20.0/tests/jni_global_refs.rs80
-rw-r--r--0.20.0/tests/threads_attach_guard.rs18
-rw-r--r--0.20.0/tests/threads_detach.rs19
-rw-r--r--0.20.0/tests/threads_detach_daemon.rs19
-rw-r--r--0.20.0/tests/threads_explicit_detach.rs17
-rw-r--r--0.20.0/tests/threads_explicit_detach_daemon.rs17
-rw-r--r--0.20.0/tests/threads_explicit_detach_permanent.rs17
-rw-r--r--0.20.0/tests/threads_nested_attach_daemon.rs48
-rw-r--r--0.20.0/tests/threads_nested_attach_guard.rs54
-rw-r--r--0.20.0/tests/threads_nested_attach_permanently.rs49
-rw-r--r--0.20.0/tests/util/example_proxy.rs52
-rw-r--r--0.20.0/tests/util/mod.rs85
76 files changed, 9110 insertions, 0 deletions
diff --git a/0.20.0/Android.bp b/0.20.0/Android.bp
new file mode 100644
index 0000000..29ed79a
--- /dev/null
+++ b/0.20.0/Android.bp
@@ -0,0 +1,24 @@
+rust_library {
+ name: "libjni_legacy",
+ host_supported: true,
+ crate_name: "jni",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.20.0",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ features: ["default"],
+ rustlibs: [
+ "libcesu8",
+ "libcombine",
+ "libjni_sys",
+ "liblog_rust",
+ "libthiserror",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.uwb",
+ ],
+ product_available: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+}
diff --git a/0.20.0/CHANGELOG.md b/0.20.0/CHANGELOG.md
new file mode 100644
index 0000000..aeb692b
--- /dev/null
+++ b/0.20.0/CHANGELOG.md
@@ -0,0 +1,264 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+
+<!-- Use the following sections from the spec: http://keepachangelog.com/en/1.0.0/
+ - Added for new features.
+ - Changed for changes in existing functionality.
+ - Deprecated for soon-to-be removed features.
+ - Removed for now removed features.
+ - Fixed for any bug fixes.
+ - Security in case of vulnerabilities. -->
+
+## [Unreleased]
+
+
+## [0.20.0] — 2022-10-17
+
+### Added
+- `Default` trait implemented for `JObject`, `JString`, `JClass`, and `JByteBuffer` ([#199](https://github.com/jni-rs/jni-rs/issues/199))
+- `Debug` trait implemented for `JavaVM`, `GlobalRef`, `GlobalRefGuard`, `JStaticMethodID` and `ReleaseMode` ([#345](https://github.com/jni-rs/jni-rs/pull/345))
+- `ReturnType` for specifying object return types without a String allocation. ([#329](https://github.com/jni-rs/jni-rs/issues/329))
+
+### Changed
+- The `release_string_utf_chars` function has been marked as unsafe. ([#334](https://github.com/jni-rs/jni-rs/pull/334))
+- Mark `JNIEnv::new_direct_byte_buffer` as `unsafe` ([#320](https://github.com/jni-rs/jni-rs/pull/320))
+- `JNIEnv::new_direct_byte_buffer` now takes a raw pointer and size instead of a slice ([#351](https://github.com/jni-rs/jni-rs/pull/351) and [#364](https://github.com/jni-rs/jni-rs/pull/364))
+- `JNIEnv::direct_buffer_address` returns a raw pointer instead of a slice ([#364](https://github.com/jni-rs/jni-rs/pull/364))
+- The lifetime of `AutoArray` is no longer tied to the lifetime of a particular `JNIEnv` reference. ([#302](https://github.com/jni-rs/jni-rs/issues/302))
+- Relaxed lifetime restrictions on `JNIEnv::new_local_ref`. Now it can be used to create a local
+ reference from a global reference. ([#301](https://github.com/jni-rs/jni-rs/issues/301) / [#319](https://github.com/jni-rs/jni-rs/pull/319))
+- `JMethodID` and `JStaticMethodID` implement `Send` + `Sync` and no longer has a lifetime parameter, making method
+ IDs cacheable (with a documented 'Safety' note about ensuring they remain valid). ([#346](https://github.com/jni-rs/jni-rs/pull/346))
+- `JFieldID` and `JStaticFieldID` implement `Send` + `Sync` and no longer has a lifetime parameter, making field
+ IDs cacheable (with a documented 'Safety' note about ensuring they remain valid). ([#346](https://github.com/jni-rs/jni-rs/pull/365))
+- The `call_*_method_unchecked` functions now take `jni:sys::jvalue` arguments to avoid allocating
+ a `Vec` on each call to map + collect `JValue`s as `sys:jvalue`s ([#329](https://github.com/jni-rs/jni-rs/issues/329))
+- The `From` trait implementations converting `jni_sys` types like `jobject` to `JObject` have been replaced
+ with `unsafe` `::from_raw` functions and corresponding `::into_raw` methods. Existing `::into_inner` APIs were
+ renamed `::into_raw` for symmetry. ([#197](https://github.com/jni-rs/jni-rs/issues/197))
+- The APIs `JNIEnv::set_rust_field`, `JNIEnv::get_rust_field` and `JNIEnv::take_rust_field` have been marked as `unsafe` ([#219](https://github.com/jni-rs/jni-rs/issues/219))
+
+## [0.19.0] — 2021-01-24
+
+### Added
+- `AutoArray` and generic `get_array_elements()`, along with `get_<type>_array_elements` helpers. (#287)
+- `size()` method to `AutoArray` and `AutoPrimitiveArray`. (#278 / #287)
+- `discard()` method to `AutoArray` and `AutoPrimitiveArray`. (#275 / #287)
+
+### Changed
+- Removed AutoPrimitiveArray::commit(). (#290)
+- `AutoByte/PrimitiveArray.commit()` now returns `Result`. (#275)
+- Removed methods get/release/commit_byte/primitive_array_{elements|critical}. (#281)
+- Renamed methods get_auto_byte/long/primitive_array_{elements|critical} to
+ get_byte/long/primitive_array_{elements|critical}. (#281)
+
+## [0.18.0] — 2020-09-23
+
+### Added
+- `JNIEnv#define_unnamed_class` function that allows loading a class without
+ specifying its name. The name is inferred from the class data. (#246)
+- `SetStatic<type>Field`. (#248)
+- `TryFrom<JValue>` for types inside JValue variants (#264).
+- Implemented Copy for JNIEnv (#255).
+- `repr(transparent)` attribute to JavaVM struct (#259)
+
+### Changed
+- Switch from `error-chain` to `thiserror`, making all errors `Send`. Also, support all JNI errors
+ in the `jni_error_code_to_result` function and add more information to the `InvalidArgList`
+ error. ([#242](https://github.com/jni-rs/jni-rs/pull/242))
+
+## [0.17.0] — 2020-06-30
+
+### Added
+- Get/ReleaseByteArrayElements, and Get/ReleasePrimitiveArrayCritical. (#237)
+
+## [0.16.0] — 2020-02-28
+
+### Fixed
+- Java VM instantiation with some MacOS configurations. (#220, #229, #230).
+
+## [0.15.0] — 2020-02-28
+
+### Added
+- Ability to pass object wrappers that are convertible to `JObject` as arguments to the majority
+ of JNIEnv methods without explicit conversion. (#213)
+- `JNIEnv#is_same_object` implementation. (#213)
+- `JNIEnv#register_native_methods`. (#214)
+- Conversion from `Into<JObject>` to `JValue::Object`.
+
+### Fixed
+- Passing `null` as class loader to `define_class` method now allowed according
+ to the JNI specification. (#225)
+
+## [0.14.0] — 2019-10-31
+
+### Changed
+- Relaxed some lifetime restrictions in JNIEnv to support the case when
+ method, field ids; and global references to classes
+ have a different (larger) lifetime than JNIEnv. (#209)
+
+## [0.13.1] — 2019-08-22
+
+### Changed
+- Various documentation improvements.
+
+## [0.13.0] — 2019-07-05
+
+0.13 brings major improvements in thread management, allowing to attach the native threads
+permanently and safely; `Executor` for extra convenience and safety; and other
+improvements and fixes.
+
+:warning: If your code attaches native threads — make sure to check the updated documentation
+of [JavaVM](https://docs.rs/jni/0.13.0/jni/struct.JavaVM.html) to learn about the new features!
+
+### Added
+- `JavaVM::attach_current_thread_permanently` method, which attaches the current
+ thread and detaches it when the thread finishes. Daemon threads attached
+ with `JavaVM::attach_current_thread_as_daemon` also automatically detach themselves
+ when finished. The number of currently attached threads may be acquired using
+ `JavaVM::threads_attached` method. (#179, #180)
+- `Executor` — a simple thread attachment manager which helps to safely
+ execute a closure in attached thread context and to automatically free
+ created local references at closure exit. (#186)
+
+### Changed
+- The default JNI API version in `InitArgsBuilder` from V1 to V8. (#178)
+- Extended the lifetimes of `AutoLocal` to make it more flexible. (#190)
+- Default exception type from checked `java.lang.Exception` to unchecked `java.lang.RuntimeException`.
+ It is used implicitly when `JNIEnv#throw` is invoked with exception message:
+ `env.throw("Exception message")`; however, for efficiency reasons, it is recommended
+ to specify the exception type explicitly *and* use `throw_new`:
+ `env.throw_new(exception_type, "Exception message")`. (#194)
+
+### Fixed
+- Native threads attached with `JavaVM::attach_current_thread_as_daemon` now automatically detach
+ themselves on exit, preventing Java Thread leaks. (#179)
+- Local reference leaks in `JList`, `JMap` and `JMapIter`. (#190, #191)
+
+## [0.12.3]
+
+### Added
+- `From<jboolean>` implementation for `JValue` (#173)
+- `Debug` trait for InitArgsBuilder. (#175)
+- `InitArgsBuilder#options` returning the collected JVM options. (#177)
+
+## [0.12.2]
+
+### Changed
+- Updated documentation of GetXArrayRegion methods (#169)
+- Improved ABI compatibility on various platforms (#170)
+
+## [0.12.1]
+
+This release does not bring code changes.
+
+### Changed
+- Updated project documentation.
+
+## [0.12.0]
+
+### Changed
+- `JString`, `JMap` and `JavaStr` and their respective iterators now require an extra lifetime so
+ that they can now work with `&'b JNIEnv<'a>`, where `'a: 'b`.
+
+## [0.11.0]
+
+### Highlights
+This release brings various improvements and fixes, outlined below. The most notable changes are:
+- `null` is no longer represented as an `Err` with error kind `NullPtr` if it is a value of some
+ nullable Java reference (not an indication of an error). Related issues: #136, #148, #163.
+- `unsafe` methods, providing a low-level API similar to JNI, has been marked safe and renamed
+ to have `_unchecked` suffix. Such methods can be used to implement caching of class references
+ and method IDs to improve performance in loops and frequently called Java callbacks.
+ If you have such, check out [the docs][unchecked-docs] and [one of early usages][cache-exonum]
+ of this feature.
+
+[unchecked-docs]: https://docs.rs/jni/0.11.0/jni/struct.JNIEnv.html
+[cache-exonum]: https://github.com/exonum/exonum-java-binding/blob/affa85c026c1870b502725b291822c00f199745d/exonum-java-binding/core/rust/src/utils/jni_cache.rs#L40
+
+### Added
+- Invocation API support on Windows and AppVeyor CI (#149)
+
+### Changed
+- `push_local_frame`, `delete_global_ref` and `release_string_utf_chars`
+no longer check for exceptions as they are
+[safe](https://docs.oracle.com/javase/10/docs/specs/jni/design.html#exception-handling)
+to call if there is a pending exception (#124):
+ - `push_local_frame` will now work in case of pending exceptions — as
+ the spec requires; and fail in case of allocation errors
+ - `delete_global_ref` and `release_string_utf_chars` won't print incorrect
+ log messages
+
+- Rename some macros to better express their intent (see #123):
+ - Rename `jni_call` to `jni_non_null_call` as it checks the return value
+ to be non-null.
+ - Rename `jni_non_null_call` (which may return nulls) to `jni_non_void_call`.
+
+- A lot of public methods of `JNIEnv` have been marked as safe, all unsafe code
+ has been isolated inside internal macros. Methods with `_unsafe` suffixes have
+ been renamed and now have `_unchecked` suffixes (#140)
+
+- `from_str` method of the `JavaType` has been replaced by the `FromStr`
+ implementation
+
+- Implemented Sync for GlobalRef (#102).
+
+- Improvements in macro usage for JNI methods calls (#136):
+ - `call_static_method_unchecked` and `get_static_field_unchecked` methods are
+ allowed to return NULL object
+ - Added checking for pending exception to the `call_static_method_unchecked`
+ method (eliminated WARNING messages in log)
+
+- Further improvements in macro usage for JNI method calls (#150):
+ - The new_global_ref() and new_local_ref() functions are allowed to work with NULL objects according to specification.
+ - Fixed the family of functions new_direct_byte_buffer(), get_direct_buffer_address() and get_direct_buffer_capacity()
+ by adding checking for null and error codes.
+ - Increased tests coverage for JNIEnv functions.
+
+- Implemented Clone for JNIEnv (#147).
+
+- The get_superclass(), get_field_unchecked() and get_object_array_element() are allowed to return NULL object according
+ to the specification (#163).
+
+### Fixed
+- The issue with early detaching of a thread by nested AttachGuard. (#139)
+
+## [0.10.2]
+
+### Added
+- `JavaVM#get_java_vm_pointer` to retrieve a JavaVM pointer (#98)
+- This changelog and other project documents (#106)
+
+### Changed
+- The project is moved to an organization (#104)
+- Updated versions of dependencies (#105)
+- Improved project documents (#107)
+
+### Fixed
+- Crate type of a shared library with native methods
+ must be `cdylib` (#100)
+
+## [0.10.1]
+- No changes has been made to the Changelog until this release.
+
+[Unreleased]: https://github.com/jni-rs/jni-rs/compare/v0.20.0...HEAD
+[0.20.0]: https://github.com/jni-rs/jni-rs/compare/v0.19.0...v0.20.0
+[0.19.0]: https://github.com/jni-rs/jni-rs/compare/v0.18.0...v0.19.0
+[0.18.0]: https://github.com/jni-rs/jni-rs/compare/v0.17.0...v0.18.0
+[0.17.0]: https://github.com/jni-rs/jni-rs/compare/v0.16.0...v0.17.0
+[0.16.0]: https://github.com/jni-rs/jni-rs/compare/v0.15.0...v0.16.0
+[0.15.0]: https://github.com/jni-rs/jni-rs/compare/v0.14.0...v0.15.0
+[0.14.0]: https://github.com/jni-rs/jni-rs/compare/v0.13.1...v0.14.0
+[0.13.1]: https://github.com/jni-rs/jni-rs/compare/v0.13.0...v0.13.1
+[0.13.0]: https://github.com/jni-rs/jni-rs/compare/v0.12.3...v0.13.0
+[0.12.3]: https://github.com/jni-rs/jni-rs/compare/v0.12.2...v0.12.3
+[0.12.2]: https://github.com/jni-rs/jni-rs/compare/v0.12.1...v0.12.2
+[0.12.1]: https://github.com/jni-rs/jni-rs/compare/v0.12.0...v0.12.1
+[0.12.0]: https://github.com/jni-rs/jni-rs/compare/v0.11.0...v0.12.0
+[0.11.0]: https://github.com/jni-rs/jni-rs/compare/v0.10.2...v0.11.0
+[0.10.2]: https://github.com/jni-rs/jni-rs/compare/v0.10.1...v0.10.2
+[0.10.1]: https://github.com/jni-rs/jni-rs/compare/v0.1...v0.10.1
diff --git a/0.20.0/CODE_OF_CONDUCT.md b/0.20.0/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..6fd4aee
--- /dev/null
+++ b/0.20.0/CODE_OF_CONDUCT.md
@@ -0,0 +1,73 @@
+# Jni-rs Contributor Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at ilya.bogdanov@xdev.re. The project
+team will review and investigate all complaints, and will respond in a way that
+it deems appropriate to the circumstances. The project team is obligated to
+maintain confidentiality with regard to the reporter of an incident. Further
+details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.4, available at [http://contributor-covenant.org/version/1/4][url].
+
+[homepage]: http://contributor-covenant.org
+[url]: http://contributor-covenant.org/version/1/4/
diff --git a/0.20.0/CONTRIBUTING.md b/0.20.0/CONTRIBUTING.md
new file mode 100644
index 0000000..eea3908
--- /dev/null
+++ b/0.20.0/CONTRIBUTING.md
@@ -0,0 +1,165 @@
+# Jni-rs Contribution Guide
+
+Jni-rs is open to any contributions, whether
+it is a feedback on existing features, a request for a new one, a bug report
+or a pull request. This document describes how to work with this project:
+
+* how to [build](#how-to-build) it
+* how to [test](#tests) it
+* the [code style guidelines](#the-code-style)
+* how to [submit an issue](#submitting-issues)
+* how to [submit a PR](#submitting-pull-requests).
+
+## How to Build
+
+### System Dependencies
+
+You need to install the following dependencies:
+
+* [JDK 1.8+](http://jdk.java.net/10/).
+* [Rust (latest stable)](https://www.rust-lang.org/).
+
+### Building
+
+To build `jni-rs`, simply run
+
+```$sh
+cargo build
+```
+
+inside project root directory. You can also build the library in release mode by
+adding `--release` flag.
+
+## Tests
+
+### Categories of Tests
+
+* Unit tests are typically placed at the bottom of their module file.
+ E.g. [unit tests of signature module](src/wrapper/signature.rs). Tests are wrapped
+ in private module with `test` attribute:
+
+ ```rust
+ #[cfg(test)]
+ mod test {
+ use super::*;
+
+ #[test]
+ fn first_test() { /* ... */ }
+
+ // More tests...
+ }
+ ```
+
+* Integration tests are in [tests directory](tests). They use the same pattern as
+ unit tests, but split into several files instead of private modules.
+ Integration tests use `jni-rs` as every other Rust application — by importing
+ library using `extern crate` keyword.
+
+ ```rust
+ extern crate jni;
+ use jni::*;
+ ```
+
+ Integration tests typically require running a JVM, so you should add
+ `#![cfg(feature = "invocation")]` at the top of the file. You can use helper
+ methods from [util module](tests/util/mod.rs) to run JVM.
+
+ Keep in mind, that only one JVM can be run per process. Therefore, tests that
+ need to launch it with different parameters have to be placed in different
+ source files. `Cargo` runs tests from different modules in parallel.
+
+* Benchmarks are in [benches](benches) directory. As integration tests, they
+ require launching a JVM from native.
+
+* Doc tests are rarely used, but they allow to efficiently test some functionality
+ by providing an example of its usage. Consult
+ [Rust documentation](https://doc.rust-lang.org/beta/rustdoc/documentation-tests.html)
+ for more info about writing these tests.
+
+### Running Tests
+
+#### Setup Environment
+
+As some tests need to launch a JVM, add a directory with JVM library that you want
+to use to `LD_LIBRARY_PATH` on Linux/Mac or `PATH` environment variable on Windows.
+On Linux/Mac, you can use the following script for this purpose:
+
+```$sh
+source test_profile
+```
+
+#### Run Tests
+
+To run all tests, execute the following command:
+
+```$sh
+cargo test --features=invocation
+```
+
+This command will run all tests, including unit, integration and documentation
+tests.
+
+#### Run Benchmarks
+
+To run all benchmarks, execute the following command (nightly Rust required):
+
+```$sh
+cargo +nightly bench --features=invocation
+```
+
+They might help you to see the performance differences between
+two [API flavours][checked-unchecked]: checked and unchecked, and
+pick the right one for your application.
+
+[checked-unchecked]: https://docs.rs/jni/0.20.0/jni/struct.JNIEnv.html#checked-and-unchecked-methods
+
+## The Code Style
+
+Rust code follows the [Rust style guide](https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md).
+[`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) enforces the code style.
+
+After installation, you can run it with
+
+```$sh
+cargo fmt --all -- --check
+```
+
+Every public entry of the API must be thoroughly documented and covered with tests if it is possible.
+You can use [JNI specification](https://docs.oracle.com/javase/10/docs/specs/jni/index.html) as
+a reference for how to write detailed documentation.
+
+Do not forget to update project guides and tutorials along with documentation.
+
+To open local documentation of the project, you can use the following command:
+
+```$sh
+cargo doc --open
+```
+
+## Submitting Issues
+
+Use Github Issues to submit an issue, whether it is a question, some feedback, a bug or a feature request: <https://github.com/jni-rs/jni-rs/issues/new>
+
+## Submitting Pull Requests
+
+Before starting to work on a PR, please submit an issue describing the intended changes.
+Chances are — we are already working on something similar. If not — we can then offer some
+help with the requirements, design, implementation or documentation.
+
+It’s fine to open a PR as soon as you need any feedback — ask any questions in the description.
+
+## License
+
+Licensed under either of
+
+* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+ <http://www.apache.org/licenses/LICENSE-2.0>)
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
+ at your option.
+
+### Contributor License Agreement
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion is the work by you, as defined in the
+Apache-2.0 license, shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/0.20.0/Cargo.toml b/0.20.0/Cargo.toml
new file mode 100644
index 0000000..2441b4a
--- /dev/null
+++ b/0.20.0/Cargo.toml
@@ -0,0 +1,55 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "jni"
+version = "0.20.0"
+authors = ["Josh Chase <josh@prevoty.com>"]
+description = "Rust bindings to the JNI"
+documentation = "https://docs.rs/jni"
+readme = "README.md"
+keywords = [
+ "ffi",
+ "jni",
+ "java",
+]
+categories = ["api-bindings"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/jni-rs/jni-rs"
+
+[package.metadata.docs.rs]
+features = ["invocation"]
+
+[dependencies.cesu8]
+version = "1.1.0"
+
+[dependencies.combine]
+version = "4.1.0"
+
+[dependencies.jni-sys]
+version = "0.3.0"
+
+[dependencies.log]
+version = "0.4.4"
+
+[dependencies.thiserror]
+version = "1.0.20"
+
+[dev-dependencies.lazy_static]
+version = "1"
+
+[build-dependencies.walkdir]
+version = "2"
+
+[features]
+default = []
+invocation = []
diff --git a/0.20.0/Cargo.toml.orig b/0.20.0/Cargo.toml.orig
new file mode 100644
index 0000000..913294d
--- /dev/null
+++ b/0.20.0/Cargo.toml.orig
@@ -0,0 +1,38 @@
+[package]
+authors = ["Josh Chase <josh@prevoty.com>"]
+description = "Rust bindings to the JNI"
+documentation = "https://docs.rs/jni"
+readme = "README.md"
+keywords = [
+ "ffi",
+ "jni",
+ "java",
+]
+categories = ["api-bindings"]
+license = "MIT/Apache-2.0"
+name = "jni"
+repository = "https://github.com/jni-rs/jni-rs"
+# ¡When bumping version please also update it in examples and documentation!
+version = "0.20.0"
+edition = "2018"
+
+[dependencies]
+cesu8 = "1.1.0"
+combine = "4.1.0"
+jni-sys = "0.3.0"
+log = "0.4.4"
+thiserror = "1.0.20"
+
+[build-dependencies]
+walkdir = "2"
+
+[dev-dependencies]
+lazy_static = "1"
+
+
+[features]
+invocation = []
+default = []
+
+[package.metadata.docs.rs]
+features = ["invocation"]
diff --git a/0.20.0/LICENSE b/0.20.0/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/0.20.0/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE \ No newline at end of file
diff --git a/0.20.0/LICENSE-APACHE b/0.20.0/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/0.20.0/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/0.20.0/LICENSE-MIT b/0.20.0/LICENSE-MIT
new file mode 100644
index 0000000..7432388
--- /dev/null
+++ b/0.20.0/LICENSE-MIT
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Prevoty, Inc. and jni-rs contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/0.20.0/METADATA b/0.20.0/METADATA
new file mode 100644
index 0000000..f891cf7
--- /dev/null
+++ b/0.20.0/METADATA
@@ -0,0 +1,23 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update rust/crates/jni
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
+name: "jni"
+description: "Rust bindings to the JNI"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/jni"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/jni/jni-0.20.0.crate"
+ }
+ version: "0.20.0"
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 1
+ day: 19
+ }
+}
diff --git a/0.20.0/MODULE_LICENSE_APACHE2 b/0.20.0/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/0.20.0/MODULE_LICENSE_APACHE2
diff --git a/0.20.0/OWNERS b/0.20.0/OWNERS
new file mode 100644
index 0000000..5a2b844
--- /dev/null
+++ b/0.20.0/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/0.20.0/README.md b/0.20.0/README.md
new file mode 100644
index 0000000..53a8508
--- /dev/null
+++ b/0.20.0/README.md
@@ -0,0 +1,34 @@
+[![Build Status](https://github.com/jni-rs/jni-rs/workflows/CI/badge.svg)](https://github.com/jni-rs/jni-rs/actions)
+[![Docs](https://docs.rs/jni/badge.svg)](https://docs.rs/jni)
+[![Crates.io](https://img.shields.io/crates/v/jni.svg)](https://crates.io/crates/jni)
+
+# JNI Bindings for Rust
+
+[![Join the chat at https://gitter.im/jni-rs/Lobby](https://badges.gitter.im/jni-rs/Lobby.svg)](https://gitter.im/jni-rs/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+This project provides complete JNI bindings for Rust, allowing to:
+
+- Implement native Java methods for JVM and Android in Rust
+- Call Java code from Rust
+- Embed JVM in Rust applications and use any Java libraries
+
+See the [docs](https://docs.rs/jni) for more details.
+
+## Example
+```
+cd example
+make
+```
+
+## Contribution
+
+See the [Contribution Guide](CONTRIBUTING.md) for details.
+
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+at your option.
diff --git a/0.20.0/benches/api_calls.rs b/0.20.0/benches/api_calls.rs
new file mode 100644
index 0000000..0ee0351
--- /dev/null
+++ b/0.20.0/benches/api_calls.rs
@@ -0,0 +1,366 @@
+#![cfg(feature = "invocation")]
+#![feature(test)]
+
+extern crate test;
+
+use jni_sys::jvalue;
+use lazy_static::lazy_static;
+
+use jni::{
+ descriptors::Desc,
+ objects::{JClass, JMethodID, JObject, JStaticMethodID, JValue},
+ signature::{Primitive, ReturnType},
+ sys::jint,
+ InitArgsBuilder, JNIEnv, JNIVersion, JavaVM,
+};
+
+static CLASS_MATH: &str = "java/lang/Math";
+static CLASS_OBJECT: &str = "java/lang/Object";
+static CLASS_LOCAL_DATE_TIME: &str = "java/time/LocalDateTime";
+static METHOD_MATH_ABS: &str = "abs";
+static METHOD_OBJECT_HASH_CODE: &str = "hashCode";
+static METHOD_CTOR: &str = "<init>";
+static METHOD_LOCAL_DATE_TIME_OF: &str = "of";
+static SIG_OBJECT_CTOR: &str = "()V";
+static SIG_MATH_ABS: &str = "(I)I";
+static SIG_OBJECT_HASH_CODE: &str = "()I";
+static SIG_LOCAL_DATE_TIME_OF: &str = "(IIIIIII)Ljava/time/LocalDateTime;";
+
+#[inline(never)]
+fn native_abs(x: i32) -> i32 {
+ x.abs()
+}
+
+fn jni_abs_safe(env: &JNIEnv, x: jint) -> jint {
+ let x = JValue::from(x);
+ let v = env
+ .call_static_method(CLASS_MATH, METHOD_MATH_ABS, SIG_MATH_ABS, &[x])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_hash_safe(env: &JNIEnv, obj: JObject) -> jint {
+ let v = env
+ .call_method(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE, &[])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_local_date_time_of_safe<'e>(
+ env: &JNIEnv<'e>,
+ year: jint,
+ month: jint,
+ day_of_month: jint,
+ hour: jint,
+ minute: jint,
+ second: jint,
+ nanosecond: jint,
+) -> JObject<'e> {
+ let v = env
+ .call_static_method(
+ CLASS_LOCAL_DATE_TIME,
+ METHOD_LOCAL_DATE_TIME_OF,
+ SIG_LOCAL_DATE_TIME_OF,
+ &[
+ year.into(),
+ month.into(),
+ day_of_month.into(),
+ hour.into(),
+ minute.into(),
+ second.into(),
+ nanosecond.into(),
+ ],
+ )
+ .unwrap();
+ v.l().unwrap()
+}
+
+fn jni_int_call_static_unchecked<'c, C>(
+ env: &JNIEnv<'c>,
+ class: C,
+ method_id: JStaticMethodID,
+ x: jint,
+) -> jint
+where
+ C: Desc<'c, JClass<'c>>,
+{
+ let x = JValue::from(x);
+ let ret = ReturnType::Primitive(Primitive::Int);
+ let v = env
+ .call_static_method_unchecked(class, method_id, ret, &[x.into()])
+ .unwrap();
+ v.i().unwrap()
+}
+
+fn jni_int_call_unchecked<'m, M>(env: &JNIEnv<'m>, obj: JObject<'m>, method_id: M) -> jint
+where
+ M: Desc<'m, JMethodID>,
+{
+ let ret = ReturnType::Primitive(Primitive::Int);
+ let v = env.call_method_unchecked(obj, method_id, ret, &[]).unwrap();
+ v.i().unwrap()
+}
+
+fn jni_object_call_static_unchecked<'c, C>(
+ env: &JNIEnv<'c>,
+ class: C,
+ method_id: JStaticMethodID,
+ args: &[jvalue],
+) -> JObject<'c>
+where
+ C: Desc<'c, JClass<'c>>,
+{
+ let v = env
+ .call_static_method_unchecked(class, method_id, ReturnType::Object, args)
+ .unwrap();
+ v.l().unwrap()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::rc::Rc;
+ use std::sync::Arc;
+ use test::{black_box, Bencher};
+
+ lazy_static! {
+ static ref VM: JavaVM = {
+ let args = InitArgsBuilder::new()
+ .version(JNIVersion::V8)
+ .build()
+ .unwrap();
+ JavaVM::new(args).unwrap()
+ };
+ }
+
+ #[bench]
+ fn native_call_function(b: &mut Bencher) {
+ b.iter(|| {
+ let _ = native_abs(black_box(-3));
+ });
+ }
+
+ #[bench]
+ fn jni_call_static_abs_method_safe(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| jni_abs_safe(&env, -3));
+ }
+
+ #[bench]
+ fn jni_call_static_abs_method_unchecked_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_MATH;
+ let method_id = env
+ .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
+ .unwrap();
+
+ b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3));
+ }
+
+ #[bench]
+ fn jni_call_static_abs_method_unchecked_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_MATH.lookup(&env).unwrap();
+ let method_id = env
+ .get_static_method_id(class, METHOD_MATH_ABS, SIG_MATH_ABS)
+ .unwrap();
+
+ b.iter(|| jni_int_call_static_unchecked(&env, class, method_id, -3));
+ }
+
+ #[bench]
+ fn jni_call_static_date_time_method_safe(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ b.iter(|| {
+ let obj = jni_local_date_time_of_safe(&env, 1, 1, 1, 1, 1, 1, 1);
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_call_static_date_time_method_unchecked_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_LOCAL_DATE_TIME.lookup(&env).unwrap();
+ let method_id = env
+ .get_static_method_id(class, METHOD_LOCAL_DATE_TIME_OF, SIG_LOCAL_DATE_TIME_OF)
+ .unwrap();
+
+ b.iter(|| {
+ let obj = jni_object_call_static_unchecked(
+ &env,
+ class,
+ method_id,
+ &[
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ JValue::Int(1).into(),
+ ],
+ );
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_call_object_hash_method_safe(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let s = env.new_string("").unwrap();
+ let obj = black_box(JObject::from(s));
+
+ b.iter(|| jni_hash_safe(&env, obj));
+ }
+
+ #[bench]
+ fn jni_call_object_hash_method_unchecked(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let s = env.new_string("").unwrap();
+ let obj = black_box(JObject::from(s));
+ let method_id = env
+ .get_method_id(obj, METHOD_OBJECT_HASH_CODE, SIG_OBJECT_HASH_CODE)
+ .unwrap();
+
+ b.iter(|| jni_int_call_unchecked(&env, obj, method_id));
+ }
+
+ #[bench]
+ fn jni_new_object_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+
+ b.iter(|| {
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_by_id_str(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let ctor_id = env
+ .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
+ .unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_object_by_id_jclass(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class: JClass = CLASS_OBJECT.lookup(&env).unwrap();
+ let ctor_id = env
+ .get_method_id(class, METHOD_CTOR, SIG_OBJECT_CTOR)
+ .unwrap();
+
+ b.iter(|| {
+ let obj = env.new_object_unchecked(class, ctor_id, &[]).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ });
+ }
+
+ #[bench]
+ fn jni_new_global_ref(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ let global_ref = env.new_global_ref(obj).unwrap();
+ env.delete_local_ref(obj).unwrap();
+
+ b.iter(|| env.new_global_ref(&global_ref).unwrap());
+ }
+
+ /// Checks the overhead of checking if exception has occurred.
+ ///
+ /// Such checks are required each time a Java method is called, but
+ /// can be omitted if we call a JNI method that returns an error status.
+ ///
+ /// See also #58
+ #[bench]
+ fn jni_check_exception(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| env.exception_check().unwrap());
+ }
+
+ #[bench]
+ fn jni_get_java_vm(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+
+ b.iter(|| {
+ let _jvm = env.get_java_vm().unwrap();
+ });
+ }
+
+ /// A benchmark measuring Push/PopLocalFrame overhead.
+ ///
+ /// Such operations are *required* if one attaches a long-running
+ /// native thread to the JVM because there is no 'return-from-native-method'
+ /// event when created local references are freed, hence no way for
+ /// the JVM to know that the local references are no longer used in the native code.
+ #[bench]
+ fn jni_noop_with_local_frame(b: &mut Bencher) {
+ // Local frame size actually doesn't matter since JVM does not preallocate anything.
+ const LOCAL_FRAME_SIZE: i32 = 32;
+ let env = VM.attach_current_thread().unwrap();
+ b.iter(|| {
+ env.with_local_frame(LOCAL_FRAME_SIZE, || Ok(JObject::null()))
+ .unwrap()
+ });
+ }
+
+ /// A benchmark of the overhead of attaching and detaching a native thread.
+ ///
+ /// It is *huge* — two orders of magnitude higher than calling a single
+ /// Java method using unchecked APIs (e.g., `jni_call_static_unchecked`).
+ ///
+ #[bench]
+ fn jvm_noop_attach_detach_native_thread(b: &mut Bencher) {
+ b.iter(|| {
+ let env = VM.attach_current_thread().unwrap();
+ black_box(&env);
+ });
+ }
+
+ #[bench]
+ fn native_arc(b: &mut Bencher) {
+ let env = VM.attach_current_thread().unwrap();
+ let class = CLASS_OBJECT;
+ let obj = env.new_object(class, SIG_OBJECT_CTOR, &[]).unwrap();
+ let global_ref = env.new_global_ref(obj).unwrap();
+ env.delete_local_ref(obj).unwrap();
+ let arc = Arc::new(global_ref);
+
+ b.iter(|| {
+ let _ = black_box(Arc::clone(&arc));
+ });
+ }
+
+ #[bench]
+ fn native_rc(b: &mut Bencher) {
+ let _env = VM.attach_current_thread().unwrap();
+ let env = VM.get_env().unwrap();
+ let rc = Rc::new(env);
+
+ b.iter(|| {
+ let _ = black_box(Rc::clone(&rc));
+ });
+ }
+}
diff --git a/0.20.0/build.rs b/0.20.0/build.rs
new file mode 100644
index 0000000..05419f1
--- /dev/null
+++ b/0.20.0/build.rs
@@ -0,0 +1,109 @@
+//! This build script is used to link with `jvm` dynamic library when
+//! `invocation` feature is enabled.
+//!
+//! To do so, we look for `JAVA_HOME` environment variable.
+//! * If it exists, we recursively search for `jvm` library file inside `JAVA_HOME` directory.
+//! * If it is not set, we use the following commmand to find actual JVM home directory:
+//! ```bash
+//! java -XshowSettings:properties -version | grep 'java.home'
+//! ```
+//! Then, we search for `jvm` as we have `JAVA_HOME`.
+//!
+//! On Windows, we also need to find `jvm.lib` file which is used while linking
+//! at build time. This file is typically placed in `$JAVA_HOME/lib` directory.
+
+use std::{
+ env,
+ path::{Path, PathBuf},
+ process::Command,
+};
+
+#[cfg(target_os = "windows")]
+const EXPECTED_JVM_FILENAME: &str = "jvm.dll";
+#[cfg(any(
+ target_os = "android",
+ target_os = "freebsd",
+ target_os = "linux",
+ target_os = "netbsd",
+ target_os = "openbsd"
+))]
+const EXPECTED_JVM_FILENAME: &str = "libjvm.so";
+#[cfg(target_os = "macos")]
+const EXPECTED_JVM_FILENAME: &str = "libjli.dylib";
+
+fn main() {
+ if cfg!(feature = "invocation") {
+ let java_home = match env::var("JAVA_HOME") {
+ Ok(java_home) => PathBuf::from(java_home),
+ Err(_) => find_java_home().expect(
+ "Failed to find Java home directory. \
+ Try setting JAVA_HOME",
+ ),
+ };
+
+ let libjvm_path =
+ find_libjvm(&java_home).expect("Failed to find libjvm.so. Check JAVA_HOME");
+
+ println!("cargo:rustc-link-search=native={}", libjvm_path.display());
+
+ // On Windows, we need additional file called `jvm.lib`
+ // and placed inside `JAVA_HOME\lib` directory.
+ if cfg!(windows) {
+ let lib_path = java_home.join("lib");
+ println!("cargo:rustc-link-search={}", lib_path.display());
+ }
+
+ println!("cargo:rerun-if-env-changed=JAVA_HOME");
+
+ // On MacOS, we need to link to libjli instead of libjvm as a workaround
+ // to a Java8 bug. See here for more information:
+ // https://bugs.openjdk.java.net/browse/JDK-7131356
+ if env::var("CARGO_CFG_TARGET_OS").unwrap() == "macos" {
+ println!("cargo:rustc-link-lib=dylib=jli");
+ } else {
+ println!("cargo:rustc-link-lib=dylib=jvm");
+ }
+ }
+}
+
+/// To find Java home directory, we call
+/// `java -XshowSettings:properties -version` command and parse its output to
+/// find the line `java.home=<some path>`.
+fn find_java_home() -> Option<PathBuf> {
+ Command::new("java")
+ .arg("-XshowSettings:properties")
+ .arg("-version")
+ .output()
+ .ok()
+ .and_then(|output| {
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ for line in stdout.lines().chain(stderr.lines()) {
+ if line.contains("java.home") {
+ let pos = line.find('=').unwrap() + 1;
+ let path = line[pos..].trim();
+ return Some(PathBuf::from(path));
+ }
+ }
+ None
+ })
+}
+
+fn find_libjvm<S: AsRef<Path>>(path: S) -> Option<PathBuf> {
+ let walker = walkdir::WalkDir::new(path).follow_links(true);
+
+ for entry in walker {
+ let entry = match entry {
+ Ok(entry) => entry,
+ Err(_e) => continue,
+ };
+
+ let file_name = entry.file_name().to_str().unwrap_or("");
+
+ if file_name == EXPECTED_JVM_FILENAME {
+ return entry.path().parent().map(Into::into);
+ }
+ }
+
+ None
+}
diff --git a/0.20.0/cargo_embargo.json b/0.20.0/cargo_embargo.json
new file mode 100644
index 0000000..9b3ad03
--- /dev/null
+++ b/0.20.0/cargo_embargo.json
@@ -0,0 +1,8 @@
+{
+ "apex_available": [
+ "//apex_available:platform",
+ "com.android.uwb"
+ ],
+ "min_sdk_version": "29",
+ "tests": true
+}
diff --git a/0.20.0/clippy.toml b/0.20.0/clippy.toml
new file mode 100644
index 0000000..00aaff1
--- /dev/null
+++ b/0.20.0/clippy.toml
@@ -0,0 +1,2 @@
+# Temporary increased until https://github.com/jni-rs/jni-rs/issues/184
+cognitive-complexity-threshold = 120
diff --git a/0.20.0/example/HelloWorld.h b/0.20.0/example/HelloWorld.h
new file mode 100644
index 0000000..b8bf1d4
--- /dev/null
+++ b/0.20.0/example/HelloWorld.h
@@ -0,0 +1,69 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class HelloWorld */
+
+#ifndef _Included_HelloWorld
+#define _Included_HelloWorld
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: HelloWorld
+ * Method: hello
+ * Signature: (Ljava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_HelloWorld_hello
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: HelloWorld
+ * Method: helloByte
+ * Signature: ([B)[B
+ */
+JNIEXPORT jbyteArray JNICALL Java_HelloWorld_helloByte
+ (JNIEnv *, jclass, jbyteArray);
+
+/*
+ * Class: HelloWorld
+ * Method: factAndCallMeBack
+ * Signature: (ILHelloWorld;)V
+ */
+JNIEXPORT void JNICALL Java_HelloWorld_factAndCallMeBack
+ (JNIEnv *, jclass, jint, jobject);
+
+/*
+ * Class: HelloWorld
+ * Method: counterNew
+ * Signature: (LHelloWorld;)J
+ */
+JNIEXPORT jlong JNICALL Java_HelloWorld_counterNew
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: HelloWorld
+ * Method: counterIncrement
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_HelloWorld_counterIncrement
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: HelloWorld
+ * Method: counterDestroy
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_HelloWorld_counterDestroy
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: HelloWorld
+ * Method: asyncComputation
+ * Signature: (LHelloWorld;)V
+ */
+JNIEXPORT void JNICALL Java_HelloWorld_asyncComputation
+ (JNIEnv *, jclass, jobject);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/0.20.0/example/HelloWorld.java b/0.20.0/example/HelloWorld.java
new file mode 100644
index 0000000..42d755e
--- /dev/null
+++ b/0.20.0/example/HelloWorld.java
@@ -0,0 +1,49 @@
+class HelloWorld {
+ private static native String hello(String input);
+ private static native byte[] helloByte(byte[] input);
+ private static native void factAndCallMeBack(int n, HelloWorld callback);
+
+ private static native long counterNew(HelloWorld callback);
+ private static native void counterIncrement(long counter_ptr);
+ private static native void counterDestroy(long counter_ptr);
+
+ private static native void asyncComputation(HelloWorld callback);
+
+ static {
+ System.loadLibrary("mylib");
+ }
+
+ public static void main(String[] args) {
+
+ String output = HelloWorld.hello("josh");
+ System.out.println(output);
+ byte[] outputByte = HelloWorld.helloByte("byte".getBytes());
+ System.out.println(outputByte);
+
+
+ HelloWorld.factAndCallMeBack(6, new HelloWorld());
+
+ long counter_ptr = counterNew(new HelloWorld());
+
+ for (int i = 0; i < 5; i++) {
+ counterIncrement(counter_ptr);
+ }
+
+ counterDestroy(counter_ptr);
+
+ System.out.println("Invoking asyncComputation (thread id = " + Thread.currentThread().getId() + ")");
+ asyncComputation(new HelloWorld());
+ }
+
+ public void factCallback(int res) {
+ System.out.println("factCallback: res = " + res);
+ }
+
+ public void counterCallback(int count) {
+ System.out.println("counterCallback: count = " + count);
+ }
+
+ public void asyncCallback(int progress) {
+ System.out.println("asyncCallback: thread id = " + Thread.currentThread().getId() + ", progress = " + progress + "%");
+ }
+}
diff --git a/0.20.0/example/Makefile b/0.20.0/example/Makefile
new file mode 100644
index 0000000..15f72c6
--- /dev/null
+++ b/0.20.0/example/Makefile
@@ -0,0 +1,10 @@
+java_run: lib
+ javac HelloWorld.java && java -Djava.library.path=mylib/target/debug/ HelloWorld
+
+.PHONY: lib
+
+javah:
+ javah HelloWorld
+
+lib:
+ cd mylib && cargo build
diff --git a/0.20.0/src/lib.rs b/0.20.0/src/lib.rs
new file mode 100644
index 0000000..dff0c20
--- /dev/null
+++ b/0.20.0/src/lib.rs
@@ -0,0 +1,231 @@
+#![warn(missing_docs)]
+#![allow(clippy::upper_case_acronyms)]
+// TODO: https://github.com/jni-rs/jni-rs/issues/348
+#![allow(clippy::not_unsafe_ptr_arg_deref)]
+
+//! # Safe JNI Bindings in Rust
+//!
+//! This crate provides a (mostly) safe way to implement methods in Java using
+//! the JNI. Because who wants to *actually* write Java?
+//!
+//! ## Getting Started
+//!
+//! Naturally, any ffi-related project is going to require some code in both
+//! languages that we're trying to make communicate. Java requires all native
+//! methods to adhere to the Java Native Interface (JNI), so we first have to
+//! define our function signature from Java, and then we can write Rust that
+//! will adhere to it.
+//!
+//! ### The Java side
+//!
+//! First, you need a Java class definition. `HelloWorld.java`:
+//!
+//! ```java
+//! class HelloWorld {
+//! // This declares that the static `hello` method will be provided
+//! // a native library.
+//! private static native String hello(String input);
+//!
+//! static {
+//! // This actually loads the shared object that we'll be creating.
+//! // The actual location of the .so or .dll may differ based on your
+//! // platform.
+//! System.loadLibrary("mylib");
+//! }
+//!
+//! // The rest is just regular ol' Java!
+//! public static void main(String[] args) {
+//! String output = HelloWorld.hello("josh");
+//! System.out.println(output);
+//! }
+//! }
+//! ```
+//!
+//! Compile this to a class file with `javac HelloWorld.java`.
+//!
+//! Trying to run it now will give us the error `Exception in thread "main"
+//! java.lang.UnsatisfiedLinkError: no mylib in java.library.path` since we
+//! haven't written our native code yet.
+//!
+//! To do that, first we need the name and type signature that our Rust function
+//! needs to adhere to. Luckily, the Java compiler can generate that for you!
+//! Run `javac -h . HelloWorld.java` and you'll get a `HelloWorld.h` output to your
+//! directory. It should look something like this:
+//!
+//! ```c
+//! /* DO NOT EDIT THIS FILE - it is machine generated */
+//! #include <jni.h>
+//! /* Header for class HelloWorld */
+//!
+//! #ifndef _Included_HelloWorld
+//! #define _Included_HelloWorld
+//! #ifdef __cplusplus
+//! extern "C" {
+//! #endif
+//! /*
+//! * Class: HelloWorld
+//! * Method: hello
+//! * Signature: (Ljava/lang/String;)Ljava/lang/String;
+//! */
+//! JNIEXPORT jstring JNICALL Java_HelloWorld_hello
+//! (JNIEnv *, jclass, jstring);
+//!
+//! #ifdef __cplusplus
+//! }
+//! #endif
+//! #endif
+//! ```
+//!
+//! It's a C header, but luckily for us, the types will mostly match up. Let's
+//! make our crate that's going to compile to our native library.
+//!
+//! ### The Rust side
+//!
+//! Create your crate with `cargo new mylib`. This will create a directory
+//! `mylib` that has everything needed to build an basic crate with `cargo`. We
+//! need to make a couple of changes to `Cargo.toml` before we do anything else.
+//!
+//! * Under `[dependencies]`, add `jni = "0.20.0"`
+//! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`.
+//!
+//! Now, if you run `cargo build` from inside the crate directory, you should
+//! see a `libmylib.so` (if you're on linux) or a `libmylib.dylib` (if you are on OSX) in the `target/debug`
+//! directory.
+//!
+//! The last thing we need to do is to define our exported method. Add this to
+//! your crate's `src/lib.rs`:
+//!
+//! ```rust,ignore
+//! // This is the interface to the JVM that we'll call the majority of our
+//! // methods on.
+//! use jni::JNIEnv;
+//!
+//! // These objects are what you should use as arguments to your native
+//! // function. They carry extra lifetime information to prevent them escaping
+//! // this context and getting used after being GC'd.
+//! use jni::objects::{JClass, JString};
+//!
+//! // This is just a pointer. We'll be returning it from our function. We
+//! // can't return one of the objects with lifetime information because the
+//! // lifetime checker won't let us.
+//! use jni::sys::jstring;
+//!
+//! // This keeps Rust from "mangling" the name and making it unique for this
+//! // crate.
+//! #[no_mangle]
+//! pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv,
+//! // This is the class that owns our static method. It's not going to be used,
+//! // but still must be present to match the expected signature of a static
+//! // native method.
+//! class: JClass,
+//! input: JString)
+//! -> jstring {
+//! // First, we have to get the string out of Java. Check out the `strings`
+//! // module for more info on how this works.
+//! let input: String =
+//! env.get_string(input).expect("Couldn't get java string!").into();
+//!
+//! // Then we have to create a new Java string to return. Again, more info
+//! // in the `strings` module.
+//! let output = env.new_string(format!("Hello, {}!", input))
+//! .expect("Couldn't create java string!");
+//!
+//! // Finally, extract the raw pointer to return.
+//! output.into_inner()
+//! }
+//! ```
+//!
+//! Note that the type signature for our function is almost identical to the one
+//! from the generated header, aside from our lifetime-carrying arguments.
+//!
+//! ### Final steps
+//!
+//! That's it! Build your crate and try to run your Java class again.
+//!
+//! ... Same error as before you say? Well that's because JVM is looking for
+//! `mylib` in all the wrong places. This will differ by platform thanks to
+//! different linker/loader semantics, but on Linux, you can simply `export
+//! LD_LIBRARY_PATH=/path/to/mylib/target/debug`. Now, you should get the
+//! expected output `Hello, josh!` from your Java class.
+//!
+//! ## Launching JVM from Rust
+//!
+//! It is possible to launch a JVM from a native process using the [Invocation API], provided
+//! by [`JavaVM`](struct.JavaVM.html).
+//!
+//! ## See Also
+//!
+//! ### Examples
+//! - [Example project][jni-rs-example]
+//! - Our [integration tests][jni-rs-its] and [benchmarks][jni-rs-benches]
+//!
+//! ### JNI Documentation
+//! - [Java Native Interface Specification][jni-spec]
+//! - [JNI tips][jni-tips] — general tips on JNI development and some Android-specific
+//!
+//! ### Open-Source Users
+//! - The Servo browser engine Android [port][users-servo]
+//! - The Exonum framework [Java Binding][users-ejb]
+//! - MaidSafe [Java Binding][users-maidsafe]
+//!
+//! ### Other Projects Simplifying Java and Rust Communication
+//! - Consider [JNR][projects-jnr] if you just need to use a native library with C interface
+//! - Watch OpenJDK [Project Panama][projects-panama] which aims to enable using native libraries
+//! with no JNI code
+//! - Consider [GraalVM][projects-graalvm] — a recently released VM that gives zero-cost
+//! interoperability between various languages (including Java and [Rust][graalvm-rust] compiled
+//! into LLVM-bitcode)
+//!
+//! [Invocation API]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html
+//! [jni-spec]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html
+//! [jni-tips]: https://developer.android.com/training/articles/perf-jni
+//! [jni-rs-example]: https://github.com/jni-rs/jni-rs/tree/master/example
+//! [jni-rs-its]: https://github.com/jni-rs/jni-rs/tree/master/tests
+//! [jni-rs-benches]: https://github.com/jni-rs/jni-rs/tree/master/benches
+//! [users-servo]: https://github.com/servo/servo/tree/master/ports/libsimpleservo
+//! [users-ejb]: https://github.com/exonum/exonum-java-binding/tree/master/exonum-java-binding/core/rust
+//! [users-maidsafe]: https://github.com/maidsafe/safe_client_libs/tree/master/safe_app_jni
+//! [projects-jnr]: https://github.com/jnr/jnr-ffi/
+//! [projects-graalvm]: http://www.graalvm.org/docs/why-graal/#for-java-programs
+//! [graalvm-rust]: http://www.graalvm.org/docs/reference-manual/languages/llvm/#running-rust
+//! [projects-panama]: https://jdk.java.net/panama/
+
+/// `jni-sys` re-exports
+pub mod sys;
+
+mod wrapper {
+ mod version;
+ pub use self::version::*;
+
+ #[macro_use]
+ mod macros;
+
+ /// Errors. Do you really need more explanation?
+ pub mod errors;
+
+ /// Descriptors for classes and method IDs.
+ pub mod descriptors;
+
+ /// Parser for java type signatures.
+ pub mod signature;
+
+ /// Wrappers for object pointers returned from the JVM.
+ pub mod objects;
+
+ /// String types for going to/from java strings.
+ pub mod strings;
+
+ /// Actual communication with the JVM.
+ mod jnienv;
+ pub use self::jnienv::*;
+
+ /// Java VM interface.
+ mod java_vm;
+ pub use self::java_vm::*;
+
+ /// Optional thread attachment manager.
+ mod executor;
+ pub use self::executor::*;
+}
+
+pub use wrapper::*;
diff --git a/0.20.0/src/sys.rs b/0.20.0/src/sys.rs
new file mode 100644
index 0000000..e1c7b74
--- /dev/null
+++ b/0.20.0/src/sys.rs
@@ -0,0 +1 @@
+pub use jni_sys::*;
diff --git a/0.20.0/src/wrapper/descriptors/class_desc.rs b/0.20.0/src/wrapper/descriptors/class_desc.rs
new file mode 100644
index 0000000..9b2e46b
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/class_desc.rs
@@ -0,0 +1,39 @@
+use crate::{
+ descriptors::Desc,
+ errors::*,
+ objects::{AutoLocal, GlobalRef, JClass, JObject},
+ strings::JNIString,
+ JNIEnv,
+};
+
+impl<'a, T> Desc<'a, JClass<'a>> for T
+where
+ T: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JClass<'a>> {
+ env.find_class(self)
+ }
+}
+
+impl<'a, 'b> Desc<'a, JClass<'a>> for JObject<'b> {
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JClass<'a>> {
+ env.get_object_class(self)
+ }
+}
+
+/// This conversion assumes that the `GlobalRef` is a pointer to a class object.
+impl<'a, 'b> Desc<'a, JClass<'b>> for &'b GlobalRef {
+ fn lookup(self, _: &JNIEnv<'a>) -> Result<JClass<'b>> {
+ Ok(self.as_obj().into())
+ }
+}
+
+/// This conversion assumes that the `AutoLocal` is a pointer to a class object.
+impl<'a, 'b, 'c> Desc<'a, JClass<'b>> for &'b AutoLocal<'c, '_>
+where
+ 'c: 'b,
+{
+ fn lookup(self, _: &JNIEnv<'a>) -> Result<JClass<'b>> {
+ Ok(self.as_obj().into())
+ }
+}
diff --git a/0.20.0/src/wrapper/descriptors/desc.rs b/0.20.0/src/wrapper/descriptors/desc.rs
new file mode 100644
index 0000000..2d7fd9d
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/desc.rs
@@ -0,0 +1,17 @@
+use crate::{errors::*, JNIEnv};
+
+/// Trait for things that can be looked up through the JNI via a descriptor.
+/// This will be something like the fully-qualified class name
+/// `java/lang/String` or a tuple containing a class descriptor, method name,
+/// and method signature. For convenience, this is also implemented for the
+/// concrete types themselves in addition to their descriptors.
+pub trait Desc<'a, T> {
+ /// Look up the concrete type from the JVM.
+ fn lookup(self, _: &JNIEnv<'a>) -> Result<T>;
+}
+
+impl<'a, T> Desc<'a, T> for T {
+ fn lookup(self, _: &JNIEnv<'a>) -> Result<T> {
+ Ok(self)
+ }
+}
diff --git a/0.20.0/src/wrapper/descriptors/exception_desc.rs b/0.20.0/src/wrapper/descriptors/exception_desc.rs
new file mode 100644
index 0000000..4911650
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/exception_desc.rs
@@ -0,0 +1,47 @@
+use crate::{
+ descriptors::Desc,
+ errors::*,
+ objects::{JClass, JObject, JThrowable, JValue},
+ strings::JNIString,
+ JNIEnv,
+};
+
+const DEFAULT_EXCEPTION_CLASS: &str = "java/lang/RuntimeException";
+
+impl<'a, 'c, C, M> Desc<'a, JThrowable<'a>> for (C, M)
+where
+ C: Desc<'a, JClass<'c>>,
+ M: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> {
+ let jmsg: JObject = env.new_string(self.1)?.into();
+ let obj: JThrowable = env
+ .new_object(self.0, "(Ljava/lang/String;)V", &[JValue::from(jmsg)])?
+ .into();
+ Ok(obj)
+ }
+}
+
+impl<'a> Desc<'a, JThrowable<'a>> for Exception {
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> {
+ (self.class, self.msg).lookup(env)
+ }
+}
+
+impl<'a, 'b> Desc<'a, JThrowable<'a>> for &'b str {
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> {
+ (DEFAULT_EXCEPTION_CLASS, self).lookup(env)
+ }
+}
+
+impl<'a> Desc<'a, JThrowable<'a>> for String {
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> {
+ (DEFAULT_EXCEPTION_CLASS, self).lookup(env)
+ }
+}
+
+impl<'a> Desc<'a, JThrowable<'a>> for JNIString {
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JThrowable<'a>> {
+ (DEFAULT_EXCEPTION_CLASS, self).lookup(env)
+ }
+}
diff --git a/0.20.0/src/wrapper/descriptors/field_desc.rs b/0.20.0/src/wrapper/descriptors/field_desc.rs
new file mode 100644
index 0000000..405c03b
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/field_desc.rs
@@ -0,0 +1,29 @@
+use crate::{
+ descriptors::Desc,
+ errors::*,
+ objects::{JClass, JFieldID, JStaticFieldID},
+ strings::JNIString,
+ JNIEnv,
+};
+
+impl<'a, 'c, T, U, V> Desc<'a, JFieldID> for (T, U, V)
+where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JFieldID> {
+ env.get_field_id(self.0, self.1, self.2)
+ }
+}
+
+impl<'a, 'c, T, U, V> Desc<'a, JStaticFieldID> for (T, U, V)
+where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticFieldID> {
+ env.get_static_field_id(self.0, self.1, self.2)
+ }
+}
diff --git a/0.20.0/src/wrapper/descriptors/method_desc.rs b/0.20.0/src/wrapper/descriptors/method_desc.rs
new file mode 100644
index 0000000..1b94e04
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/method_desc.rs
@@ -0,0 +1,39 @@
+use crate::{
+ descriptors::Desc,
+ errors::*,
+ objects::{JClass, JMethodID, JStaticMethodID},
+ strings::JNIString,
+ JNIEnv,
+};
+
+impl<'a, 'c, T, U, V> Desc<'a, JMethodID> for (T, U, V)
+where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> {
+ env.get_method_id(self.0, self.1, self.2)
+ }
+}
+
+impl<'a, 'c, T, Signature> Desc<'a, JMethodID> for (T, Signature)
+where
+ T: Desc<'a, JClass<'c>>,
+ Signature: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JMethodID> {
+ (self.0, "<init>", self.1).lookup(env)
+ }
+}
+
+impl<'a, 'c, T, U, V> Desc<'a, JStaticMethodID> for (T, U, V)
+where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+{
+ fn lookup(self, env: &JNIEnv<'a>) -> Result<JStaticMethodID> {
+ env.get_static_method_id(self.0, self.1, self.2)
+ }
+}
diff --git a/0.20.0/src/wrapper/descriptors/mod.rs b/0.20.0/src/wrapper/descriptors/mod.rs
new file mode 100644
index 0000000..e2fc230
--- /dev/null
+++ b/0.20.0/src/wrapper/descriptors/mod.rs
@@ -0,0 +1,14 @@
+mod desc;
+pub use self::desc::*;
+
+mod class_desc;
+pub use self::class_desc::*;
+
+mod method_desc;
+pub use self::method_desc::*;
+
+mod field_desc;
+pub use self::field_desc::*;
+
+mod exception_desc;
+pub use self::exception_desc::*;
diff --git a/0.20.0/src/wrapper/errors.rs b/0.20.0/src/wrapper/errors.rs
new file mode 100644
index 0000000..19b9d8c
--- /dev/null
+++ b/0.20.0/src/wrapper/errors.rs
@@ -0,0 +1,89 @@
+#![allow(missing_docs)]
+
+use thiserror::Error;
+
+use crate::sys;
+use crate::wrapper::signature::TypeSignature;
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, Error)]
+pub enum Error {
+ #[error("Invalid JValue type cast: {0}. Actual type: {1}")]
+ WrongJValueType(&'static str, &'static str),
+ #[error("Invalid constructor return type (must be void)")]
+ InvalidCtorReturn,
+ #[error("Invalid number of arguments passed to java method: {0}")]
+ InvalidArgList(TypeSignature),
+ #[error("Method not found: {name} {sig}")]
+ MethodNotFound { name: String, sig: String },
+ #[error("Field not found: {name} {sig}")]
+ FieldNotFound { name: String, sig: String },
+ #[error("Java exception was thrown")]
+ JavaException,
+ #[error("JNIEnv null method pointer for {0}")]
+ JNIEnvMethodNotFound(&'static str),
+ #[error("Null pointer in {0}")]
+ NullPtr(&'static str),
+ #[error("Null pointer deref in {0}")]
+ NullDeref(&'static str),
+ #[error("Mutex already locked")]
+ TryLock,
+ #[error("JavaVM null method pointer for {0}")]
+ JavaVMMethodNotFound(&'static str),
+ #[error("Field already set: {0}")]
+ FieldAlreadySet(String),
+ #[error("Throw failed with error code {0}")]
+ ThrowFailed(i32),
+ #[error("Parse failed for input: {1}")]
+ ParseFailed(#[source] combine::error::StringStreamError, String),
+ #[error("JNI call failed")]
+ JniCall(#[source] JniError),
+}
+
+#[derive(Debug, Error)]
+pub enum JniError {
+ #[error("Unknown error")]
+ Unknown,
+ #[error("Current thread is not attached to the Java VM")]
+ ThreadDetached,
+ #[error("JNI version error")]
+ WrongVersion,
+ #[error("Not enough memory")]
+ NoMemory,
+ #[error("VM already created")]
+ AlreadyCreated,
+ #[error("Invalid arguments")]
+ InvalidArguments,
+ #[error("Error code {0}")]
+ Other(sys::jint),
+}
+
+impl<T> From<::std::sync::TryLockError<T>> for Error {
+ fn from(_: ::std::sync::TryLockError<T>) -> Self {
+ Error::TryLock
+ }
+}
+
+pub fn jni_error_code_to_result(code: sys::jint) -> Result<()> {
+ match code {
+ sys::JNI_OK => Ok(()),
+ sys::JNI_ERR => Err(JniError::Unknown),
+ sys::JNI_EDETACHED => Err(JniError::ThreadDetached),
+ sys::JNI_EVERSION => Err(JniError::WrongVersion),
+ sys::JNI_ENOMEM => Err(JniError::NoMemory),
+ sys::JNI_EEXIST => Err(JniError::AlreadyCreated),
+ sys::JNI_EINVAL => Err(JniError::InvalidArguments),
+ _ => Err(JniError::Other(code)),
+ }
+ .map_err(Error::JniCall)
+}
+
+pub struct Exception {
+ pub class: String,
+ pub msg: String,
+}
+
+pub trait ToException {
+ fn to_exception(&self) -> Exception;
+}
diff --git a/0.20.0/src/wrapper/executor.rs b/0.20.0/src/wrapper/executor.rs
new file mode 100644
index 0000000..9ef12db
--- /dev/null
+++ b/0.20.0/src/wrapper/executor.rs
@@ -0,0 +1,96 @@
+use std::sync::Arc;
+
+use crate::{errors::*, objects::JObject, JNIEnv, JavaVM};
+
+/// The capacity of local frames, allocated for attached threads by default. Same as the default
+/// value Hotspot uses when calling native Java methods.
+pub const DEFAULT_LOCAL_FRAME_CAPACITY: i32 = 32;
+
+/// Thread attachment manager. It allows to execute closures in attached threads with automatic
+/// local references management done with `with_local_frame`. It combines the performance benefits
+/// of permanent attaches whilst removing the risk of local references leaks if used consistently.
+///
+/// Although all locals are freed on closure exit, it might be needed to manually free
+/// locals _inside_ the closure if an unbounded number of them is created (e.g., in a loop).
+/// See ["Local Reference Management"](struct.JavaVM.html#local-reference-management) for details.
+///
+/// Threads using the Executor are attached on the first invocation as daemons,
+/// hence they do not block JVM exit. Finished threads detach automatically.
+///
+/// ## Example
+///
+/// ```rust
+/// # use jni::errors;
+/// # //
+/// # fn main() -> errors::Result<()> {
+/// # // Ignore this test without invocation feature, so that simple `cargo test` works
+/// # #[cfg(feature = "invocation")] {
+/// # //
+/// # use jni::{objects::JValue, Executor, InitArgsBuilder, JavaVM, sys::jint};
+/// # use std::sync::Arc;
+/// # //
+/// # let jvm_args = InitArgsBuilder::new()
+/// # .build()
+/// # .unwrap();
+/// # // Create a new VM
+/// # let jvm = Arc::new(JavaVM::new(jvm_args)?);
+///
+/// let exec = Executor::new(jvm);
+///
+/// let val: jint = exec.with_attached(|env| {
+/// let x = JValue::from(-10);
+/// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?
+/// .i()?;
+/// Ok(val)
+/// })?;
+///
+/// assert_eq!(val, 10);
+///
+/// # }
+/// # Ok(()) }
+/// ```
+#[derive(Clone)]
+pub struct Executor {
+ vm: Arc<JavaVM>,
+}
+
+impl Executor {
+ /// Creates new Executor with specified JVM.
+ pub fn new(vm: Arc<JavaVM>) -> Self {
+ Self { vm }
+ }
+
+ /// Executes a provided closure, making sure that the current thread
+ /// is attached to the JVM. Additionally ensures that local object references are freed after
+ /// call.
+ ///
+ /// Allocates a local frame with the specified capacity.
+ pub fn with_attached_capacity<F, R>(&self, capacity: i32, f: F) -> Result<R>
+ where
+ F: FnOnce(&JNIEnv) -> Result<R>,
+ {
+ assert!(capacity > 0, "capacity should be a positive integer");
+
+ let jni_env = self.vm.attach_current_thread_as_daemon()?;
+ let mut result = None;
+ jni_env.with_local_frame(capacity, || {
+ result = Some(f(&jni_env));
+ Ok(JObject::null())
+ })?;
+
+ result.expect("The result should be Some or this line shouldn't be reached")
+ }
+
+ /// Executes a provided closure, making sure that the current thread
+ /// is attached to the JVM. Additionally ensures that local object references are freed after
+ /// call.
+ ///
+ /// Allocates a local frame with
+ /// [the default capacity](constant.DEFAULT_LOCAL_FRAME_CAPACITY.html).
+ pub fn with_attached<F, R>(&self, f: F) -> Result<R>
+ where
+ F: FnOnce(&JNIEnv) -> Result<R>,
+ {
+ self.with_attached_capacity(DEFAULT_LOCAL_FRAME_CAPACITY, f)
+ }
+}
diff --git a/0.20.0/src/wrapper/java_vm/init_args.rs b/0.20.0/src/wrapper/java_vm/init_args.rs
new file mode 100644
index 0000000..c1fd6e1
--- /dev/null
+++ b/0.20.0/src/wrapper/java_vm/init_args.rs
@@ -0,0 +1,138 @@
+use std::{ffi::CString, os::raw::c_void};
+
+use thiserror::Error;
+
+use crate::{
+ sys::{JavaVMInitArgs, JavaVMOption},
+ JNIVersion,
+};
+
+/// Errors that can occur when invoking a [`JavaVM`](super::vm::JavaVM) with the
+/// [Invocation API](https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html).
+#[derive(Debug, Error)]
+pub enum JvmError {
+ /// An internal `0` byte was found when constructing a string.
+ #[error("internal null in option: {0}")]
+ NullOptString(String),
+}
+
+/// Builder for JavaVM InitArgs.
+///
+/// *This API requires "invocation" feature to be enabled,
+/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
+#[derive(Debug)]
+pub struct InitArgsBuilder {
+ opts: Vec<String>,
+ ignore_unrecognized: bool,
+ version: JNIVersion,
+}
+
+impl Default for InitArgsBuilder {
+ fn default() -> Self {
+ InitArgsBuilder {
+ opts: vec![],
+ ignore_unrecognized: false,
+ version: JNIVersion::V8,
+ }
+ }
+}
+
+impl InitArgsBuilder {
+ /// Create a new default InitArgsBuilder
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// Add an option to the init args
+ ///
+ /// The `vfprintf`, `abort`, and `exit` options are unsupported at this time.
+ pub fn option(self, opt_string: &str) -> Self {
+ let mut s = self;
+
+ match opt_string {
+ "vfprintf" | "abort" | "exit" => return s,
+ _ => {}
+ }
+
+ s.opts.push(opt_string.into());
+
+ s
+ }
+
+ /// Set JNI version for the init args
+ ///
+ /// Default: V8
+ pub fn version(self, version: JNIVersion) -> Self {
+ let mut s = self;
+ s.version = version;
+ s
+ }
+
+ /// Set the `ignoreUnrecognized` init arg flag
+ ///
+ /// If ignoreUnrecognized is true, JavaVM::new ignores all unrecognized option strings that
+ /// begin with "-X" or "_". If ignoreUnrecognized is false, JavaVM::new returns Err as soon as
+ /// it encounters any unrecognized option strings.
+ ///
+ /// Default: `false`
+ pub fn ignore_unrecognized(self, ignore: bool) -> Self {
+ let mut s = self;
+ s.ignore_unrecognized = ignore;
+ s
+ }
+
+ /// Build the `InitArgs`
+ ///
+ /// This will check for internal nulls in the option strings and will return
+ /// an error if one is found.
+ pub fn build(self) -> Result<InitArgs, JvmError> {
+ let mut opts = Vec::with_capacity(self.opts.len());
+ for opt in self.opts {
+ let option_string =
+ CString::new(opt.as_str()).map_err(|_| JvmError::NullOptString(opt))?;
+ let jvm_opt = JavaVMOption {
+ optionString: option_string.into_raw(),
+ extraInfo: ::std::ptr::null_mut(),
+ };
+ opts.push(jvm_opt);
+ }
+
+ Ok(InitArgs {
+ inner: JavaVMInitArgs {
+ version: self.version.into(),
+ ignoreUnrecognized: self.ignore_unrecognized as _,
+ options: opts.as_ptr() as _,
+ nOptions: opts.len() as _,
+ },
+ opts,
+ })
+ }
+
+ /// Returns collected options
+ pub fn options(&self) -> Vec<String> {
+ self.opts.clone()
+ }
+}
+
+/// JavaVM InitArgs.
+///
+/// *This API requires "invocation" feature to be enabled,
+/// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
+pub struct InitArgs {
+ inner: JavaVMInitArgs,
+ opts: Vec<JavaVMOption>,
+}
+
+impl InitArgs {
+ pub(crate) fn inner_ptr(&self) -> *mut c_void {
+ &self.inner as *const _ as _
+ }
+}
+
+impl Drop for InitArgs {
+ fn drop(&mut self) {
+ for opt in self.opts.iter() {
+ unsafe { CString::from_raw(opt.optionString) };
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/java_vm/mod.rs b/0.20.0/src/wrapper/java_vm/mod.rs
new file mode 100644
index 0000000..64e4c6c
--- /dev/null
+++ b/0.20.0/src/wrapper/java_vm/mod.rs
@@ -0,0 +1,7 @@
+#[cfg(feature = "invocation")]
+mod init_args;
+#[cfg(feature = "invocation")]
+pub use self::init_args::*;
+
+mod vm;
+pub use self::vm::*;
diff --git a/0.20.0/src/wrapper/java_vm/vm.rs b/0.20.0/src/wrapper/java_vm/vm.rs
new file mode 100644
index 0000000..e127634
--- /dev/null
+++ b/0.20.0/src/wrapper/java_vm/vm.rs
@@ -0,0 +1,455 @@
+use std::{
+ cell::RefCell,
+ ops::Deref,
+ ptr,
+ sync::atomic::{AtomicUsize, Ordering},
+ thread::current,
+};
+
+use log::{debug, error};
+
+use crate::{errors::*, sys, JNIEnv};
+
+#[cfg(feature = "invocation")]
+use crate::InitArgs;
+use std::thread::Thread;
+
+/// The Java VM, providing [Invocation API][invocation-api] support.
+///
+/// The JavaVM can be obtained either via [`JNIEnv#get_java_vm`][get-vm] in an already attached
+/// thread, or it can be [launched](#launching-jvm-from-rust) from Rust via `JavaVM#new`.
+///
+/// ## Attaching Native Threads
+///
+/// A native thread must «attach» itself to be able to call Java methods outside of a native Java
+/// method. This library provides two modes of attachment, each ensuring the thread is promptly
+/// detached:
+/// * A scoped attachment with [`attach_current_thread`][act].
+/// The thread will automatically detach itself once the returned guard is dropped.
+/// * A permanent attachment with [`attach_current_thread_permanently`][actp]
+/// or [`attach_current_thread_as_daemon`][actd].
+/// The thread will automatically detach itself before it terminates.
+///
+/// As attachment and detachment of a thread is an expensive operation, the scoped attachment
+/// shall be used if happens infrequently. If you have an undefined scope where you need
+/// to use `JNIEnv` and cannot keep the `AttachGuard`, consider attaching the thread
+/// permanently.
+///
+/// ### Local Reference Management
+///
+/// Remember that the native thread attached to the VM **must** manage the local references
+/// properly, i.e., do not allocate an excessive number of references and release them promptly
+/// when they are no longer needed to enable the GC to collect them. A common approach is to use
+/// an appropriately-sized local frame for larger code fragments
+/// (see [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) and [Executor](#executor))
+/// and [auto locals](struct.JNIEnv.html#method.auto_local) in loops.
+///
+/// See also the [JNI specification][spec-references] for details on referencing Java objects.
+///
+/// ### Executor
+///
+/// Jni-rs provides an [`Executor`](struct.Executor.html) — a helper struct that allows to
+/// execute a closure with `JNIEnv`. It combines the performance benefits of permanent attaches
+/// *and* automatic local reference management. Prefer it to manual permanent attaches if
+/// they happen in various parts of the code to reduce the burden of local reference management.
+///
+/// ## Launching JVM from Rust
+///
+/// To [launch][launch-vm] a JVM from a native process, enable the `invocation` feature
+/// in the Cargo.toml:
+///
+/// ```toml
+/// jni = { version = "0.20.0", features = ["invocation"] }
+/// ```
+///
+/// The application will require linking to the dynamic `jvm` library, which is distributed
+/// with the JVM, and allow to use `JavaVM#new`:
+///
+/// ```rust
+/// # use jni::errors;
+/// # //
+/// # fn main() -> errors::Result<()> {
+/// # // Ignore this test without invocation feature, so that simple `cargo test` works
+/// # #[cfg(feature = "invocation")] {
+/// # //
+/// # use jni::{AttachGuard, objects::JValue, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, sys::jint};
+/// # //
+/// // Build the VM properties
+/// let jvm_args = InitArgsBuilder::new()
+/// // Pass the JNI API version (default is 8)
+/// .version(JNIVersion::V8)
+/// // You can additionally pass any JVM options (standard, like a system property,
+/// // or VM-specific).
+/// // Here we enable some extra JNI checks useful during development
+/// .option("-Xcheck:jni")
+/// .build()
+/// .unwrap();
+///
+/// // Create a new VM
+/// let jvm = JavaVM::new(jvm_args)?;
+///
+/// // Attach the current thread to call into Java — see extra options in
+/// // "Attaching Native Threads" section.
+/// //
+/// // This method returns the guard that will detach the current thread when dropped,
+/// // also freeing any local references created in it
+/// let env = jvm.attach_current_thread()?;
+///
+/// // Call Java Math#abs(-10)
+/// let x = JValue::from(-10);
+/// let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?
+/// .i()?;
+///
+/// assert_eq!(val, 10);
+///
+/// # }
+/// # Ok(()) }
+/// ```
+///
+/// During build time, the JVM installation path is determined:
+/// 1. By `JAVA_HOME` environment variable, if it is set.
+/// 2. Otherwise — from `java` output.
+///
+/// It is recommended to set `JAVA_HOME` to have reproducible builds,
+/// especially, in case of multiple VMs installed.
+///
+/// At application run time, you must specify the path
+/// to the `jvm` library so that the loader can locate it.
+/// * On **Windows**, append the path to `jvm.dll` to `PATH` environment variable.
+/// * On **MacOS**, append the path to `libjvm.dylib` to `LD_LIBRARY_PATH` environment variable.
+/// * On **Linux**, append the path to `libjvm.so` to `LD_LIBRARY_PATH` environment variable.
+///
+/// The exact relative path to `jvm` library is version-specific.
+///
+/// For more information on linking — see documentation
+/// in [build.rs](https://github.com/jni-rs/jni-rs/tree/master/build.rs).
+///
+/// [invocation-api]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html
+/// [get-vm]: struct.JNIEnv.html#method.get_java_vm
+/// [launch-vm]: struct.JavaVM.html#method.new
+/// [act]: struct.JavaVM.html#method.attach_current_thread
+/// [actp]: struct.JavaVM.html#method.attach_current_thread_permanently
+/// [actd]: struct.JavaVM.html#method.attach_current_thread_as_daemon
+/// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects
+#[repr(transparent)]
+#[derive(Debug)]
+pub struct JavaVM(*mut sys::JavaVM);
+
+unsafe impl Send for JavaVM {}
+unsafe impl Sync for JavaVM {}
+
+impl JavaVM {
+ /// Launch a new JavaVM using the provided init args.
+ ///
+ /// Unlike original JNI API, the main thread (the thread from which this method is called) will
+ /// not be attached to JVM. You must explicitly use `attach_current_thread…` methods (refer
+ /// to [Attaching Native Threads section](#attaching-native-threads)).
+ ///
+ /// *This API requires "invocation" feature to be enabled,
+ /// see ["Launching JVM from Rust"](struct.JavaVM.html#launching-jvm-from-rust).*
+ #[cfg(feature = "invocation")]
+ pub fn new(args: InitArgs) -> Result<Self> {
+ use std::os::raw::c_void;
+
+ let mut ptr: *mut sys::JavaVM = ::std::ptr::null_mut();
+ let mut env: *mut sys::JNIEnv = ::std::ptr::null_mut();
+
+ unsafe {
+ jni_error_code_to_result(sys::JNI_CreateJavaVM(
+ &mut ptr as *mut _,
+ &mut env as *mut *mut sys::JNIEnv as *mut *mut c_void,
+ args.inner_ptr(),
+ ))?;
+
+ let vm = Self::from_raw(ptr)?;
+ java_vm_unchecked!(vm.0, DetachCurrentThread);
+
+ Ok(vm)
+ }
+ }
+
+ /// Create a JavaVM from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer retrieved from the `JNI_CreateJavaVM` JNI function. Only does null check.
+ pub unsafe fn from_raw(ptr: *mut sys::JavaVM) -> Result<Self> {
+ non_null!(ptr, "from_raw ptr argument");
+ Ok(JavaVM(ptr))
+ }
+
+ /// Returns underlying `sys::JavaVM` interface.
+ pub fn get_java_vm_pointer(&self) -> *mut sys::JavaVM {
+ self.0
+ }
+
+ /// Attaches the current thread to the JVM. Calling this for a thread that is already attached
+ /// is a no-op.
+ ///
+ /// The thread will detach itself automatically when it exits.
+ ///
+ /// Attached threads [block JVM exit][block]. If it is not desirable — consider using
+ /// [`attach_current_thread_as_daemon`][attach-as-daemon].
+ ///
+ /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm
+ /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon
+ pub fn attach_current_thread_permanently(&self) -> Result<JNIEnv> {
+ match self.get_env() {
+ Ok(env) => Ok(env),
+ Err(_) => self.attach_current_thread_impl(ThreadType::Normal),
+ }
+ }
+
+ /// Attaches the current thread to the Java VM. The returned `AttachGuard`
+ /// can be dereferenced to a `JNIEnv` and automatically detaches the thread
+ /// when dropped. Calling this in a thread that is already attached is a no-op, and
+ /// will neither change its daemon status nor prematurely detach it.
+ ///
+ /// Attached threads [block JVM exit][block].
+ ///
+ /// Attaching and detaching a thread is an expensive operation. If you use it frequently
+ /// in the same threads, consider either [attaching them permanently][attach-as-daemon],
+ /// or, if the scope where you need the `JNIEnv` is well-defined, keeping the returned guard.
+ ///
+ /// [block]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/invocation.html#unloading-the-vm
+ /// [attach-as-daemon]: struct.JavaVM.html#method.attach_current_thread_as_daemon
+ pub fn attach_current_thread(&self) -> Result<AttachGuard> {
+ match self.get_env() {
+ Ok(env) => Ok(AttachGuard::new_nested(env)),
+ Err(_) => {
+ let env = self.attach_current_thread_impl(ThreadType::Normal)?;
+ Ok(AttachGuard::new(env))
+ }
+ }
+ }
+
+ /// Detaches current thread from the JVM. This operation is _rarely_ appropriate to use,
+ /// because the attachment methods [ensure](#attaching-native-threads) that the thread is
+ /// promptly detached.
+ ///
+ /// Detaching a non-attached thread is a no-op.
+ ///
+ /// __Any existing `JNIEnv`s and `AttachGuard`s created in the calling thread
+ /// will be invalidated after this method completes. It is the__ caller’s __responsibility
+ /// to ensure that no JNI calls are subsequently performed on these objects.__
+ /// Failure to do so will result in unspecified errors, possibly, the process crash.
+ ///
+ /// Given some care is exercised, this method can be used to detach permanently attached
+ /// threads _before_ they exit (when automatic detachment occurs). However, it is
+ /// never appropriate to use it with the scoped attachment (`attach_current_thread`).
+ // This method is hidden because it is almost never needed and its use requires some
+ // extra care. Its status might be reconsidered if we learn of any use cases that require it.
+ #[doc(hidden)]
+ pub fn detach_current_thread(&self) {
+ InternalAttachGuard::clear_tls();
+ }
+
+ /// Attaches the current thread to the Java VM as a _daemon_. Calling this in a thread
+ /// that is already attached is a no-op, and will not change its status to a daemon thread.
+ ///
+ /// The thread will detach itself automatically when it exits.
+ pub fn attach_current_thread_as_daemon(&self) -> Result<JNIEnv> {
+ match self.get_env() {
+ Ok(env) => Ok(env),
+ Err(_) => self.attach_current_thread_impl(ThreadType::Daemon),
+ }
+ }
+
+ /// Returns the current number of threads attached to the JVM.
+ ///
+ /// This method is provided mostly for diagnostic purposes.
+ pub fn threads_attached(&self) -> usize {
+ ATTACHED_THREADS.load(Ordering::SeqCst)
+ }
+
+ /// Get the `JNIEnv` associated with the current thread, or
+ /// `ErrorKind::Detached`
+ /// if the current thread is not attached to the java VM.
+ pub fn get_env(&self) -> Result<JNIEnv> {
+ let mut ptr = ptr::null_mut();
+ unsafe {
+ let res = java_vm_unchecked!(self.0, GetEnv, &mut ptr, sys::JNI_VERSION_1_1);
+ jni_error_code_to_result(res)?;
+
+ JNIEnv::from_raw(ptr as *mut sys::JNIEnv)
+ }
+ }
+
+ /// Creates `InternalAttachGuard` and attaches current thread.
+ fn attach_current_thread_impl(&self, thread_type: ThreadType) -> Result<JNIEnv> {
+ let guard = InternalAttachGuard::new(self.get_java_vm_pointer());
+ let env_ptr = unsafe {
+ if thread_type == ThreadType::Daemon {
+ guard.attach_current_thread_as_daemon()?
+ } else {
+ guard.attach_current_thread()?
+ }
+ };
+
+ InternalAttachGuard::fill_tls(guard);
+
+ unsafe { JNIEnv::from_raw(env_ptr as *mut sys::JNIEnv) }
+ }
+}
+
+thread_local! {
+ static THREAD_ATTACH_GUARD: RefCell<Option<InternalAttachGuard>> = RefCell::new(None)
+}
+
+static ATTACHED_THREADS: AtomicUsize = AtomicUsize::new(0);
+
+/// A RAII implementation of scoped guard which detaches the current thread
+/// when dropped. The attached `JNIEnv` can be accessed through this guard
+/// via its `Deref` implementation.
+pub struct AttachGuard<'a> {
+ env: JNIEnv<'a>,
+ should_detach: bool,
+}
+
+impl<'a> AttachGuard<'a> {
+ /// AttachGuard created with this method will detach current thread on drop
+ fn new(env: JNIEnv<'a>) -> Self {
+ Self {
+ env,
+ should_detach: true,
+ }
+ }
+
+ /// AttachGuard created with this method will not detach current thread on drop, which is
+ /// the case for nested attaches.
+ fn new_nested(env: JNIEnv<'a>) -> Self {
+ Self {
+ env,
+ should_detach: false,
+ }
+ }
+}
+
+impl<'a> Deref for AttachGuard<'a> {
+ type Target = JNIEnv<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.env
+ }
+}
+
+impl<'a> Drop for AttachGuard<'a> {
+ fn drop(&mut self) {
+ if self.should_detach {
+ InternalAttachGuard::clear_tls();
+ }
+ }
+}
+
+#[derive(PartialEq)]
+enum ThreadType {
+ Normal,
+ Daemon,
+}
+
+#[derive(Debug)]
+struct InternalAttachGuard {
+ java_vm: *mut sys::JavaVM,
+ /// A call std::thread::current() function can panic in case the local data has been destroyed
+ /// before the thead local variables. The possibility of this happening depends on the platform
+ /// implementation of the crate::sys_common::thread_local_dtor::register_dtor_fallback.
+ /// The InternalAttachGuard is a thread-local vairable, so capture the thread meta-data
+ /// during creation
+ thread: Thread,
+}
+
+impl InternalAttachGuard {
+ fn new(java_vm: *mut sys::JavaVM) -> Self {
+ Self {
+ java_vm,
+ thread: current(),
+ }
+ }
+
+ /// Stores guard in thread local storage.
+ fn fill_tls(guard: InternalAttachGuard) {
+ THREAD_ATTACH_GUARD.with(move |f| {
+ *f.borrow_mut() = Some(guard);
+ });
+ }
+
+ /// Clears thread local storage, dropping the InternalAttachGuard and causing detach of
+ /// the current thread.
+ fn clear_tls() {
+ THREAD_ATTACH_GUARD.with(move |f| {
+ *f.borrow_mut() = None;
+ });
+ }
+
+ unsafe fn attach_current_thread(&self) -> Result<*mut sys::JNIEnv> {
+ let mut env_ptr = ptr::null_mut();
+ let res = java_vm_unchecked!(
+ self.java_vm,
+ AttachCurrentThread,
+ &mut env_ptr,
+ ptr::null_mut()
+ );
+ jni_error_code_to_result(res)?;
+
+ ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst);
+
+ debug!(
+ "Attached thread {} ({:?}). {} threads attached",
+ self.thread.name().unwrap_or_default(),
+ self.thread.id(),
+ ATTACHED_THREADS.load(Ordering::SeqCst)
+ );
+
+ Ok(env_ptr as *mut sys::JNIEnv)
+ }
+
+ unsafe fn attach_current_thread_as_daemon(&self) -> Result<*mut sys::JNIEnv> {
+ let mut env_ptr = ptr::null_mut();
+ let res = java_vm_unchecked!(
+ self.java_vm,
+ AttachCurrentThreadAsDaemon,
+ &mut env_ptr,
+ ptr::null_mut()
+ );
+ jni_error_code_to_result(res)?;
+
+ ATTACHED_THREADS.fetch_add(1, Ordering::SeqCst);
+
+ debug!(
+ "Attached daemon thread {} ({:?}). {} threads attached",
+ self.thread.name().unwrap_or_default(),
+ self.thread.id(),
+ ATTACHED_THREADS.load(Ordering::SeqCst)
+ );
+
+ Ok(env_ptr as *mut sys::JNIEnv)
+ }
+
+ fn detach(&mut self) -> Result<()> {
+ unsafe {
+ java_vm_unchecked!(self.java_vm, DetachCurrentThread);
+ }
+ ATTACHED_THREADS.fetch_sub(1, Ordering::SeqCst);
+ debug!(
+ "Detached thread {} ({:?}). {} threads remain attached",
+ self.thread.name().unwrap_or_default(),
+ self.thread.id(),
+ ATTACHED_THREADS.load(Ordering::SeqCst)
+ );
+
+ Ok(())
+ }
+}
+
+impl Drop for InternalAttachGuard {
+ fn drop(&mut self) {
+ if let Err(e) = self.detach() {
+ error!(
+ "Error detaching current thread: {:#?}\nThread {} id={:?}",
+ e,
+ self.thread.name().unwrap_or_default(),
+ self.thread.id(),
+ );
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/jnienv.rs b/0.20.0/src/wrapper/jnienv.rs
new file mode 100644
index 0000000..f0da5d2
--- /dev/null
+++ b/0.20.0/src/wrapper/jnienv.rs
@@ -0,0 +1,2334 @@
+use std::{
+ marker::PhantomData,
+ os::raw::{c_char, c_void},
+ ptr, str,
+ str::FromStr,
+ sync::{Mutex, MutexGuard},
+};
+
+use log::warn;
+
+use crate::signature::ReturnType;
+use crate::{
+ descriptors::Desc,
+ errors::*,
+ objects::{
+ AutoArray, AutoLocal, AutoPrimitiveArray, GlobalRef, JByteBuffer, JClass, JFieldID, JList,
+ JMap, JMethodID, JObject, JStaticFieldID, JStaticMethodID, JString, JThrowable, JValue,
+ ReleaseMode, TypeArray,
+ },
+ signature::{JavaType, Primitive, TypeSignature},
+ strings::{JNIString, JavaStr},
+ sys::{
+ self, jarray, jboolean, jbooleanArray, jbyte, jbyteArray, jchar, jcharArray, jdouble,
+ jdoubleArray, jfloat, jfloatArray, jint, jintArray, jlong, jlongArray, jobjectArray,
+ jshort, jshortArray, jsize, jvalue, JNINativeMethod,
+ },
+ JNIVersion, JavaVM,
+};
+
+/// FFI-compatible JNIEnv struct. You can safely use this as the JNIEnv argument
+/// to exported methods that will be called by java. This is where most of the
+/// magic happens. All methods on this object are wrappers around JNI functions,
+/// so the documentation on their behavior is still pretty applicable.
+///
+/// # Exception handling
+///
+/// Since we're calling into the JVM with this, many methods also have the
+/// potential to cause an exception to get thrown. If this is the case, an `Err`
+/// result will be returned with the error kind `JavaException`. Note that this
+/// will _not_ clear the exception - it's up to the caller to decide whether to
+/// do so or to let it continue being thrown.
+///
+/// ## `null` Java references
+/// `null` Java references are handled by the following rules:
+/// - If a `null` Java reference is passed to a method that expects a non-`null`
+/// argument, an `Err` result with the kind `NullPtr` is returned.
+/// - If a JNI function returns `null` to indicate an error (e.g. `new_int_array`),
+/// it is converted to `Err`/`NullPtr` or, where possible, to a more applicable
+/// error type, such as `MethodNotFound`. If the JNI function also throws
+/// an exception, the `JavaException` error kind will be preferred.
+/// - If a JNI function may return `null` Java reference as one of possible reference
+/// values (e.g., `get_object_array_element` or `get_field_unchecked`),
+/// it is converted to `JObject::null()`.
+///
+/// # Checked and unchecked methods
+///
+/// Some of the methods come in two versions: checked (e.g. `call_method`) and
+/// unchecked (e.g. `call_method_unchecked`). Under the hood, checked methods
+/// perform some checks to ensure the validity of provided signatures, names
+/// and arguments, and then call the corresponding unchecked method.
+///
+/// Checked methods are more flexible as they allow passing class names
+/// and method/field descriptors as strings and may perform lookups
+/// of class objects and method/field ids for you, also performing
+/// all the needed precondition checks. However, these lookup operations
+/// are expensive, so if you need to call the same method (or access
+/// the same field) multiple times, it is
+/// [recommended](https://docs.oracle.com/en/java/javase/11/docs/specs/jni/design.html#accessing-fields-and-methods)
+/// to cache the instance of the class and the method/field id, e.g.
+/// - in loops
+/// - when calling the same Java callback repeatedly.
+///
+/// If you do not cache references to classes and method/field ids,
+/// you will *not* benefit from the unchecked methods.
+///
+/// Calling unchecked methods with invalid arguments and/or invalid class and
+/// method descriptors may lead to segmentation fault.
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+pub struct JNIEnv<'a> {
+ internal: *mut sys::JNIEnv,
+ lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> JNIEnv<'a> {
+ /// Create a JNIEnv from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer retrieved from the `GetEnv` JNI function. Only does a null check.
+ pub unsafe fn from_raw(ptr: *mut sys::JNIEnv) -> Result<Self> {
+ non_null!(ptr, "from_raw ptr argument");
+ Ok(JNIEnv {
+ internal: ptr,
+ lifetime: PhantomData,
+ })
+ }
+
+ /// Get the java version that we're being executed from.
+ pub fn get_version(&self) -> Result<JNIVersion> {
+ Ok(jni_unchecked!(self.internal, GetVersion).into())
+ }
+
+ /// Load a class from a buffer of raw class data. The name of the class must match the name
+ /// encoded within the class file data.
+ pub fn define_class<S>(&self, name: S, loader: JObject<'a>, buf: &[u8]) -> Result<JClass<'a>>
+ where
+ S: Into<JNIString>,
+ {
+ let name = name.into();
+ self.define_class_impl(name.as_ptr(), loader, buf)
+ }
+
+ /// Load a class from a buffer of raw class data. The name of the class is inferred from the
+ /// buffer.
+ pub fn define_unnamed_class<S>(&self, loader: JObject<'a>, buf: &[u8]) -> Result<JClass<'a>>
+ where
+ S: Into<JNIString>,
+ {
+ self.define_class_impl(ptr::null(), loader, buf)
+ }
+
+ fn define_class_impl(
+ &self,
+ name: *const c_char,
+ loader: JObject<'a>,
+ buf: &[u8],
+ ) -> Result<JClass<'a>> {
+ let class = jni_non_null_call!(
+ self.internal,
+ DefineClass,
+ name,
+ loader.into_raw(),
+ buf.as_ptr() as *const jbyte,
+ buf.len() as jsize
+ );
+ Ok(unsafe { JClass::from_raw(class) })
+ }
+
+ /// Look up a class by name.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let class: JClass<'a> = env.find_class("java/lang/String");
+ /// ```
+ pub fn find_class<S>(&self, name: S) -> Result<JClass<'a>>
+ where
+ S: Into<JNIString>,
+ {
+ let name = name.into();
+ let class = jni_non_null_call!(self.internal, FindClass, name.as_ptr());
+ Ok(unsafe { JClass::from_raw(class) })
+ }
+
+ /// Returns the superclass for a particular class OR `JObject::null()` for `java.lang.Object` or
+ /// an interface. As with `find_class`, takes a descriptor.
+ pub fn get_superclass<'c, T>(&self, class: T) -> Result<JClass<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ Ok(unsafe {
+ JClass::from_raw(jni_non_void_call!(
+ self.internal,
+ GetSuperclass,
+ class.into_raw()
+ ))
+ })
+ }
+
+ /// Tests whether class1 is assignable from class2.
+ pub fn is_assignable_from<'t, 'u, T, U>(&self, class1: T, class2: U) -> Result<bool>
+ where
+ T: Desc<'a, JClass<'t>>,
+ U: Desc<'a, JClass<'u>>,
+ {
+ let class1 = class1.lookup(self)?;
+ let class2 = class2.lookup(self)?;
+ Ok(jni_unchecked!(
+ self.internal,
+ IsAssignableFrom,
+ class1.into_raw(),
+ class2.into_raw()
+ ) == sys::JNI_TRUE)
+ }
+
+ /// Returns true if the object reference can be cast to the given type.
+ ///
+ /// _NB: Unlike the operator `instanceof`, function `IsInstanceOf` *returns `true`*
+ /// for all classes *if `object` is `null`.*_
+ ///
+ /// See [JNI documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#IsInstanceOf)
+ /// for details.
+ pub fn is_instance_of<'c, O, T>(&self, object: O, class: T) -> Result<bool>
+ where
+ O: Into<JObject<'a>>,
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ Ok(jni_unchecked!(
+ self.internal,
+ IsInstanceOf,
+ object.into().into_raw(),
+ class.into_raw()
+ ) == sys::JNI_TRUE)
+ }
+
+ /// Returns true if ref1 and ref2 refer to the same Java object, or are both `NULL`. Otherwise,
+ /// returns false.
+ pub fn is_same_object<'b, 'c, O, T>(&self, ref1: O, ref2: T) -> Result<bool>
+ where
+ O: Into<JObject<'b>>,
+ T: Into<JObject<'c>>,
+ {
+ Ok(jni_unchecked!(
+ self.internal,
+ IsSameObject,
+ ref1.into().into_raw(),
+ ref2.into().into_raw()
+ ) == sys::JNI_TRUE)
+ }
+
+ /// Raise an exception from an existing object. This will continue being
+ /// thrown in java unless `exception_clear` is called.
+ ///
+ /// # Examples
+ /// ```rust,ignore
+ /// let _ = env.throw(("java/lang/Exception", "something bad happened"));
+ /// ```
+ ///
+ /// Defaulting to "java/lang/Exception":
+ ///
+ /// ```rust,ignore
+ /// let _ = env.throw("something bad happened");
+ /// ```
+ pub fn throw<'e, E>(&self, obj: E) -> Result<()>
+ where
+ E: Desc<'a, JThrowable<'e>>,
+ {
+ let throwable = obj.lookup(self)?;
+ let res: i32 = jni_unchecked!(self.internal, Throw, throwable.into_raw());
+ if res == 0 {
+ Ok(())
+ } else {
+ Err(Error::ThrowFailed(res))
+ }
+ }
+
+ /// Create and throw a new exception from a class descriptor and an error
+ /// message.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let _ = env.throw_new("java/lang/Exception", "something bad happened");
+ /// ```
+ pub fn throw_new<'c, S, T>(&self, class: T, msg: S) -> Result<()>
+ where
+ S: Into<JNIString>,
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ let msg = msg.into();
+ let res: i32 = jni_unchecked!(self.internal, ThrowNew, class.into_raw(), msg.as_ptr());
+ if res == 0 {
+ Ok(())
+ } else {
+ Err(Error::ThrowFailed(res))
+ }
+ }
+
+ /// Check whether or not an exception is currently in the process of being
+ /// thrown. An exception is in this state from the time it gets thrown and
+ /// not caught in a java function until `exception_clear` is called.
+ pub fn exception_occurred(&self) -> Result<JThrowable<'a>> {
+ let throwable = jni_unchecked!(self.internal, ExceptionOccurred);
+ Ok(unsafe { JThrowable::from_raw(throwable) })
+ }
+
+ /// Print exception information to the console.
+ pub fn exception_describe(&self) -> Result<()> {
+ jni_unchecked!(self.internal, ExceptionDescribe);
+ Ok(())
+ }
+
+ /// Clear an exception in the process of being thrown. If this is never
+ /// called, the exception will continue being thrown when control is
+ /// returned to java.
+ pub fn exception_clear(&self) -> Result<()> {
+ jni_unchecked!(self.internal, ExceptionClear);
+ Ok(())
+ }
+
+ /// Abort the JVM with an error message.
+ #[allow(unused_variables, unreachable_code)]
+ pub fn fatal_error<S: Into<JNIString>>(&self, msg: S) -> ! {
+ let msg = msg.into();
+ let res: Result<()> = catch!({
+ jni_unchecked!(self.internal, FatalError, msg.as_ptr());
+ unreachable!()
+ });
+
+ panic!("{:?}", res.unwrap_err());
+ }
+
+ /// Check to see if an exception is being thrown. This only differs from
+ /// `exception_occurred` in that it doesn't return the actual thrown
+ /// exception.
+ pub fn exception_check(&self) -> Result<bool> {
+ let check = jni_unchecked!(self.internal, ExceptionCheck) == sys::JNI_TRUE;
+ Ok(check)
+ }
+
+ /// Create a new instance of a direct java.nio.ByteBuffer
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let buf = vec![0; 1024 * 1024];
+ /// let (addr, len) = { // (use buf.into_raw_parts() on nightly)
+ /// let buf = buf.leak();
+ /// (buf.as_mut_ptr(), buf.len())
+ /// };
+ /// let direct_buffer = unsafe { env.new_direct_byte_buffer(addr, len) };
+ /// ```
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid (non-null) pointer and length
+ ///
+ /// Caller must ensure the lifetime of `data` extends to all uses of the returned
+ /// `ByteBuffer`. The JVM may maintain references to the `ByteBuffer` beyond the lifetime
+ /// of this `JNIEnv`.
+ pub unsafe fn new_direct_byte_buffer(
+ &self,
+ data: *mut u8,
+ len: usize,
+ ) -> Result<JByteBuffer<'a>> {
+ non_null!(data, "new_direct_byte_buffer data argument");
+ let obj = jni_non_null_call!(
+ self.internal,
+ NewDirectByteBuffer,
+ data as *mut c_void,
+ len as jlong
+ );
+ Ok(JByteBuffer::from_raw(obj))
+ }
+
+ /// Returns the starting address of the memory of the direct
+ /// java.nio.ByteBuffer.
+ pub fn get_direct_buffer_address(&self, buf: JByteBuffer) -> Result<*mut u8> {
+ non_null!(buf, "get_direct_buffer_address argument");
+ let ptr = jni_unchecked!(self.internal, GetDirectBufferAddress, buf.into_raw());
+ non_null!(ptr, "get_direct_buffer_address return value");
+ Ok(ptr as _)
+ }
+
+ /// Returns the capacity (length) of the direct java.nio.ByteBuffer.
+ ///
+ /// # Terminology
+ ///
+ /// "capacity" here means the length that was passed to [`Self::new_direct_byte_buffer()`]
+ /// which does not reflect the (potentially) larger size of the underlying allocation (unlike the `Vec`
+ /// API).
+ ///
+ /// The terminology is simply kept from the original JNI API (`GetDirectBufferCapacity`).
+ pub fn get_direct_buffer_capacity(&self, buf: JByteBuffer) -> Result<usize> {
+ non_null!(buf, "get_direct_buffer_capacity argument");
+ let capacity = jni_unchecked!(self.internal, GetDirectBufferCapacity, buf.into_raw());
+ match capacity {
+ -1 => Err(Error::JniCall(JniError::Unknown)),
+ _ => Ok(capacity as usize),
+ }
+ }
+
+ /// Turns an object into a global ref. This has the benefit of removing the
+ /// lifetime bounds since it's guaranteed to not get GC'd by java. It
+ /// releases the GC pin upon being dropped.
+ pub fn new_global_ref<O>(&self, obj: O) -> Result<GlobalRef>
+ where
+ O: Into<JObject<'a>>,
+ {
+ let new_ref = jni_unchecked!(self.internal, NewGlobalRef, obj.into().into_raw());
+ let global = unsafe { GlobalRef::from_raw(self.get_java_vm()?, new_ref) };
+ Ok(global)
+ }
+
+ /// Create a new local reference to an object.
+ ///
+ /// Specifically, this calls the JNI function [`NewLocalRef`], which creates a reference in the
+ /// current local reference frame, regardless of whether the original reference belongs to the
+ /// same local reference frame, a different one, or is a [global reference][GlobalRef]. In Rust
+ /// terms, this method accepts a JNI reference with any valid lifetime and produces a clone of
+ /// that reference with the lifetime of this `JNIEnv`. The returned reference can outlive the
+ /// original.
+ ///
+ /// This method is useful when you have a strong global reference and you can't prevent it from
+ /// being dropped before you're finished with it. In that case, you can use this method to
+ /// create a new local reference that's guaranteed to remain valid for the duration of the
+ /// current local reference frame, regardless of what later happens to the original global
+ /// reference.
+ ///
+ /// # Lifetimes
+ ///
+ /// `'a` is the lifetime of this `JNIEnv`. This method creates a new local reference with
+ /// lifetime `'a`.
+ ///
+ /// `'b` is the lifetime of the original reference. It can be any valid lifetime, even one that
+ /// `'a` outlives or vice versa.
+ ///
+ /// Think of `'a` as meaning `'new` and `'b` as meaning `'original`. (It is unfortunately not
+ /// possible to actually give these names to the two lifetimes because `'a` is a parameter to
+ /// the `JNIEnv` type, not a parameter to this method.)
+ ///
+ /// # Example
+ ///
+ /// In the following example, the `ExampleError::extract_throwable` method uses
+ /// `JNIEnv::new_local_ref` to create a new local reference that outlives the original global
+ /// reference:
+ ///
+ /// ```no_run
+ /// # use jni::{JNIEnv, objects::*};
+ /// # use std::fmt::Display;
+ /// #
+ /// # type SomeOtherErrorType = Box<dyn Display>;
+ /// #
+ /// /// An error that may be caused by either a Java exception or something going wrong in Rust
+ /// /// code.
+ /// enum ExampleError {
+ /// /// This variant represents a Java exception.
+ /// ///
+ /// /// The enclosed `GlobalRef` points to a Java object of class `java.lang.Throwable`
+ /// /// (or one of its many subclasses).
+ /// Exception(GlobalRef),
+ ///
+ /// /// This variant represents an error in Rust code, not a Java exception.
+ /// Other(SomeOtherErrorType),
+ /// }
+ ///
+ /// impl ExampleError {
+ /// /// Consumes this `ExampleError` and produces a `JThrowable`, suitable for throwing
+ /// /// back to Java code.
+ /// ///
+ /// /// If this is an `ExampleError::Exception`, then this extracts the enclosed Java
+ /// /// exception object. Otherwise, a new exception object is created to represent this
+ /// /// error.
+ /// fn extract_throwable(self, env: JNIEnv) -> jni::errors::Result<JThrowable> {
+ /// let throwable: JObject = match self {
+ /// ExampleError::Exception(exception) => {
+ /// // The error was caused by a Java exception.
+ ///
+ /// // Here, `exception` is a `GlobalRef` pointing to a Java `Throwable`. It
+ /// // will be dropped at the end of this `match` arm. We'll use
+ /// // `new_local_ref` to create a local reference that will outlive the
+ /// // `GlobalRef`.
+ ///
+ /// env.new_local_ref(exception.as_obj())?
+ /// }
+ ///
+ /// ExampleError::Other(error) => {
+ /// // The error was caused by something that happened in Rust code. Create a
+ /// // new `java.lang.Error` to represent it.
+ ///
+ /// env.new_object(
+ /// "java/lang/Error",
+ /// "(Ljava/lang/String;)V",
+ /// &[
+ /// env.new_string(error.to_string())?.into(),
+ /// ],
+ /// )?
+ /// }
+ /// };
+ ///
+ /// Ok(JThrowable::from(throwable))
+ /// }
+ /// }
+ /// ```
+ ///
+ /// [`NewLocalRef`]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/functions.html#newlocalref
+ pub fn new_local_ref<'b, O>(&self, obj: O) -> Result<JObject<'a>>
+ where
+ O: Into<JObject<'b>>,
+ {
+ let local = jni_unchecked!(self.internal, NewLocalRef, obj.into().into_raw());
+ Ok(unsafe { JObject::from_raw(local) })
+ }
+
+ /// Creates a new auto-deleted local reference.
+ ///
+ /// See also [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) method that
+ /// can be more convenient when you create a _bounded_ number of local references
+ /// but cannot rely on automatic de-allocation (e.g., in case of recursion, deep call stacks,
+ /// [permanently-attached](struct.JavaVM.html#attaching-native-threads) native threads, etc.).
+ pub fn auto_local<'b, O>(&'b self, obj: O) -> AutoLocal<'a, 'b>
+ where
+ O: Into<JObject<'a>>,
+ {
+ AutoLocal::new(self, obj.into())
+ }
+
+ /// Deletes the local reference.
+ ///
+ /// Local references are valid for the duration of a native method call.
+ /// They are
+ /// freed automatically after the native method returns. Each local
+ /// reference costs
+ /// some amount of Java Virtual Machine resource. Programmers need to make
+ /// sure that
+ /// native methods do not excessively allocate local references. Although
+ /// local
+ /// references are automatically freed after the native method returns to
+ /// Java,
+ /// excessive allocation of local references may cause the VM to run out of
+ /// memory
+ /// during the execution of a native method.
+ ///
+ /// In most cases it is better to use `AutoLocal` (see `auto_local` method)
+ /// or `with_local_frame` instead of direct `delete_local_ref` calls.
+ pub fn delete_local_ref(&self, obj: JObject) -> Result<()> {
+ jni_unchecked!(self.internal, DeleteLocalRef, obj.into_raw());
+ Ok(())
+ }
+
+ /// Creates a new local reference frame, in which at least a given number
+ /// of local references can be created.
+ ///
+ /// Returns `Err` on failure, with a pending `OutOfMemoryError`.
+ ///
+ /// Prefer to use [`with_local_frame`](struct.JNIEnv.html#method.with_local_frame) instead of
+ /// direct `push_local_frame`/`pop_local_frame` calls.
+ ///
+ /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method
+ /// and `AutoLocal` type — that approach can be more convenient in loops.
+ pub fn push_local_frame(&self, capacity: i32) -> Result<()> {
+ // This method is safe to call in case of pending exceptions (see chapter 2 of the spec)
+ let res = jni_unchecked!(self.internal, PushLocalFrame, capacity);
+ jni_error_code_to_result(res)
+ }
+
+ /// Pops off the current local reference frame, frees all the local
+ /// references allocated on the current stack frame, except the `result`,
+ /// which is returned from this function and remains valid.
+ ///
+ /// The resulting `JObject` will be `NULL` iff `result` is `NULL`.
+ pub fn pop_local_frame(&self, result: JObject<'a>) -> Result<JObject<'a>> {
+ // This method is safe to call in case of pending exceptions (see chapter 2 of the spec)
+ Ok(unsafe {
+ JObject::from_raw(jni_unchecked!(
+ self.internal,
+ PopLocalFrame,
+ result.into_raw()
+ ))
+ })
+ }
+
+ /// Executes the given function in a new local reference frame, in which at least a given number
+ /// of references can be created. Once this method returns, all references allocated
+ /// in the frame are freed, except the one that the function returns, which remains valid.
+ ///
+ /// If _no_ new frames can be allocated, returns `Err` with a pending `OutOfMemoryError`.
+ ///
+ /// See also [`auto_local`](struct.JNIEnv.html#method.auto_local) method
+ /// and `AutoLocal` type - that approach can be more convenient in loops.
+ pub fn with_local_frame<F>(&self, capacity: i32, f: F) -> Result<JObject<'a>>
+ where
+ F: FnOnce() -> Result<JObject<'a>>,
+ {
+ self.push_local_frame(capacity)?;
+ let res = f();
+ match res {
+ Ok(obj) => self.pop_local_frame(obj),
+ Err(e) => {
+ self.pop_local_frame(JObject::null())?;
+ Err(e)
+ }
+ }
+ }
+
+ /// Allocates a new object from a class descriptor without running a
+ /// constructor.
+ pub fn alloc_object<'c, T>(&self, class: T) -> Result<JObject<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ let obj = jni_non_null_call!(self.internal, AllocObject, class.into_raw());
+ Ok(unsafe { JObject::from_raw(obj) })
+ }
+
+ /// Common functionality for finding methods.
+ #[allow(clippy::redundant_closure_call)]
+ fn get_method_id_base<'c, T, U, V, C, R>(
+ &self,
+ class: T,
+ name: U,
+ sig: V,
+ get_method: C,
+ ) -> Result<R>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+ C: for<'d> Fn(&JClass<'d>, &JNIString, &JNIString) -> Result<R>,
+ {
+ let class = class.lookup(self)?;
+ let ffi_name = name.into();
+ let sig = sig.into();
+
+ let res: Result<R> = catch!({ get_method(&class, &ffi_name, &sig) });
+
+ match res {
+ Ok(m) => Ok(m),
+ Err(e) => match e {
+ Error::NullPtr(_) => {
+ let name: String = ffi_name.into();
+ let sig: String = sig.into();
+ Err(Error::MethodNotFound { name, sig })
+ }
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Look up a method by class descriptor, name, and
+ /// signature.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let method_id: JMethodID =
+ /// env.get_method_id("java/lang/String", "substring", "(II)Ljava/lang/String;");
+ /// ```
+ pub fn get_method_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JMethodID>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+ {
+ self.get_method_id_base(class, name, sig, |class, name, sig| {
+ let method_id = jni_non_null_call!(
+ self.internal,
+ GetMethodID,
+ class.into_raw(),
+ name.as_ptr(),
+ sig.as_ptr()
+ );
+ Ok(unsafe { JMethodID::from_raw(method_id) })
+ })
+ }
+
+ /// Look up a static method by class descriptor, name, and
+ /// signature.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let method_id: JMethodID =
+ /// env.get_static_method_id("java/lang/String", "valueOf", "(I)Ljava/lang/String;");
+ /// ```
+ pub fn get_static_method_id<'c, T, U, V>(
+ &self,
+ class: T,
+ name: U,
+ sig: V,
+ ) -> Result<JStaticMethodID>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+ {
+ self.get_method_id_base(class, name, sig, |class, name, sig| {
+ let method_id = jni_non_null_call!(
+ self.internal,
+ GetStaticMethodID,
+ class.into_raw(),
+ name.as_ptr(),
+ sig.as_ptr()
+ );
+ Ok(unsafe { JStaticMethodID::from_raw(method_id) })
+ })
+ }
+
+ /// Look up the field ID for a class/name/type combination.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let field_id = env.get_field_id("com/my/Class", "intField", "I");
+ /// ```
+ pub fn get_field_id<'c, T, U, V>(&self, class: T, name: U, sig: V) -> Result<JFieldID>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+ {
+ let class = class.lookup(self)?;
+ let ffi_name = name.into();
+ let ffi_sig = sig.into();
+
+ let res: Result<JFieldID> = catch!({
+ let field_id = jni_non_null_call!(
+ self.internal,
+ GetFieldID,
+ class.into_raw(),
+ ffi_name.as_ptr(),
+ ffi_sig.as_ptr()
+ );
+ Ok(unsafe { JFieldID::from_raw(field_id) })
+ });
+
+ match res {
+ Ok(m) => Ok(m),
+ Err(e) => match e {
+ Error::NullPtr(_) => {
+ let name: String = ffi_name.into();
+ let sig: String = ffi_sig.into();
+ Err(Error::FieldNotFound { name, sig })
+ }
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Look up the static field ID for a class/name/type combination.
+ ///
+ /// # Example
+ /// ```rust,ignore
+ /// let field_id = env.get_static_field_id("com/my/Class", "intField", "I");
+ /// ```
+ pub fn get_static_field_id<'c, T, U, V>(
+ &self,
+ class: T,
+ name: U,
+ sig: V,
+ ) -> Result<JStaticFieldID>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString>,
+ {
+ let class = class.lookup(self)?;
+ let ffi_name = name.into();
+ let ffi_sig = sig.into();
+
+ let res: Result<JStaticFieldID> = catch!({
+ let field_id = jni_non_null_call!(
+ self.internal,
+ GetStaticFieldID,
+ class.into_raw(),
+ ffi_name.as_ptr(),
+ ffi_sig.as_ptr()
+ );
+ Ok(unsafe { JStaticFieldID::from_raw(field_id) })
+ });
+
+ match res {
+ Ok(m) => Ok(m),
+ Err(e) => match e {
+ Error::NullPtr(_) => {
+ let name: String = ffi_name.into();
+ let sig: String = ffi_sig.into();
+ Err(Error::FieldNotFound { name, sig })
+ }
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Get the class for an object.
+ pub fn get_object_class<'b, O>(&self, obj: O) -> Result<JClass<'a>>
+ where
+ O: Into<JObject<'b>>,
+ {
+ let obj = obj.into();
+ non_null!(obj, "get_object_class");
+ Ok(unsafe {
+ JClass::from_raw(jni_unchecked!(
+ self.internal,
+ GetObjectClass,
+ obj.into_raw()
+ ))
+ })
+ }
+
+ /// Call a static method in an unsafe manner. This does nothing to check
+ /// whether the method is valid to call on the class, whether the return
+ /// type is correct, or whether the number of args is valid for the method.
+ ///
+ /// Under the hood, this simply calls the `CallStatic<Type>MethodA` method
+ /// with the provided arguments.
+ pub fn call_static_method_unchecked<'c, T, U>(
+ &self,
+ class: T,
+ method_id: U,
+ ret: ReturnType,
+ args: &[jvalue],
+ ) -> Result<JValue<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Desc<'a, JStaticMethodID>,
+ {
+ let class = class.lookup(self)?;
+
+ let method_id = method_id.lookup(self)?.into_raw();
+
+ let class = class.into_raw();
+ let jni_args = args.as_ptr();
+
+ // TODO clean this up
+ Ok(match ret {
+ ReturnType::Object | ReturnType::Array => {
+ let obj = jni_non_void_call!(
+ self.internal,
+ CallStaticObjectMethodA,
+ class,
+ method_id,
+ jni_args
+ );
+ let obj = unsafe { JObject::from_raw(obj) };
+ obj.into()
+ }
+ ReturnType::Primitive(p) => match p {
+ Primitive::Boolean => jni_non_void_call!(
+ self.internal,
+ CallStaticBooleanMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Char => jni_non_void_call!(
+ self.internal,
+ CallStaticCharMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Short => jni_non_void_call!(
+ self.internal,
+ CallStaticShortMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Int => jni_non_void_call!(
+ self.internal,
+ CallStaticIntMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Long => jni_non_void_call!(
+ self.internal,
+ CallStaticLongMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Float => jni_non_void_call!(
+ self.internal,
+ CallStaticFloatMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Double => jni_non_void_call!(
+ self.internal,
+ CallStaticDoubleMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Byte => jni_non_void_call!(
+ self.internal,
+ CallStaticByteMethodA,
+ class,
+ method_id,
+ jni_args
+ )
+ .into(),
+ Primitive::Void => {
+ jni_void_call!(
+ self.internal,
+ CallStaticVoidMethodA,
+ class,
+ method_id,
+ jni_args
+ );
+ return Ok(JValue::Void);
+ }
+ }, // JavaType::Primitive
+ }) // match parsed.ret
+ }
+
+ /// Call an object method in an unsafe manner. This does nothing to check
+ /// whether the method is valid to call on the object, whether the return
+ /// type is correct, or whether the number of args is valid for the method.
+ ///
+ /// Under the hood, this simply calls the `Call<Type>MethodA` method with
+ /// the provided arguments.
+ pub fn call_method_unchecked<O, T>(
+ &self,
+ obj: O,
+ method_id: T,
+ ret: ReturnType,
+ args: &[jvalue],
+ ) -> Result<JValue<'a>>
+ where
+ O: Into<JObject<'a>>,
+ T: Desc<'a, JMethodID>,
+ {
+ let method_id = method_id.lookup(self)?.into_raw();
+
+ let obj = obj.into().into_raw();
+
+ let jni_args = args.as_ptr();
+
+ // TODO clean this up
+ Ok(match ret {
+ ReturnType::Object | ReturnType::Array => {
+ let obj =
+ jni_non_void_call!(self.internal, CallObjectMethodA, obj, method_id, jni_args);
+ let obj = unsafe { JObject::from_raw(obj) };
+ obj.into()
+ }
+ ReturnType::Primitive(p) => match p {
+ Primitive::Boolean => {
+ jni_non_void_call!(self.internal, CallBooleanMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Char => {
+ jni_non_void_call!(self.internal, CallCharMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Short => {
+ jni_non_void_call!(self.internal, CallShortMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Int => {
+ jni_non_void_call!(self.internal, CallIntMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Long => {
+ jni_non_void_call!(self.internal, CallLongMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Float => {
+ jni_non_void_call!(self.internal, CallFloatMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Double => {
+ jni_non_void_call!(self.internal, CallDoubleMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Byte => {
+ jni_non_void_call!(self.internal, CallByteMethodA, obj, method_id, jni_args)
+ .into()
+ }
+ Primitive::Void => {
+ jni_void_call!(self.internal, CallVoidMethodA, obj, method_id, jni_args);
+ return Ok(JValue::Void);
+ }
+ }, // JavaType::Primitive
+ }) // match parsed.ret
+ }
+
+ /// Calls an object method safely. This comes with a number of
+ /// lookups/checks. It
+ ///
+ /// * Parses the type signature to find the number of arguments and return
+ /// type
+ /// * Looks up the JClass for the given object.
+ /// * Looks up the JMethodID for the class/name/signature combination
+ /// * Ensures that the number of args matches the signature
+ /// * Calls `call_method_unchecked` with the verified safe arguments.
+ ///
+ /// Note: this may cause a java exception if the arguments are the wrong
+ /// type, in addition to if the method itself throws.
+ pub fn call_method<O, S, T>(
+ &self,
+ obj: O,
+ name: S,
+ sig: T,
+ args: &[JValue],
+ ) -> Result<JValue<'a>>
+ where
+ O: Into<JObject<'a>>,
+ S: Into<JNIString>,
+ T: Into<JNIString> + AsRef<str>,
+ {
+ let obj = obj.into();
+ non_null!(obj, "call_method obj argument");
+
+ // parse the signature
+ let parsed = TypeSignature::from_str(sig.as_ref())?;
+ if parsed.args.len() != args.len() {
+ return Err(Error::InvalidArgList(parsed));
+ }
+
+ let class = self.auto_local(self.get_object_class(obj)?);
+
+ let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect();
+ self.call_method_unchecked(obj, (&class, name, sig), parsed.ret, &args)
+ }
+
+ /// Calls a static method safely. This comes with a number of
+ /// lookups/checks. It
+ ///
+ /// * Parses the type signature to find the number of arguments and return
+ /// type
+ /// * Looks up the JMethodID for the class/name/signature combination
+ /// * Ensures that the number of args matches the signature
+ /// * Calls `call_method_unchecked` with the verified safe arguments.
+ ///
+ /// Note: this may cause a java exception if the arguments are the wrong
+ /// type, in addition to if the method itself throws.
+ pub fn call_static_method<'c, T, U, V>(
+ &self,
+ class: T,
+ name: U,
+ sig: V,
+ args: &[JValue],
+ ) -> Result<JValue<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString> + AsRef<str>,
+ {
+ let parsed = TypeSignature::from_str(&sig)?;
+ if parsed.args.len() != args.len() {
+ return Err(Error::InvalidArgList(parsed));
+ }
+
+ // go ahead and look up the class since it's already Copy,
+ // and we'll need that for the next call.
+ let class = class.lookup(self)?;
+
+ let args: Vec<jvalue> = args.iter().map(|v| v.to_jni()).collect();
+ self.call_static_method_unchecked(class, (class, name, sig), parsed.ret, &args)
+ }
+
+ /// Create a new object using a constructor. This is done safely using
+ /// checks similar to those in `call_static_method`.
+ pub fn new_object<'c, T, U>(
+ &self,
+ class: T,
+ ctor_sig: U,
+ ctor_args: &[JValue],
+ ) -> Result<JObject<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString> + AsRef<str>,
+ {
+ // parse the signature
+ let parsed = TypeSignature::from_str(&ctor_sig)?;
+
+ if parsed.args.len() != ctor_args.len() {
+ return Err(Error::InvalidArgList(parsed));
+ }
+
+ if parsed.ret != ReturnType::Primitive(Primitive::Void) {
+ return Err(Error::InvalidCtorReturn);
+ }
+
+ // build strings
+ let class = class.lookup(self)?;
+
+ let method_id: JMethodID = (class, ctor_sig).lookup(self)?;
+
+ self.new_object_unchecked(class, method_id, ctor_args)
+ }
+
+ /// Create a new object using a constructor. Arguments aren't checked
+ /// because
+ /// of the `JMethodID` usage.
+ pub fn new_object_unchecked<'c, T>(
+ &self,
+ class: T,
+ ctor_id: JMethodID,
+ ctor_args: &[JValue],
+ ) -> Result<JObject<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+
+ let jni_args: Vec<jvalue> = ctor_args.iter().map(|v| v.to_jni()).collect();
+ let jni_args = jni_args.as_ptr();
+
+ let obj = jni_non_null_call!(
+ self.internal,
+ NewObjectA,
+ class.into_raw(),
+ ctor_id.into_raw(),
+ jni_args
+ );
+ Ok(unsafe { JObject::from_raw(obj) })
+ }
+
+ /// Cast a JObject to a `JList`. This won't throw exceptions or return errors
+ /// in the event that the object isn't actually a list, but the methods on
+ /// the resulting map object will.
+ pub fn get_list(&self, obj: JObject<'a>) -> Result<JList<'a, '_>> {
+ non_null!(obj, "get_list obj argument");
+ JList::from_env(self, obj)
+ }
+
+ /// Cast a JObject to a JMap. This won't throw exceptions or return errors
+ /// in the event that the object isn't actually a map, but the methods on
+ /// the resulting map object will.
+ pub fn get_map(&self, obj: JObject<'a>) -> Result<JMap<'a, '_>> {
+ non_null!(obj, "get_map obj argument");
+ JMap::from_env(self, obj)
+ }
+
+ /// Get a JavaStr from a JString. This allows conversions from java string
+ /// objects to rust strings.
+ ///
+ /// This entails a call to `GetStringUTFChars` and only decodes java's
+ /// modified UTF-8 format on conversion to a rust-compatible string.
+ ///
+ /// # Panics
+ ///
+ /// This call panics when given an Object that is not a java.lang.String
+ pub fn get_string(&self, obj: JString<'a>) -> Result<JavaStr<'a, '_>> {
+ non_null!(obj, "get_string obj argument");
+ JavaStr::from_env(self, obj)
+ }
+
+ /// Get a pointer to the character array beneath a JString.
+ ///
+ /// Array contains Java's modified UTF-8.
+ ///
+ /// # Attention
+ /// This will leak memory if `release_string_utf_chars` is never called.
+ pub fn get_string_utf_chars(&self, obj: JString) -> Result<*const c_char> {
+ non_null!(obj, "get_string_utf_chars obj argument");
+ let ptr: *const c_char = jni_non_null_call!(
+ self.internal,
+ GetStringUTFChars,
+ obj.into_raw(),
+ ::std::ptr::null::<jboolean>() as *mut jboolean
+ );
+ Ok(ptr)
+ }
+
+ /// Unpin the array returned by `get_string_utf_chars`.
+ ///
+ /// # Safety
+ ///
+ /// The behaviour is undefined if the array isn't returned by the `get_string_utf_chars`
+ /// function.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// # let env = unsafe { jni::JNIEnv::from_raw(std::ptr::null_mut()).unwrap() };
+ /// let s = env.new_string("test").unwrap();
+ /// let array = env.get_string_utf_chars(s).unwrap();
+ /// unsafe { env.release_string_utf_chars(s, array).unwrap() };
+ /// ```
+ #[allow(unused_unsafe)]
+ pub unsafe fn release_string_utf_chars(&self, obj: JString, arr: *const c_char) -> Result<()> {
+ non_null!(obj, "release_string_utf_chars obj argument");
+ // This method is safe to call in case of pending exceptions (see the chapter 2 of the spec)
+ jni_unchecked!(self.internal, ReleaseStringUTFChars, obj.into_raw(), arr);
+ Ok(())
+ }
+
+ /// Create a new java string object from a rust string. This requires a
+ /// re-encoding of rusts *real* UTF-8 strings to java's modified UTF-8
+ /// format.
+ pub fn new_string<S: Into<JNIString>>(&self, from: S) -> Result<JString<'a>> {
+ let ffi_str = from.into();
+ let s = jni_non_null_call!(self.internal, NewStringUTF, ffi_str.as_ptr());
+ Ok(unsafe { JString::from_raw(s) })
+ }
+
+ /// Get the length of a java array
+ pub fn get_array_length(&self, array: jarray) -> Result<jsize> {
+ non_null!(array, "get_array_length array argument");
+ let len: jsize = jni_unchecked!(self.internal, GetArrayLength, array);
+ Ok(len)
+ }
+
+ /// Construct a new array holding objects in class `element_class`.
+ /// All elements are initially set to `initial_element`.
+ ///
+ /// This function returns a local reference, that must not be allocated
+ /// excessively.
+ /// See [Java documentation][1] for details.
+ ///
+ /// [1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references
+ pub fn new_object_array<'c, T, U>(
+ &self,
+ length: jsize,
+ element_class: T,
+ initial_element: U,
+ ) -> Result<jobjectArray>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JObject<'a>>,
+ {
+ let class = element_class.lookup(self)?;
+ Ok(jni_non_null_call!(
+ self.internal,
+ NewObjectArray,
+ length,
+ class.into_raw(),
+ initial_element.into().into_raw()
+ ))
+ }
+
+ /// Returns an element of the `jobjectArray` array.
+ pub fn get_object_array_element(
+ &self,
+ array: jobjectArray,
+ index: jsize,
+ ) -> Result<JObject<'a>> {
+ non_null!(array, "get_object_array_element array argument");
+ Ok(unsafe {
+ JObject::from_raw(jni_non_void_call!(
+ self.internal,
+ GetObjectArrayElement,
+ array,
+ index
+ ))
+ })
+ }
+
+ /// Sets an element of the `jobjectArray` array.
+ pub fn set_object_array_element<O>(
+ &self,
+ array: jobjectArray,
+ index: jsize,
+ value: O,
+ ) -> Result<()>
+ where
+ O: Into<JObject<'a>>,
+ {
+ non_null!(array, "set_object_array_element array argument");
+ jni_void_call!(
+ self.internal,
+ SetObjectArrayElement,
+ array,
+ index,
+ value.into().into_raw()
+ );
+ Ok(())
+ }
+
+ /// Create a new java byte array from a rust byte slice.
+ pub fn byte_array_from_slice(&self, buf: &[u8]) -> Result<jbyteArray> {
+ let length = buf.len() as i32;
+ let bytes: jbyteArray = self.new_byte_array(length)?;
+ jni_unchecked!(
+ self.internal,
+ SetByteArrayRegion,
+ bytes,
+ 0,
+ length,
+ buf.as_ptr() as *const i8
+ );
+ Ok(bytes)
+ }
+
+ /// Converts a java byte array to a rust vector of bytes.
+ pub fn convert_byte_array(&self, array: jbyteArray) -> Result<Vec<u8>> {
+ non_null!(array, "convert_byte_array array argument");
+ let length = jni_non_void_call!(self.internal, GetArrayLength, array);
+ let mut vec = vec![0u8; length as usize];
+ jni_unchecked!(
+ self.internal,
+ GetByteArrayRegion,
+ array,
+ 0,
+ length,
+ vec.as_mut_ptr() as *mut i8
+ );
+ Ok(vec)
+ }
+
+ /// Create a new java boolean array of supplied length.
+ pub fn new_boolean_array(&self, length: jsize) -> Result<jbooleanArray> {
+ let array: jbooleanArray = jni_non_null_call!(self.internal, NewBooleanArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java byte array of supplied length.
+ pub fn new_byte_array(&self, length: jsize) -> Result<jbyteArray> {
+ let array: jbyteArray = jni_non_null_call!(self.internal, NewByteArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java char array of supplied length.
+ pub fn new_char_array(&self, length: jsize) -> Result<jcharArray> {
+ let array: jcharArray = jni_non_null_call!(self.internal, NewCharArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java short array of supplied length.
+ pub fn new_short_array(&self, length: jsize) -> Result<jshortArray> {
+ let array: jshortArray = jni_non_null_call!(self.internal, NewShortArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java int array of supplied length.
+ pub fn new_int_array(&self, length: jsize) -> Result<jintArray> {
+ let array: jintArray = jni_non_null_call!(self.internal, NewIntArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java long array of supplied length.
+ pub fn new_long_array(&self, length: jsize) -> Result<jlongArray> {
+ let array: jlongArray = jni_non_null_call!(self.internal, NewLongArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java float array of supplied length.
+ pub fn new_float_array(&self, length: jsize) -> Result<jfloatArray> {
+ let array: jfloatArray = jni_non_null_call!(self.internal, NewFloatArray, length);
+ Ok(array)
+ }
+
+ /// Create a new java double array of supplied length.
+ pub fn new_double_array(&self, length: jsize) -> Result<jdoubleArray> {
+ let array: jdoubleArray = jni_non_null_call!(self.internal, NewDoubleArray, length);
+ Ok(array)
+ }
+
+ /// Copy elements of the java boolean array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_boolean_array_region(
+ &self,
+ array: jbooleanArray,
+ start: jsize,
+ buf: &mut [jboolean],
+ ) -> Result<()> {
+ non_null!(array, "get_boolean_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetBooleanArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java byte array from the `start` index to the `buf`
+ /// slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_byte_array_region(
+ &self,
+ array: jbyteArray,
+ start: jsize,
+ buf: &mut [jbyte],
+ ) -> Result<()> {
+ non_null!(array, "get_byte_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetByteArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java char array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_char_array_region(
+ &self,
+ array: jcharArray,
+ start: jsize,
+ buf: &mut [jchar],
+ ) -> Result<()> {
+ non_null!(array, "get_char_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetCharArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java short array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_short_array_region(
+ &self,
+ array: jshortArray,
+ start: jsize,
+ buf: &mut [jshort],
+ ) -> Result<()> {
+ non_null!(array, "get_short_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetShortArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java int array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_int_array_region(
+ &self,
+ array: jintArray,
+ start: jsize,
+ buf: &mut [jint],
+ ) -> Result<()> {
+ non_null!(array, "get_int_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetIntArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java long array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_long_array_region(
+ &self,
+ array: jlongArray,
+ start: jsize,
+ buf: &mut [jlong],
+ ) -> Result<()> {
+ non_null!(array, "get_long_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetLongArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java float array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_float_array_region(
+ &self,
+ array: jfloatArray,
+ start: jsize,
+ buf: &mut [jfloat],
+ ) -> Result<()> {
+ non_null!(array, "get_float_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetFloatArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy elements of the java double array from the `start` index to the
+ /// `buf` slice. The number of copied elements is equal to the `buf` length.
+ ///
+ /// # Errors
+ /// If `start` is negative _or_ `start + buf.len()` is greater than [`array.length`]
+ /// then no elements are copied, an `ArrayIndexOutOfBoundsException` is thrown,
+ /// and `Err` is returned.
+ ///
+ /// [`array.length`]: struct.JNIEnv.html#method.get_array_length
+ pub fn get_double_array_region(
+ &self,
+ array: jdoubleArray,
+ start: jsize,
+ buf: &mut [jdouble],
+ ) -> Result<()> {
+ non_null!(array, "get_double_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ GetDoubleArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_mut_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java boolean array at the
+ /// `start` index.
+ pub fn set_boolean_array_region(
+ &self,
+ array: jbooleanArray,
+ start: jsize,
+ buf: &[jboolean],
+ ) -> Result<()> {
+ non_null!(array, "set_boolean_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetBooleanArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java byte array at the
+ /// `start` index.
+ pub fn set_byte_array_region(
+ &self,
+ array: jbyteArray,
+ start: jsize,
+ buf: &[jbyte],
+ ) -> Result<()> {
+ non_null!(array, "set_byte_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetByteArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java char array at the
+ /// `start` index.
+ pub fn set_char_array_region(
+ &self,
+ array: jcharArray,
+ start: jsize,
+ buf: &[jchar],
+ ) -> Result<()> {
+ non_null!(array, "set_char_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetCharArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java short array at the
+ /// `start` index.
+ pub fn set_short_array_region(
+ &self,
+ array: jshortArray,
+ start: jsize,
+ buf: &[jshort],
+ ) -> Result<()> {
+ non_null!(array, "set_short_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetShortArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java int array at the
+ /// `start` index.
+ pub fn set_int_array_region(&self, array: jintArray, start: jsize, buf: &[jint]) -> Result<()> {
+ non_null!(array, "set_int_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetIntArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java long array at the
+ /// `start` index.
+ pub fn set_long_array_region(
+ &self,
+ array: jlongArray,
+ start: jsize,
+ buf: &[jlong],
+ ) -> Result<()> {
+ non_null!(array, "set_long_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetLongArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java float array at the
+ /// `start` index.
+ pub fn set_float_array_region(
+ &self,
+ array: jfloatArray,
+ start: jsize,
+ buf: &[jfloat],
+ ) -> Result<()> {
+ non_null!(array, "set_float_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetFloatArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Copy the contents of the `buf` slice to the java double array at the
+ /// `start` index.
+ pub fn set_double_array_region(
+ &self,
+ array: jdoubleArray,
+ start: jsize,
+ buf: &[jdouble],
+ ) -> Result<()> {
+ non_null!(array, "set_double_array_region array argument");
+ jni_void_call!(
+ self.internal,
+ SetDoubleArrayRegion,
+ array,
+ start,
+ buf.len() as jsize,
+ buf.as_ptr()
+ );
+ Ok(())
+ }
+
+ /// Get a field without checking the provided type against the actual field.
+ pub fn get_field_unchecked<O, T>(&self, obj: O, field: T, ty: ReturnType) -> Result<JValue<'a>>
+ where
+ O: Into<JObject<'a>>,
+ T: Desc<'a, JFieldID>,
+ {
+ let obj = obj.into();
+ non_null!(obj, "get_field_typed obj argument");
+
+ let field = field.lookup(self)?.into_raw();
+ let obj = obj.into_raw();
+
+ // TODO clean this up
+ Ok(match ty {
+ ReturnType::Object | ReturnType::Array => {
+ let obj = jni_non_void_call!(self.internal, GetObjectField, obj, field);
+ let obj = unsafe { JObject::from_raw(obj) };
+ obj.into()
+ }
+ ReturnType::Primitive(p) => match p {
+ Primitive::Boolean => {
+ jni_unchecked!(self.internal, GetBooleanField, obj, field).into()
+ }
+ Primitive::Char => jni_unchecked!(self.internal, GetCharField, obj, field).into(),
+ Primitive::Short => jni_unchecked!(self.internal, GetShortField, obj, field).into(),
+ Primitive::Int => jni_unchecked!(self.internal, GetIntField, obj, field).into(),
+ Primitive::Long => jni_unchecked!(self.internal, GetLongField, obj, field).into(),
+ Primitive::Float => jni_unchecked!(self.internal, GetFloatField, obj, field).into(),
+ Primitive::Double => {
+ jni_unchecked!(self.internal, GetDoubleField, obj, field).into()
+ }
+ Primitive::Byte => jni_unchecked!(self.internal, GetByteField, obj, field).into(),
+ Primitive::Void => {
+ return Err(Error::WrongJValueType("void", "see java field"));
+ }
+ },
+ })
+ }
+
+ /// Set a field without any type checking.
+ pub fn set_field_unchecked<O, T>(&self, obj: O, field: T, val: JValue) -> Result<()>
+ where
+ O: Into<JObject<'a>>,
+ T: Desc<'a, JFieldID>,
+ {
+ let obj = obj.into();
+ non_null!(obj, "set_field_typed obj argument");
+
+ let field = field.lookup(self)?.into_raw();
+ let obj = obj.into_raw();
+
+ // TODO clean this up
+ match val {
+ JValue::Object(o) => {
+ jni_unchecked!(self.internal, SetObjectField, obj, field, o.into_raw());
+ }
+ // JavaType::Object
+ JValue::Bool(b) => {
+ jni_unchecked!(self.internal, SetBooleanField, obj, field, b);
+ }
+ JValue::Char(c) => {
+ jni_unchecked!(self.internal, SetCharField, obj, field, c);
+ }
+ JValue::Short(s) => {
+ jni_unchecked!(self.internal, SetShortField, obj, field, s);
+ }
+ JValue::Int(i) => {
+ jni_unchecked!(self.internal, SetIntField, obj, field, i);
+ }
+ JValue::Long(l) => {
+ jni_unchecked!(self.internal, SetLongField, obj, field, l);
+ }
+ JValue::Float(f) => {
+ jni_unchecked!(self.internal, SetFloatField, obj, field, f);
+ }
+ JValue::Double(d) => {
+ jni_unchecked!(self.internal, SetDoubleField, obj, field, d);
+ }
+ JValue::Byte(b) => {
+ jni_unchecked!(self.internal, SetByteField, obj, field, b);
+ }
+ JValue::Void => {
+ return Err(Error::WrongJValueType("void", "see java field"));
+ }
+ };
+
+ Ok(())
+ }
+
+ /// Get a field. Requires an object class lookup and a field id lookup
+ /// internally.
+ pub fn get_field<O, S, T>(&self, obj: O, name: S, ty: T) -> Result<JValue<'a>>
+ where
+ O: Into<JObject<'a>>,
+ S: Into<JNIString>,
+ T: Into<JNIString> + AsRef<str>,
+ {
+ let obj = obj.into();
+ let class = self.auto_local(self.get_object_class(obj)?);
+
+ let parsed = ReturnType::from_str(ty.as_ref())?;
+
+ let field_id: JFieldID = (&class, name, ty).lookup(self)?;
+
+ self.get_field_unchecked(obj, field_id, parsed)
+ }
+
+ /// Set a field. Does the same lookups as `get_field` and ensures that the
+ /// type matches the given value.
+ pub fn set_field<O, S, T>(&self, obj: O, name: S, ty: T, val: JValue) -> Result<()>
+ where
+ O: Into<JObject<'a>>,
+ S: Into<JNIString>,
+ T: Into<JNIString> + AsRef<str>,
+ {
+ let obj = obj.into();
+ let parsed = JavaType::from_str(ty.as_ref())?;
+ let in_type = val.primitive_type();
+
+ match parsed {
+ JavaType::Object(_) | JavaType::Array(_) => {
+ if in_type.is_some() {
+ return Err(Error::WrongJValueType(val.type_name(), "see java field"));
+ }
+ }
+ JavaType::Primitive(p) => {
+ if let Some(in_p) = in_type {
+ if in_p == p {
+ // good
+ } else {
+ return Err(Error::WrongJValueType(val.type_name(), "see java field"));
+ }
+ } else {
+ return Err(Error::WrongJValueType(val.type_name(), "see java field"));
+ }
+ }
+ JavaType::Method(_) => unimplemented!(),
+ }
+
+ let class = self.auto_local(self.get_object_class(obj)?);
+
+ self.set_field_unchecked(obj, (&class, name, ty), val)
+ }
+
+ /// Get a static field without checking the provided type against the actual
+ /// field.
+ pub fn get_static_field_unchecked<'c, T, U>(
+ &self,
+ class: T,
+ field: U,
+ ty: JavaType,
+ ) -> Result<JValue<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Desc<'a, JStaticFieldID>,
+ {
+ use JavaType::Primitive as JP;
+
+ let class = class.lookup(self)?.into_raw();
+ let field = field.lookup(self)?.into_raw();
+
+ let result = match ty {
+ JavaType::Object(_) | JavaType::Array(_) => {
+ let obj = jni_non_void_call!(self.internal, GetStaticObjectField, class, field);
+ let obj = unsafe { JObject::from_raw(obj) };
+ obj.into()
+ }
+ JavaType::Method(_) => return Err(Error::WrongJValueType("Method", "see java field")),
+ JP(Primitive::Boolean) => {
+ jni_unchecked!(self.internal, GetStaticBooleanField, class, field).into()
+ }
+ JP(Primitive::Char) => {
+ jni_unchecked!(self.internal, GetStaticCharField, class, field).into()
+ }
+ JP(Primitive::Short) => {
+ jni_unchecked!(self.internal, GetStaticShortField, class, field).into()
+ }
+ JP(Primitive::Int) => {
+ jni_unchecked!(self.internal, GetStaticIntField, class, field).into()
+ }
+ JP(Primitive::Long) => {
+ jni_unchecked!(self.internal, GetStaticLongField, class, field).into()
+ }
+ JP(Primitive::Float) => {
+ jni_unchecked!(self.internal, GetStaticFloatField, class, field).into()
+ }
+ JP(Primitive::Double) => {
+ jni_unchecked!(self.internal, GetStaticDoubleField, class, field).into()
+ }
+ JP(Primitive::Byte) => {
+ jni_unchecked!(self.internal, GetStaticByteField, class, field).into()
+ }
+ JP(Primitive::Void) => return Err(Error::WrongJValueType("void", "see java field")),
+ };
+ Ok(result)
+ }
+
+ /// Get a static field. Requires a class lookup and a field id lookup
+ /// internally.
+ pub fn get_static_field<'c, T, U, V>(&self, class: T, field: U, sig: V) -> Result<JValue<'a>>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Into<JNIString>,
+ V: Into<JNIString> + AsRef<str>,
+ {
+ let ty = JavaType::from_str(sig.as_ref())?;
+
+ // go ahead and look up the class since it's already Copy,
+ // and we'll need that for the next call.
+ let class = class.lookup(self)?;
+
+ self.get_static_field_unchecked(class, (class, field, sig), ty)
+ }
+
+ /// Set a static field. Requires a class lookup and a field id lookup internally.
+ pub fn set_static_field<'c, T, U>(&self, class: T, field: U, value: JValue) -> Result<()>
+ where
+ T: Desc<'a, JClass<'c>>,
+ U: Desc<'a, JStaticFieldID>,
+ {
+ let class = class.lookup(self)?.into_raw();
+ let field = field.lookup(self)?.into_raw();
+
+ match value {
+ JValue::Object(v) => jni_unchecked!(
+ self.internal,
+ SetStaticObjectField,
+ class,
+ field,
+ v.into_raw()
+ ),
+ JValue::Byte(v) => jni_unchecked!(self.internal, SetStaticByteField, class, field, v),
+ JValue::Char(v) => jni_unchecked!(self.internal, SetStaticCharField, class, field, v),
+ JValue::Short(v) => jni_unchecked!(self.internal, SetStaticShortField, class, field, v),
+ JValue::Int(v) => jni_unchecked!(self.internal, SetStaticIntField, class, field, v),
+ JValue::Long(v) => jni_unchecked!(self.internal, SetStaticLongField, class, field, v),
+ JValue::Bool(v) => {
+ jni_unchecked!(self.internal, SetStaticBooleanField, class, field, v)
+ }
+ JValue::Float(v) => jni_unchecked!(self.internal, SetStaticFloatField, class, field, v),
+ JValue::Double(v) => {
+ jni_unchecked!(self.internal, SetStaticDoubleField, class, field, v)
+ }
+ JValue::Void => return Err(Error::WrongJValueType("void", "?")),
+ }
+
+ Ok(())
+ }
+
+ /// Surrenders ownership of a Rust value to Java.
+ ///
+ /// This requires an object with a `long` field to store the pointer.
+ ///
+ /// The Rust value will be implicitly wrapped in a `Box<Mutex<T>>`.
+ ///
+ /// The Java object will be locked before changing the field value.
+ ///
+ /// # Safety
+ ///
+ /// It's important to note that using this API will leak memory if
+ /// [`Self::take_rust_field`] is never called so that the Rust type may be
+ /// dropped.
+ ///
+ /// One suggestion that may help ensure that a set Rust field will be
+ /// cleaned up later is for the Java object to implement `Closeable` and let
+ /// people use a `use` block (Kotlin) or `try-with-resources` (Java).
+ ///
+ /// **DO NOT** make a copy of the object containing one of these fields
+ /// since that will lead to a use-after-free error if the Rust type is
+ /// taken and dropped multiple times from Rust.
+ #[allow(unused_variables)]
+ pub unsafe fn set_rust_field<O, S, T>(&self, obj: O, field: S, rust_object: T) -> Result<()>
+ where
+ O: Into<JObject<'a>>,
+ S: AsRef<str>,
+ T: Send + 'static,
+ {
+ let obj = obj.into();
+ let class = self.auto_local(self.get_object_class(obj)?);
+ let field_id: JFieldID = (&class, &field, "J").lookup(self)?;
+
+ let guard = self.lock_obj(obj)?;
+
+ // Check to see if we've already set this value. If it's not null, that
+ // means that we're going to leak memory if it gets overwritten.
+ let field_ptr = self
+ .get_field_unchecked(obj, field_id, ReturnType::Primitive(Primitive::Long))?
+ .j()? as *mut Mutex<T>;
+ if !field_ptr.is_null() {
+ return Err(Error::FieldAlreadySet(field.as_ref().to_owned()));
+ }
+
+ let mbox = Box::new(::std::sync::Mutex::new(rust_object));
+ let ptr: *mut Mutex<T> = Box::into_raw(mbox);
+
+ self.set_field_unchecked(obj, field_id, (ptr as crate::sys::jlong).into())
+ }
+
+ /// Gets a lock on a Rust value that's been given to a Java object.
+ ///
+ /// Java still retains ownership and [`Self::take_rust_field`] will still
+ /// need to be called at some point.
+ ///
+ /// The Java object will be locked before reading the field value but the
+ /// Java object lock will be released after the Rust `Mutex` lock for the
+ /// field value has been taken (i.e the Java object won't be locked once
+ /// this function returns).
+ ///
+ /// # Safety
+ ///
+ /// Checks for a null pointer, but assumes that the data it points to is valid for T.
+ #[allow(unused_variables)]
+ pub unsafe fn get_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<MutexGuard<T>>
+ where
+ O: Into<JObject<'a>>,
+ S: Into<JNIString>,
+ T: Send + 'static,
+ {
+ let obj = obj.into();
+ let guard = self.lock_obj(obj)?;
+
+ let ptr = self.get_field(obj, field, "J")?.j()? as *mut Mutex<T>;
+ non_null!(ptr, "rust value from Java");
+ // dereferencing is safe, because we checked it for null
+ Ok((*ptr).lock().unwrap())
+ }
+
+ /// Take a Rust field back from Java.
+ ///
+ /// It sets the field to a null pointer to signal that it's empty.
+ ///
+ /// The Java object will be locked before taking the field value.
+ ///
+ /// # Safety
+ ///
+ /// This will make sure that the pointer is non-null, but still assumes that
+ /// the data it points to is valid for T.
+ #[allow(unused_variables)]
+ pub unsafe fn take_rust_field<O, S, T>(&self, obj: O, field: S) -> Result<T>
+ where
+ O: Into<JObject<'a>>,
+ S: AsRef<str>,
+ T: Send + 'static,
+ {
+ let obj = obj.into();
+ let class = self.auto_local(self.get_object_class(obj)?);
+ let field_id: JFieldID = (&class, &field, "J").lookup(self)?;
+
+ let mbox = {
+ let guard = self.lock_obj(obj)?;
+
+ let ptr = self
+ .get_field_unchecked(obj, field_id, ReturnType::Primitive(Primitive::Long))?
+ .j()? as *mut Mutex<T>;
+
+ non_null!(ptr, "rust value from Java");
+
+ let mbox = Box::from_raw(ptr);
+
+ // attempt to acquire the lock. This prevents us from consuming the
+ // mutex if there's an outstanding lock. No one else will be able to
+ // get a new one as long as we're in the guarded scope.
+ drop(mbox.try_lock()?);
+
+ self.set_field_unchecked(
+ obj,
+ field_id,
+ (::std::ptr::null_mut::<()>() as sys::jlong).into(),
+ )?;
+
+ mbox
+ };
+
+ Ok(mbox.into_inner().unwrap())
+ }
+
+ /// Lock a Java object. The MonitorGuard that this returns is responsible
+ /// for ensuring that it gets unlocked.
+ pub fn lock_obj<O>(&self, obj: O) -> Result<MonitorGuard<'a>>
+ where
+ O: Into<JObject<'a>>,
+ {
+ let inner = obj.into().into_raw();
+ let _ = jni_unchecked!(self.internal, MonitorEnter, inner);
+
+ Ok(MonitorGuard {
+ obj: inner,
+ env: self.internal,
+ life: Default::default(),
+ })
+ }
+
+ /// Returns underlying `sys::JNIEnv` interface.
+ pub fn get_native_interface(&self) -> *mut sys::JNIEnv {
+ self.internal
+ }
+
+ /// Returns the Java VM interface.
+ pub fn get_java_vm(&self) -> Result<JavaVM> {
+ let mut raw = ptr::null_mut();
+ let res = jni_unchecked!(self.internal, GetJavaVM, &mut raw);
+ jni_error_code_to_result(res)?;
+ unsafe { JavaVM::from_raw(raw) }
+ }
+
+ /// Ensures that at least a given number of local references can be created
+ /// in the current thread.
+ pub fn ensure_local_capacity(&self, capacity: jint) -> Result<()> {
+ jni_void_call!(self.internal, EnsureLocalCapacity, capacity);
+ Ok(())
+ }
+
+ /// Bind function pointers to native methods of class
+ /// according to method name and signature.
+ /// For details see [documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives).
+ pub fn register_native_methods<'c, T>(&self, class: T, methods: &[NativeMethod]) -> Result<()>
+ where
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ let jni_native_methods: Vec<JNINativeMethod> = methods
+ .iter()
+ .map(|nm| JNINativeMethod {
+ name: nm.name.as_ptr() as *mut c_char,
+ signature: nm.sig.as_ptr() as *mut c_char,
+ fnPtr: nm.fn_ptr,
+ })
+ .collect();
+ let res = jni_non_void_call!(
+ self.internal,
+ RegisterNatives,
+ class.into_raw(),
+ jni_native_methods.as_ptr(),
+ jni_native_methods.len() as jint
+ );
+ jni_error_code_to_result(res)
+ }
+
+ /// Unbind all native methods of class.
+ pub fn unregister_native_methods<'c, T>(&self, class: T) -> Result<()>
+ where
+ T: Desc<'a, JClass<'c>>,
+ {
+ let class = class.lookup(self)?;
+ let res = jni_non_void_call!(self.internal, UnregisterNatives, class.into_raw());
+ jni_error_code_to_result(res)
+ }
+
+ /// Return an AutoArray of the given Java array.
+ ///
+ /// The result is valid until the AutoArray object goes out of scope, when the
+ /// release happens automatically according to the mode parameter.
+ ///
+ /// Since the returned array may be a copy of the Java array, changes made to the
+ /// returned array will not necessarily be reflected in the original array until
+ /// the corresponding Release*ArrayElements JNI method is called.
+ /// AutoArray has a commit() method, to force a copy of the array if needed (and without
+ /// releasing it).
+
+ /// Prefer to use the convenience wrappers:
+ /// [`get_int_array_elements`](struct.JNIEnv.html#method.get_int_array_elements)
+ /// [`get_long_array_elements`](struct.JNIEnv.html#method.get_long_array_elements)
+ /// [`get_byte_array_elements`](struct.JNIEnv.html#method.get_byte_array_elements)
+ /// [`get_boolean_array_elements`](struct.JNIEnv.html#method.get_boolean_array_elements)
+ /// [`get_char_array_elements`](struct.JNIEnv.html#method.get_char_array_elements)
+ /// [`get_short_array_elements`](struct.JNIEnv.html#method.get_short_array_elements)
+ /// [`get_float_array_elements`](struct.JNIEnv.html#method.get_float_array_elements)
+ /// [`get_double_array_elements`](struct.JNIEnv.html#method.get_double_array_elements)
+ /// And the associated [`AutoArray`](struct.objects.AutoArray) struct.
+ pub fn get_array_elements<T: TypeArray>(
+ &self,
+ array: jarray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, T>> {
+ non_null!(array, "get_array_elements array argument");
+ AutoArray::new(self, unsafe { JObject::from_raw(array) }, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_int_array_elements(
+ &self,
+ array: jintArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jint>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_long_array_elements(
+ &self,
+ array: jlongArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jlong>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_byte_array_elements(
+ &self,
+ array: jbyteArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jbyte>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_boolean_array_elements(
+ &self,
+ array: jbooleanArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jboolean>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_char_array_elements(
+ &self,
+ array: jcharArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jchar>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_short_array_elements(
+ &self,
+ array: jshortArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jshort>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_float_array_elements(
+ &self,
+ array: jfloatArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jfloat>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// See also [`get_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_double_array_elements(
+ &self,
+ array: jdoubleArray,
+ mode: ReleaseMode,
+ ) -> Result<AutoArray<'a, jdouble>> {
+ self.get_array_elements(array, mode)
+ }
+
+ /// Return an AutoPrimitiveArray of the given Java primitive array.
+ ///
+ /// The result is valid until the corresponding AutoPrimitiveArray object goes out of scope,
+ /// when the release happens automatically according to the mode parameter.
+ ///
+ /// Given that Critical sections must be as short as possible, and that they come with a
+ /// number of important restrictions (see GetPrimitiveArrayCritical JNI doc), use this
+ /// wrapper wisely, to avoid holding the array longer that strictly necessary.
+ /// In any case, you can:
+ /// - Use std::mem::drop explicitly, to force / anticipate resource release.
+ /// - Use a nested scope, to release the array at the nested scope's exit.
+ ///
+ /// Since the returned array may be a copy of the Java array, changes made to the
+ /// returned array will not necessarily be reflected in the original array until
+ /// ReleasePrimitiveArrayCritical is called; which happens at AutoPrimitiveArray
+ /// destruction.
+ ///
+ /// If the given array is `null`, an `Error::NullPtr` is returned.
+ ///
+ /// See also [`get_byte_array_elements`](struct.JNIEnv.html#method.get_array_elements)
+ pub fn get_primitive_array_critical(
+ &self,
+ array: jarray,
+ mode: ReleaseMode,
+ ) -> Result<AutoPrimitiveArray> {
+ non_null!(array, "get_primitive_array_critical array argument");
+ let mut is_copy: jboolean = 0xff;
+ // Even though this method may throw OoME, use `jni_unchecked`
+ // instead of `jni_non_null_call` to remove (a slight) overhead
+ // of exception checking. An error will still be detected as a `null`
+ // result inside AutoPrimitiveArray ctor; and, as this method is unlikely
+ // to create a copy, an OoME is highly unlikely.
+ let ptr = jni_unchecked!(
+ self.internal,
+ GetPrimitiveArrayCritical,
+ array,
+ &mut is_copy
+ );
+ AutoPrimitiveArray::new(
+ self,
+ unsafe { JObject::from_raw(array) },
+ ptr,
+ mode,
+ is_copy == sys::JNI_TRUE,
+ )
+ }
+}
+
+/// Native method descriptor.
+pub struct NativeMethod {
+ /// Name of method.
+ pub name: JNIString,
+ /// Method signature.
+ pub sig: JNIString,
+ /// Pointer to native function with signature
+ /// `fn(env: JNIEnv, class: JClass, ...arguments according to sig) -> RetType`
+ /// for static methods or
+ /// `fn(env: JNIEnv, object: JObject, ...arguments according to sig) -> RetType`
+ /// for instance methods.
+ pub fn_ptr: *mut c_void,
+}
+
+/// Guard for a lock on a java object. This gets returned from the `lock_obj`
+/// method.
+pub struct MonitorGuard<'a> {
+ obj: sys::jobject,
+ env: *mut sys::JNIEnv,
+ life: PhantomData<&'a ()>,
+}
+
+impl<'a> Drop for MonitorGuard<'a> {
+ fn drop(&mut self) {
+ let res: Result<()> = catch!({
+ jni_unchecked!(self.env, MonitorExit, self.obj);
+ Ok(())
+ });
+
+ if let Err(e) = res {
+ warn!("error releasing java monitor: {}", e)
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/macros.rs b/0.20.0/src/wrapper/macros.rs
new file mode 100644
index 0000000..68604ed
--- /dev/null
+++ b/0.20.0/src/wrapper/macros.rs
@@ -0,0 +1,142 @@
+// A JNI call that is expected to return a non-null pointer when successful.
+// If a null pointer is returned, it is converted to an Err.
+// Returns Err if there is a pending exception after the call.
+macro_rules! jni_non_null_call {
+ ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({
+ let res = jni_non_void_call!($jnienv, $name $(, $args)*);
+ non_null!(res, concat!(stringify!($name), " result"))
+ })
+}
+
+// A non-void JNI call. May return anything — primitives, references, error codes.
+// Returns Err if there is a pending exception after the call.
+macro_rules! jni_non_void_call {
+ ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({
+ log::trace!("calling checked jni method: {}", stringify!($name));
+
+ #[allow(unused_unsafe)]
+ let res = unsafe {
+ jni_method!($jnienv, $name)($jnienv, $($args),*)
+ };
+
+ check_exception!($jnienv);
+ res
+ })
+}
+
+macro_rules! non_null {
+ ( $obj:expr, $ctx:expr ) => {
+ if $obj.is_null() {
+ return Err($crate::errors::Error::NullPtr($ctx));
+ } else {
+ $obj
+ }
+ };
+}
+
+// A void JNI call.
+// Returns Err if there is a pending exception after the call.
+macro_rules! jni_void_call {
+ ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({
+ log::trace!("calling checked jni method: {}", stringify!($name));
+
+ #[allow(unused_unsafe)]
+ unsafe {
+ jni_method!($jnienv, $name)($jnienv, $($args),*)
+ };
+
+ check_exception!($jnienv);
+ })
+}
+
+// A JNI call that does not check for exceptions or verify
+// error codes (if any).
+macro_rules! jni_unchecked {
+ ( $jnienv:expr, $name:tt $(, $args:expr )* ) => ({
+ log::trace!("calling unchecked jni method: {}", stringify!($name));
+
+ #[allow(unused_unsafe)]
+ unsafe {
+ jni_method!($jnienv, $name)($jnienv, $($args),*)
+ }
+ })
+}
+
+macro_rules! jni_method {
+ ( $jnienv:expr, $name:tt ) => {{
+ log::trace!("looking up jni method {}", stringify!($name));
+ let env = $jnienv;
+ match deref!(deref!(env, "JNIEnv"), "*JNIEnv").$name {
+ Some(method) => {
+ log::trace!("found jni method");
+ method
+ }
+ None => {
+ log::trace!("jnienv method not defined, returning error");
+ return Err($crate::errors::Error::JNIEnvMethodNotFound(stringify!(
+ $name
+ )));
+ }
+ }
+ }};
+}
+
+macro_rules! check_exception {
+ ( $jnienv:expr ) => {
+ log::trace!("checking for exception");
+ let check = { jni_unchecked!($jnienv, ExceptionCheck) } == $crate::sys::JNI_TRUE;
+ if check {
+ log::trace!("exception found, returning error");
+ return Err($crate::errors::Error::JavaException);
+ }
+ log::trace!("no exception found");
+ };
+}
+
+macro_rules! catch {
+ ( move $b:block ) => {
+ (move || $b)()
+ };
+ ( $b:block ) => {
+ (|| $b)()
+ };
+}
+
+macro_rules! java_vm_unchecked {
+ ( $java_vm:expr, $name:tt $(, $args:expr )* ) => ({
+ log::trace!("calling unchecked JavaVM method: {}", stringify!($name));
+ java_vm_method!($java_vm, $name)($java_vm, $($args),*)
+ })
+}
+
+macro_rules! java_vm_method {
+ ( $jnienv:expr, $name:tt ) => {{
+ log::trace!("looking up JavaVM method {}", stringify!($name));
+ let env = $jnienv;
+ match deref!(deref!(env, "JavaVM"), "*JavaVM").$name {
+ Some(meth) => {
+ log::trace!("found JavaVM method");
+ meth
+ }
+ None => {
+ log::trace!("JavaVM method not defined, returning error");
+ return Err($crate::errors::Error::JavaVMMethodNotFound(stringify!(
+ $name
+ )));
+ }
+ }
+ }};
+}
+
+macro_rules! deref {
+ ( $obj:expr, $ctx:expr ) => {
+ if $obj.is_null() {
+ return Err($crate::errors::Error::NullDeref($ctx));
+ } else {
+ #[allow(unused_unsafe)]
+ unsafe {
+ *$obj
+ }
+ }
+ };
+}
diff --git a/0.20.0/src/wrapper/objects/auto_array.rs b/0.20.0/src/wrapper/objects/auto_array.rs
new file mode 100644
index 0000000..2c73fac
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/auto_array.rs
@@ -0,0 +1,137 @@
+use crate::sys::jsize;
+use log::error;
+
+use std::ptr::NonNull;
+
+use crate::objects::release_mode::ReleaseMode;
+use crate::sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jshort};
+use crate::{errors::*, objects::JObject, sys, JNIEnv};
+
+/// Trait to define type array access/release
+pub trait TypeArray {
+ /// getter
+ fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self>;
+
+ /// releaser
+ fn release(env: &JNIEnv, obj: JObject, ptr: NonNull<Self>, mode: i32) -> Result<()>;
+}
+
+// TypeArray builder
+macro_rules! type_array {
+ ( $jni_type:ty, $jni_get:tt, $jni_release:tt ) => {
+ /// $jni_type array access/release impl
+ impl TypeArray for $jni_type {
+ /// Get Java $jni_type array
+ fn get(env: &JNIEnv, obj: JObject, is_copy: &mut jboolean) -> Result<*mut Self> {
+ let internal = env.get_native_interface();
+ // Even though this method may throw OoME, use `jni_unchecked`
+ // instead of `jni_non_null_call` to remove (a slight) overhead
+ // of exception checking. An error will still be detected as a `null`
+ // result inside AutoArray ctor. Also, modern Hotspot in case of lack
+ // of memory will return null and won't throw an exception:
+ // https://sourcegraph.com/github.com/openjdk/jdk/-/blob/src/hotspot/share/memory/allocation.hpp#L488-489
+ let res = jni_unchecked!(internal, $jni_get, *obj, is_copy);
+ Ok(res)
+ }
+
+ /// Release Java $jni_type array
+ fn release(env: &JNIEnv, obj: JObject, ptr: NonNull<Self>, mode: i32) -> Result<()> {
+ let internal = env.get_native_interface();
+ jni_unchecked!(internal, $jni_release, *obj, ptr.as_ptr(), mode as i32);
+ Ok(())
+ }
+ }
+ };
+}
+
+type_array!(jint, GetIntArrayElements, ReleaseIntArrayElements);
+type_array!(jlong, GetLongArrayElements, ReleaseLongArrayElements);
+type_array!(jbyte, GetByteArrayElements, ReleaseByteArrayElements);
+type_array!(
+ jboolean,
+ GetBooleanArrayElements,
+ ReleaseBooleanArrayElements
+);
+type_array!(jchar, GetCharArrayElements, ReleaseCharArrayElements);
+type_array!(jshort, GetShortArrayElements, ReleaseShortArrayElements);
+type_array!(jfloat, GetFloatArrayElements, ReleaseFloatArrayElements);
+type_array!(jdouble, GetDoubleArrayElements, ReleaseDoubleArrayElements);
+
+/// Auto-release wrapper for pointer-based generic arrays.
+///
+/// This wrapper is used to wrap pointers returned by Get<Type>ArrayElements.
+/// While wrapped, the object can be accessed via the `From` impl.
+///
+/// AutoArray provides automatic array release through a call to appropriate
+/// Release<Type>ArrayElements when it goes out of scope.
+pub struct AutoArray<'a, T: TypeArray> {
+ obj: JObject<'a>,
+ ptr: NonNull<T>,
+ mode: ReleaseMode,
+ is_copy: bool,
+ env: JNIEnv<'a>,
+}
+
+impl<'a, T: TypeArray> AutoArray<'a, T> {
+ pub(crate) fn new(env: &JNIEnv<'a>, obj: JObject<'a>, mode: ReleaseMode) -> Result<Self> {
+ let mut is_copy: jboolean = 0xff;
+ Ok(AutoArray {
+ obj,
+ ptr: {
+ let ptr = T::get(env, obj, &mut is_copy)?;
+ NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?
+ },
+ mode,
+ is_copy: is_copy == sys::JNI_TRUE,
+ env: *env,
+ })
+ }
+
+ /// Get a reference to the wrapped pointer
+ pub fn as_ptr(&self) -> *mut T {
+ self.ptr.as_ptr()
+ }
+
+ /// Commits the changes to the array, if it is a copy
+ pub fn commit(&self) -> Result<()> {
+ self.release_array_elements(sys::JNI_COMMIT)
+ }
+
+ fn release_array_elements(&self, mode: i32) -> Result<()> {
+ T::release(&self.env, self.obj, self.ptr, mode)
+ }
+
+ /// Don't commit the changes to the array on release (if it is a copy).
+ /// This has no effect if the array is not a copy.
+ /// This method is useful to change the release mode of an array originally created
+ /// with `ReleaseMode::CopyBack`.
+ pub fn discard(&mut self) {
+ self.mode = ReleaseMode::NoCopyBack;
+ }
+
+ /// Indicates if the array is a copy or not
+ pub fn is_copy(&self) -> bool {
+ self.is_copy
+ }
+
+ /// Returns the array size
+ pub fn size(&self) -> Result<jsize> {
+ self.env.get_array_length(*self.obj)
+ }
+}
+
+impl<'a, T: TypeArray> Drop for AutoArray<'a, T> {
+ fn drop(&mut self) {
+ let res = self.release_array_elements(self.mode as i32);
+ match res {
+ Ok(()) => {}
+ Err(e) => error!("error releasing array: {:#?}", e),
+ }
+ }
+}
+
+impl<'a, T: TypeArray> From<&'a AutoArray<'a, T>> for *mut T {
+ fn from(other: &'a AutoArray<T>) -> *mut T {
+ other.as_ptr()
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/auto_local.rs b/0.20.0/src/wrapper/objects/auto_local.rs
new file mode 100644
index 0000000..6d6cd2f
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/auto_local.rs
@@ -0,0 +1,80 @@
+use std::mem;
+
+use log::debug;
+
+use crate::{objects::JObject, JNIEnv};
+
+/// Auto-delete wrapper for local refs.
+///
+/// Anything passed to a foreign method _and_ returned from JNI methods is considered a local ref
+/// unless it is specified otherwise.
+/// These refs are automatically deleted once the foreign method exits, but it's possible that
+/// they may reach the JVM-imposed limit before that happens.
+///
+/// This wrapper provides automatic local ref deletion when it goes out of
+/// scope.
+///
+/// NOTE: This comes with some potential safety risks. DO NOT use this to wrap
+/// something unless you're SURE it won't be used after this wrapper gets
+/// dropped. Otherwise, you'll get a nasty JVM crash.
+///
+/// See also the [JNI specification][spec-references] for details on referencing Java objects
+/// and some [extra information][android-jni-references].
+///
+/// [spec-references]: https://docs.oracle.com/en/java/javase/12/docs/specs/jni/design.html#referencing-java-objects
+/// [android-jni-references]: https://developer.android.com/training/articles/perf-jni#local-and-global-references
+pub struct AutoLocal<'a: 'b, 'b> {
+ obj: JObject<'a>,
+ env: &'b JNIEnv<'a>,
+}
+
+impl<'a, 'b> AutoLocal<'a, 'b> {
+ /// Creates a new auto-delete wrapper for a local ref.
+ ///
+ /// Once this wrapper goes out of scope, the `delete_local_ref` will be
+ /// called on the object. While wrapped, the object can be accessed via
+ /// the `Deref` impl.
+ pub fn new(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Self {
+ AutoLocal { obj, env }
+ }
+
+ /// Forget the wrapper, returning the original object.
+ ///
+ /// This prevents `delete_local_ref` from being called when the `AutoLocal`
+ /// gets
+ /// dropped. You must either remember to delete the local ref manually, or
+ /// be
+ /// ok with it getting deleted once the foreign method returns.
+ pub fn forget(self) -> JObject<'a> {
+ let obj = self.obj;
+ mem::forget(self);
+ obj
+ }
+
+ /// Get a reference to the wrapped object
+ ///
+ /// Unlike `forget`, this ensures the wrapper from being dropped while the
+ /// returned `JObject` is still live.
+ pub fn as_obj<'c>(&self) -> JObject<'c>
+ where
+ 'a: 'c,
+ {
+ self.obj
+ }
+}
+
+impl<'a, 'b> Drop for AutoLocal<'a, 'b> {
+ fn drop(&mut self) {
+ let res = self.env.delete_local_ref(self.obj);
+ match res {
+ Ok(()) => {}
+ Err(e) => debug!("error dropping global ref: {:#?}", e),
+ }
+ }
+}
+
+impl<'a> From<&'a AutoLocal<'a, '_>> for JObject<'a> {
+ fn from(other: &'a AutoLocal) -> JObject<'a> {
+ other.as_obj()
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/auto_primitive_array.rs b/0.20.0/src/wrapper/objects/auto_primitive_array.rs
new file mode 100644
index 0000000..d67b1d7
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/auto_primitive_array.rs
@@ -0,0 +1,90 @@
+use log::debug;
+
+use crate::sys::jsize;
+use crate::wrapper::objects::ReleaseMode;
+use crate::{errors::*, objects::JObject, JNIEnv};
+use std::os::raw::c_void;
+use std::ptr::NonNull;
+
+/// Auto-release wrapper for pointer-based primitive arrays.
+///
+/// This wrapper is used to wrap pointers returned by GetPrimitiveArrayCritical.
+/// While wrapped, the object can be accessed via the `From` impl.
+///
+/// AutoPrimitiveArray provides automatic array release through a call to
+/// ReleasePrimitiveArrayCritical when it goes out of scope.
+pub struct AutoPrimitiveArray<'a: 'b, 'b> {
+ obj: JObject<'a>,
+ ptr: NonNull<c_void>,
+ mode: ReleaseMode,
+ is_copy: bool,
+ env: &'b JNIEnv<'a>,
+}
+
+impl<'a, 'b> AutoPrimitiveArray<'a, 'b> {
+ pub(crate) fn new(
+ env: &'b JNIEnv<'a>,
+ obj: JObject<'a>,
+ ptr: *mut c_void,
+ mode: ReleaseMode,
+ is_copy: bool,
+ ) -> Result<Self> {
+ Ok(AutoPrimitiveArray {
+ obj,
+ ptr: NonNull::new(ptr).ok_or(Error::NullPtr("Non-null ptr expected"))?,
+ mode,
+ is_copy,
+ env,
+ })
+ }
+
+ /// Get a reference to the wrapped pointer
+ pub fn as_ptr(&self) -> *mut c_void {
+ self.ptr.as_ptr()
+ }
+
+ fn release_primitive_array_critical(&mut self, mode: i32) -> Result<()> {
+ jni_unchecked!(
+ self.env.get_native_interface(),
+ ReleasePrimitiveArrayCritical,
+ *self.obj,
+ self.ptr.as_mut(),
+ mode
+ );
+ Ok(())
+ }
+
+ /// Don't copy the changes to the array on release (if it is a copy).
+ /// This has no effect if the array is not a copy.
+ /// This method is useful to change the release mode of an array originally created
+ /// with `ReleaseMode::CopyBack`.
+ pub fn discard(&mut self) {
+ self.mode = ReleaseMode::NoCopyBack;
+ }
+
+ /// Indicates if the array is a copy or not
+ pub fn is_copy(&self) -> bool {
+ self.is_copy
+ }
+
+ /// Returns the array size
+ pub fn size(&self) -> Result<jsize> {
+ self.env.get_array_length(*self.obj)
+ }
+}
+
+impl<'a, 'b> Drop for AutoPrimitiveArray<'a, 'b> {
+ fn drop(&mut self) {
+ let res = self.release_primitive_array_critical(self.mode as i32);
+ match res {
+ Ok(()) => {}
+ Err(e) => debug!("error releasing primitive array: {:#?}", e),
+ }
+ }
+}
+
+impl<'a> From<&'a AutoPrimitiveArray<'a, '_>> for *mut c_void {
+ fn from(other: &'a AutoPrimitiveArray) -> *mut c_void {
+ other.as_ptr()
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/global_ref.rs b/0.20.0/src/wrapper/objects/global_ref.rs
new file mode 100644
index 0000000..7e8b81c
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/global_ref.rs
@@ -0,0 +1,107 @@
+use std::{convert::From, sync::Arc};
+
+use log::{debug, warn};
+
+use crate::{errors::Result, objects::JObject, sys, JNIEnv, JavaVM};
+
+/// A global JVM reference. These are "pinned" by the garbage collector and are
+/// guaranteed to not get collected until released. Thus, this is allowed to
+/// outlive the `JNIEnv` that it came from and can be used in other threads.
+///
+/// `GlobalRef` can be cloned to use _the same_ global reference in different
+/// contexts. If you want to create yet another global ref to the same java object
+/// you may call `JNIEnv#new_global_ref` just like you do when create `GlobalRef`
+/// from a local reference.
+///
+/// Underlying global reference will be dropped, when the last instance
+/// of `GlobalRef` leaves its scope.
+///
+/// It is _recommended_ that a native thread that drops the global reference is attached
+/// to the Java thread (i.e., has an instance of `JNIEnv`). If the native thread is *not* attached,
+/// the `GlobalRef#drop` will print a warning and implicitly `attach` and `detach` it, which
+/// significantly affects performance.
+
+#[derive(Clone, Debug)]
+pub struct GlobalRef {
+ inner: Arc<GlobalRefGuard>,
+}
+
+#[derive(Debug)]
+struct GlobalRefGuard {
+ obj: JObject<'static>,
+ vm: JavaVM,
+}
+
+unsafe impl Send for GlobalRef {}
+unsafe impl Sync for GlobalRef {}
+
+impl<'a> From<&'a GlobalRef> for JObject<'a> {
+ fn from(other: &'a GlobalRef) -> JObject<'a> {
+ other.as_obj()
+ }
+}
+
+impl GlobalRef {
+ /// Creates a new wrapper for a global reference.
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid raw global reference that should be created with `NewGlobalRef` JNI function.
+ pub(crate) unsafe fn from_raw(vm: JavaVM, raw_global_ref: sys::jobject) -> Self {
+ GlobalRef {
+ inner: Arc::new(GlobalRefGuard::from_raw(vm, raw_global_ref)),
+ }
+ }
+
+ /// Get the object from the global ref
+ ///
+ /// This borrows the ref and prevents it from being dropped as long as the
+ /// JObject sticks around.
+ pub fn as_obj(&self) -> JObject {
+ self.inner.as_obj()
+ }
+}
+
+impl GlobalRefGuard {
+ /// Creates a new global reference guard. This assumes that `NewGlobalRef`
+ /// has already been called.
+ unsafe fn from_raw(vm: JavaVM, obj: sys::jobject) -> Self {
+ GlobalRefGuard {
+ obj: JObject::from_raw(obj),
+ vm,
+ }
+ }
+
+ /// Get the object from the global ref
+ ///
+ /// This borrows the ref and prevents it from being dropped as long as the
+ /// JObject sticks around.
+ pub fn as_obj(&self) -> JObject {
+ self.obj
+ }
+}
+
+impl Drop for GlobalRefGuard {
+ fn drop(&mut self) {
+ fn drop_impl(env: &JNIEnv, global_ref: JObject) -> Result<()> {
+ let internal = env.get_native_interface();
+ // This method is safe to call in case of pending exceptions (see chapter 2 of the spec)
+ jni_unchecked!(internal, DeleteGlobalRef, global_ref.into_raw());
+ Ok(())
+ }
+
+ let res = match self.vm.get_env() {
+ Ok(env) => drop_impl(&env, self.as_obj()),
+ Err(_) => {
+ warn!("Dropping a GlobalRef in a detached thread. Fix your code if this message appears frequently (see the GlobalRef docs).");
+ self.vm
+ .attach_current_thread()
+ .and_then(|env| drop_impl(&env, self.as_obj()))
+ }
+ };
+
+ if let Err(err) = res {
+ debug!("error dropping global ref: {:#?}", err);
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jbytebuffer.rs b/0.20.0/src/wrapper/objects/jbytebuffer.rs
new file mode 100644
index 0000000..fd6cb60
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jbytebuffer.rs
@@ -0,0 +1,49 @@
+use crate::{objects::JObject, sys::jobject};
+
+/// Lifetime'd representation of a `jobject` that is an instance of the
+/// ByteBuffer Java class. Just a `JObject` wrapped in a new class.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug)]
+pub struct JByteBuffer<'a>(JObject<'a>);
+
+impl<'a> ::std::ops::Deref for JByteBuffer<'a> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'a> From<JByteBuffer<'a>> for JObject<'a> {
+ fn from(other: JByteBuffer) -> JObject {
+ other.0
+ }
+}
+
+impl<'a> From<JObject<'a>> for JByteBuffer<'a> {
+ fn from(other: JObject) -> Self {
+ unsafe { Self::from_raw(other.into_raw()) }
+ }
+}
+
+impl<'a> std::default::Default for JByteBuffer<'a> {
+ fn default() -> Self {
+ Self(JObject::null())
+ }
+}
+
+impl<'a> JByteBuffer<'a> {
+ /// Creates a [`JByteBuffer`] that wraps the given `raw` [`jobject`]
+ ///
+ /// # Safety
+ /// No runtime check is made to verify that the given [`jobject`] is an instance of
+ /// a `ByteBuffer`.
+ pub unsafe fn from_raw(raw: jobject) -> Self {
+ Self(JObject::from_raw(raw as jobject))
+ }
+
+ /// Unwrap to the raw jni type.
+ pub fn into_raw(self) -> jobject {
+ self.0.into_raw() as jobject
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jclass.rs b/0.20.0/src/wrapper/objects/jclass.rs
new file mode 100644
index 0000000..aa7194b
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jclass.rs
@@ -0,0 +1,53 @@
+use crate::{
+ objects::JObject,
+ sys::{jclass, jobject},
+};
+
+/// Lifetime'd representation of a `jclass`. Just a `JObject` wrapped in a new
+/// class.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug)]
+pub struct JClass<'a>(JObject<'a>);
+
+impl<'a> ::std::ops::Deref for JClass<'a> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'a> From<JClass<'a>> for JObject<'a> {
+ fn from(other: JClass) -> JObject {
+ other.0
+ }
+}
+
+/// This conversion assumes that the `JObject` is a pointer to a class object.
+impl<'a> From<JObject<'a>> for JClass<'a> {
+ fn from(other: JObject) -> Self {
+ unsafe { Self::from_raw(other.into_raw()) }
+ }
+}
+
+impl<'a> std::default::Default for JClass<'a> {
+ fn default() -> Self {
+ Self(JObject::null())
+ }
+}
+
+impl<'a> JClass<'a> {
+ /// Creates a [`JClass`] that wraps the given `raw` [`jclass`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer or `null`
+ pub unsafe fn from_raw(raw: jclass) -> Self {
+ Self(JObject::from_raw(raw as jobject))
+ }
+
+ /// Unwrap to the raw jni type.
+ pub fn into_raw(self) -> jclass {
+ self.0.into_raw() as jclass
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jfieldid.rs b/0.20.0/src/wrapper/objects/jfieldid.rs
new file mode 100644
index 0000000..c445ec6
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jfieldid.rs
@@ -0,0 +1,48 @@
+use crate::sys::jfieldID;
+
+/// Wrapper around [`jfieldID`] that implements `Send` + `Sync` since method IDs
+/// are valid across threads (not tied to a `JNIEnv`).
+///
+/// There is no lifetime associated with these since they aren't garbage
+/// collected like objects and their lifetime is not implicitly connected with
+/// the scope in which they are queried.
+///
+/// It matches C's representation of the raw pointer, so it can be used in any
+/// of the extern function argument positions that would take a [`jfieldID`].
+///
+/// # Safety
+///
+/// According to the JNI spec field IDs may be invalidated when the
+/// corresponding class is unloaded.
+///
+/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the
+/// excessive cost of having every Method ID be associated with a global
+/// reference to the corresponding class then it is the developers
+/// responsibility to ensure they hold some class reference for the lifetime of
+/// cached method IDs.
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct JFieldID {
+ internal: jfieldID,
+}
+
+// Field IDs are valid across threads (not tied to a JNIEnv)
+unsafe impl Send for JFieldID {}
+unsafe impl Sync for JFieldID {}
+
+impl JFieldID {
+ /// Creates a [`JFieldID`] that wraps the given `raw` [`jfieldID`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid, non-`null` ID
+ pub unsafe fn from_raw(raw: jfieldID) -> Self {
+ debug_assert!(!raw.is_null(), "from_raw fieldID argument");
+ Self { internal: raw }
+ }
+
+ /// Unwrap to the internal jni type.
+ pub fn into_raw(self) -> jfieldID {
+ self.internal
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jlist.rs b/0.20.0/src/wrapper/objects/jlist.rs
new file mode 100644
index 0000000..1f07f71
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jlist.rs
@@ -0,0 +1,202 @@
+use crate::{
+ errors::*,
+ objects::{JMethodID, JObject, JValue},
+ signature::{Primitive, ReturnType},
+ sys::jint,
+ JNIEnv,
+};
+
+/// Wrapper for JObjects that implement `java/util/List`. Provides methods to get,
+/// add, and remove elements.
+///
+/// Looks up the class and method ids on creation rather than for every method
+/// call.
+pub struct JList<'a: 'b, 'b> {
+ internal: JObject<'a>,
+ get: JMethodID,
+ add: JMethodID,
+ add_idx: JMethodID,
+ remove: JMethodID,
+ size: JMethodID,
+ env: &'b JNIEnv<'a>,
+}
+
+impl<'a: 'b, 'b> ::std::ops::Deref for JList<'a, 'b> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.internal
+ }
+}
+
+impl<'a: 'b, 'b> From<JList<'a, 'b>> for JObject<'a> {
+ fn from(other: JList<'a, 'b>) -> JObject<'a> {
+ other.internal
+ }
+}
+
+impl<'a: 'b, 'b> JList<'a, 'b> {
+ /// Create a map from the environment and an object. This looks up the
+ /// necessary class and method ids to call all of the methods on it so that
+ /// exra work doesn't need to be done on every method call.
+ pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result<JList<'a, 'b>> {
+ let class = env.auto_local(env.find_class("java/util/List")?);
+
+ let get = env.get_method_id(&class, "get", "(I)Ljava/lang/Object;")?;
+ let add = env.get_method_id(&class, "add", "(Ljava/lang/Object;)Z")?;
+ let add_idx = env.get_method_id(&class, "add", "(ILjava/lang/Object;)V")?;
+ let remove = env.get_method_id(&class, "remove", "(I)Ljava/lang/Object;")?;
+ let size = env.get_method_id(&class, "size", "()I")?;
+
+ Ok(JList {
+ internal: obj,
+ get,
+ add,
+ add_idx,
+ remove,
+ size,
+ env,
+ })
+ }
+
+ /// Look up the value for a key. Returns `Some` if it's found and `None` if
+ /// a null pointer would be returned.
+ pub fn get(&self, idx: jint) -> Result<Option<JObject<'a>>> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.get,
+ ReturnType::Object,
+ &[JValue::from(idx).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Append an element to the list
+ pub fn add(&self, value: JObject<'a>) -> Result<()> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.add,
+ ReturnType::Primitive(Primitive::Boolean),
+ &[JValue::from(value).to_jni()],
+ );
+
+ let _ = result?;
+ Ok(())
+ }
+
+ /// Insert an element at a specific index
+ pub fn insert(&self, idx: jint, value: JObject<'a>) -> Result<()> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.add_idx,
+ ReturnType::Primitive(Primitive::Void),
+ &[JValue::from(idx).to_jni(), JValue::from(value).to_jni()],
+ );
+
+ let _ = result?;
+ Ok(())
+ }
+
+ /// Remove an element from the list by index
+ pub fn remove(&self, idx: jint) -> Result<Option<JObject<'a>>> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.remove,
+ ReturnType::Object,
+ &[JValue::from(idx).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Get the size of the list
+ pub fn size(&self) -> Result<jint> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.size,
+ ReturnType::Primitive(Primitive::Int),
+ &[],
+ );
+
+ result.and_then(|v| v.i())
+ }
+
+ /// Pop the last element from the list
+ ///
+ /// Note that this calls `size()` to determine the last index.
+ pub fn pop(&self) -> Result<Option<JObject<'a>>> {
+ let size = self.size()?;
+ if size == 0 {
+ return Ok(None);
+ }
+
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.remove,
+ ReturnType::Object,
+ &[JValue::from(size - 1).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Get key/value iterator for the map. This is done by getting the
+ /// `EntrySet` from java and iterating over it.
+ pub fn iter(&self) -> Result<JListIter<'a, 'b, '_>> {
+ Ok(JListIter {
+ list: self,
+ current: 0,
+ size: self.size()?,
+ })
+ }
+}
+
+/// An iterator over the keys and values in a map.
+///
+/// TODO: make the iterator implementation for java iterators its own thing
+/// and generic enough to use elsewhere.
+pub struct JListIter<'a: 'b, 'b: 'c, 'c> {
+ list: &'c JList<'a, 'b>,
+ current: jint,
+ size: jint,
+}
+
+impl<'a: 'b, 'b: 'c, 'c> Iterator for JListIter<'a, 'b, 'c> {
+ type Item = JObject<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.current == self.size {
+ return None;
+ }
+ let res = self.list.get(self.current);
+ match res {
+ Ok(elem) => {
+ self.current += 1;
+ elem
+ }
+ Err(_) => {
+ self.current = self.size;
+ None
+ }
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jmap.rs b/0.20.0/src/wrapper/objects/jmap.rs
new file mode 100644
index 0000000..bc391e3
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jmap.rs
@@ -0,0 +1,246 @@
+use crate::{
+ errors::*,
+ objects::{AutoLocal, JMethodID, JObject, JValue},
+ signature::{Primitive, ReturnType},
+ JNIEnv,
+};
+
+/// Wrapper for JObjects that implement `java/util/Map`. Provides methods to get
+/// and set entries and a way to iterate over key/value pairs.
+///
+/// Looks up the class and method ids on creation rather than for every method
+/// call.
+pub struct JMap<'a: 'b, 'b> {
+ internal: JObject<'a>,
+ class: AutoLocal<'a, 'b>,
+ get: JMethodID,
+ put: JMethodID,
+ remove: JMethodID,
+ env: &'b JNIEnv<'a>,
+}
+
+impl<'a: 'b, 'b> ::std::ops::Deref for JMap<'a, 'b> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.internal
+ }
+}
+
+impl<'a: 'b, 'b> From<JMap<'a, 'b>> for JObject<'a> {
+ fn from(other: JMap<'a, 'b>) -> JObject<'a> {
+ other.internal
+ }
+}
+
+impl<'a: 'b, 'b> JMap<'a, 'b> {
+ /// Create a map from the environment and an object. This looks up the
+ /// necessary class and method ids to call all of the methods on it so that
+ /// exra work doesn't need to be done on every method call.
+ pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result<JMap<'a, 'b>> {
+ let class = env.auto_local(env.find_class("java/util/Map")?);
+
+ let get = env.get_method_id(&class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;")?;
+ let put = env.get_method_id(
+ &class,
+ "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;\
+ )Ljava/lang/Object;",
+ )?;
+
+ let remove =
+ env.get_method_id(&class, "remove", "(Ljava/lang/Object;)Ljava/lang/Object;")?;
+
+ Ok(JMap {
+ internal: obj,
+ class,
+ get,
+ put,
+ remove,
+ env,
+ })
+ }
+
+ /// Look up the value for a key. Returns `Some` if it's found and `None` if
+ /// a null pointer would be returned.
+ pub fn get(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.get,
+ ReturnType::Object,
+ &[JValue::from(key).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Look up the value for a key. Returns `Some` with the old value if the
+ /// key already existed and `None` if it's a new key.
+ pub fn put(&self, key: JObject<'a>, value: JObject<'a>) -> Result<Option<JObject<'a>>> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.put,
+ ReturnType::Object,
+ &[JValue::from(key).to_jni(), JValue::from(value).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Remove a value from the map. Returns `Some` with the removed value and
+ /// `None` if there was no value for the key.
+ pub fn remove(&self, key: JObject<'a>) -> Result<Option<JObject<'a>>> {
+ let result = self.env.call_method_unchecked(
+ self.internal,
+ self.remove,
+ ReturnType::Object,
+ &[JValue::from(key).to_jni()],
+ );
+
+ match result {
+ Ok(val) => Ok(Some(val.l()?)),
+ Err(e) => match e {
+ Error::NullPtr(_) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
+ /// Get key/value iterator for the map. This is done by getting the
+ /// `EntrySet` from java and iterating over it.
+ pub fn iter(&self) -> Result<JMapIter<'a, 'b, '_>> {
+ let iter_class = self
+ .env
+ .auto_local(self.env.find_class("java/util/Iterator")?);
+
+ let has_next = self.env.get_method_id(&iter_class, "hasNext", "()Z")?;
+
+ let next = self
+ .env
+ .get_method_id(&iter_class, "next", "()Ljava/lang/Object;")?;
+
+ let entry_class = self
+ .env
+ .auto_local(self.env.find_class("java/util/Map$Entry")?);
+
+ let get_key = self
+ .env
+ .get_method_id(&entry_class, "getKey", "()Ljava/lang/Object;")?;
+
+ let get_value = self
+ .env
+ .get_method_id(&entry_class, "getValue", "()Ljava/lang/Object;")?;
+
+ // Get the iterator over Map entries.
+ // Use the local frame till #109 is resolved, so that implicitly looked-up
+ // classes are freed promptly.
+ let iter = self.env.with_local_frame(16, || {
+ let entry_set = self
+ .env
+ .call_method_unchecked(
+ self.internal,
+ (&self.class, "entrySet", "()Ljava/util/Set;"),
+ ReturnType::Object,
+ &[],
+ )?
+ .l()?;
+
+ let iter = self
+ .env
+ .call_method_unchecked(
+ entry_set,
+ ("java/util/Set", "iterator", "()Ljava/util/Iterator;"),
+ ReturnType::Object,
+ &[],
+ )?
+ .l()?;
+
+ Ok(iter)
+ })?;
+ let iter = self.env.auto_local(iter);
+
+ Ok(JMapIter {
+ map: self,
+ has_next,
+ next,
+ get_key,
+ get_value,
+ iter,
+ })
+ }
+}
+
+/// An iterator over the keys and values in a map.
+///
+/// TODO: make the iterator implementation for java iterators its own thing
+/// and generic enough to use elsewhere.
+pub struct JMapIter<'a, 'b, 'c> {
+ map: &'c JMap<'a, 'b>,
+ has_next: JMethodID,
+ next: JMethodID,
+ get_key: JMethodID,
+ get_value: JMethodID,
+ iter: AutoLocal<'a, 'b>,
+}
+
+impl<'a: 'b, 'b: 'c, 'c> JMapIter<'a, 'b, 'c> {
+ fn get_next(&self) -> Result<Option<(JObject<'a>, JObject<'a>)>> {
+ let iter = self.iter.as_obj();
+ let has_next = self
+ .map
+ .env
+ .call_method_unchecked(
+ iter,
+ self.has_next,
+ ReturnType::Primitive(Primitive::Boolean),
+ &[],
+ )?
+ .z()?;
+
+ if !has_next {
+ return Ok(None);
+ }
+ let next = self
+ .map
+ .env
+ .call_method_unchecked(iter, self.next, ReturnType::Object, &[])?
+ .l()?;
+
+ let key = self
+ .map
+ .env
+ .call_method_unchecked(next, self.get_key, ReturnType::Object, &[])?
+ .l()?;
+
+ let value = self
+ .map
+ .env
+ .call_method_unchecked(next, self.get_value, ReturnType::Object, &[])?
+ .l()?;
+
+ Ok(Some((key, value)))
+ }
+}
+
+impl<'a: 'b, 'b: 'c, 'c> Iterator for JMapIter<'a, 'b, 'c> {
+ type Item = (JObject<'a>, JObject<'a>);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.get_next() {
+ Ok(Some(n)) => Some(n),
+ _ => None,
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jmethodid.rs b/0.20.0/src/wrapper/objects/jmethodid.rs
new file mode 100644
index 0000000..892fe43
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jmethodid.rs
@@ -0,0 +1,48 @@
+use crate::sys::jmethodID;
+
+/// Wrapper around [`jmethodID`] that implements `Send` + `Sync` since method IDs
+/// are valid across threads (not tied to a `JNIEnv`).
+///
+/// There is no lifetime associated with these since they aren't garbage
+/// collected like objects and their lifetime is not implicitly connected with
+/// the scope in which they are queried.
+///
+/// It matches C's representation of the raw pointer, so it can be used in any
+/// of the extern function argument positions that would take a [`jmethodID`].
+///
+/// # Safety
+///
+/// According to the JNI spec method IDs may be invalidated when the
+/// corresponding class is unloaded.
+///
+/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the
+/// excessive cost of having every Method ID be associated with a global
+/// reference to the corresponding class then it is the developers
+/// responsibility to ensure they hold some class reference for the lifetime of
+/// cached method IDs.
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug)]
+pub struct JMethodID {
+ internal: jmethodID,
+}
+
+// Method IDs are valid across threads (not tied to a JNIEnv)
+unsafe impl Send for JMethodID {}
+unsafe impl Sync for JMethodID {}
+
+impl JMethodID {
+ /// Creates a [`JMethodID`] that wraps the given `raw` [`jmethodID`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid, non-`null` ID
+ pub unsafe fn from_raw(raw: jmethodID) -> Self {
+ debug_assert!(!raw.is_null(), "from_raw methodID argument");
+ Self { internal: raw }
+ }
+
+ /// Unwrap to the internal jni type.
+ pub fn into_raw(self) -> jmethodID {
+ self.internal
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jobject.rs b/0.20.0/src/wrapper/objects/jobject.rs
new file mode 100644
index 0000000..1b55dae
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jobject.rs
@@ -0,0 +1,56 @@
+use std::marker::PhantomData;
+
+use crate::sys::jobject;
+
+/// Wrapper around `sys::jobject` that adds a lifetime. This prevents it from
+/// outliving the context in which it was acquired and getting GC'd out from
+/// under us. It matches C's representation of the raw pointer, so it can be
+/// used in any of the extern function argument positions that would take a
+/// `jobject`.
+///
+/// Most other types in the `objects` module deref to this, as they do in the C
+/// representation.
+#[repr(transparent)]
+#[derive(Clone, Copy, Debug)]
+pub struct JObject<'a> {
+ internal: jobject,
+ lifetime: PhantomData<&'a ()>,
+}
+
+impl<'a> ::std::ops::Deref for JObject<'a> {
+ type Target = jobject;
+
+ fn deref(&self) -> &Self::Target {
+ &self.internal
+ }
+}
+
+impl<'a> JObject<'a> {
+ /// Creates a [`JObject`] that wraps the given `raw` [`jobject`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer or `null`
+ pub unsafe fn from_raw(raw: jobject) -> Self {
+ Self {
+ internal: raw,
+ lifetime: PhantomData,
+ }
+ }
+
+ /// Unwrap to the internal jni type.
+ pub fn into_raw(self) -> jobject {
+ self.internal
+ }
+
+ /// Creates a new null object
+ pub fn null() -> JObject<'a> {
+ unsafe { Self::from_raw(std::ptr::null_mut() as jobject) }
+ }
+}
+
+impl<'a> std::default::Default for JObject<'a> {
+ fn default() -> Self {
+ Self::null()
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jstaticfieldid.rs b/0.20.0/src/wrapper/objects/jstaticfieldid.rs
new file mode 100644
index 0000000..4579b44
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jstaticfieldid.rs
@@ -0,0 +1,48 @@
+use crate::sys::jfieldID;
+
+/// Wrapper around [`jfieldID`] that implements `Send` + `Sync` since field IDs
+/// are valid across threads (not tied to a `JNIEnv`).
+///
+/// There is no lifetime associated with these since they aren't garbage
+/// collected like objects and their lifetime is not implicitly connected with
+/// the scope in which they are queried.
+///
+/// It matches C's representation of the raw pointer, so it can be used in any
+/// of the extern function argument positions that would take a [`jfieldID`].
+///
+/// # Safety
+///
+/// According to the JNI spec field IDs may be invalidated when the
+/// corresponding class is unloaded.
+///
+/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the
+/// excessive cost of having every Method ID be associated with a global
+/// reference to the corresponding class then it is the developers
+/// responsibility to ensure they hold some class reference for the lifetime of
+/// cached method IDs.
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug)]
+pub struct JStaticFieldID {
+ internal: jfieldID,
+}
+
+// Static Field IDs are valid across threads (not tied to a JNIEnv)
+unsafe impl Send for JStaticFieldID {}
+unsafe impl Sync for JStaticFieldID {}
+
+impl JStaticFieldID {
+ /// Creates a [`JStaticFieldID`] that wraps the given `raw` [`jfieldID`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid, non-`null` ID
+ pub unsafe fn from_raw(raw: jfieldID) -> Self {
+ debug_assert!(!raw.is_null(), "from_raw fieldID argument");
+ Self { internal: raw }
+ }
+
+ /// Unwrap to the internal jni type.
+ pub fn into_raw(self) -> jfieldID {
+ self.internal
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jstaticmethodid.rs b/0.20.0/src/wrapper/objects/jstaticmethodid.rs
new file mode 100644
index 0000000..ada3fb9
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jstaticmethodid.rs
@@ -0,0 +1,48 @@
+use crate::sys::jmethodID;
+
+/// Wrapper around [`jmethodID`] that implements `Send` + `Sync` since method IDs
+/// are valid across threads (not tied to a `JNIEnv`).
+///
+/// There is no lifetime associated with these since they aren't garbage
+/// collected like objects and their lifetime is not implicitly connected with
+/// the scope in which they are queried.
+///
+/// It matches C's representation of the raw pointer, so it can be used in any
+/// of the extern function argument positions that would take a [`jmethodID`].
+///
+/// # Safety
+///
+/// According to the JNI spec method IDs may be invalidated when the
+/// corresponding class is unloaded.
+///
+/// Since this constraint can't be encoded as a Rust lifetime, and to avoid the
+/// excessive cost of having every Method ID be associated with a global
+/// reference to the corresponding class then it is the developers
+/// responsibility to ensure they hold some class reference for the lifetime of
+/// cached method IDs.
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug)]
+pub struct JStaticMethodID {
+ internal: jmethodID,
+}
+
+// Method IDs are valid across threads (not tied to a JNIEnv)
+unsafe impl Send for JStaticMethodID {}
+unsafe impl Sync for JStaticMethodID {}
+
+impl JStaticMethodID {
+ /// Creates a [`JStaticMethodID`] that wraps the given `raw` [`jmethodID`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid, non-`null` ID
+ pub unsafe fn from_raw(raw: jmethodID) -> Self {
+ debug_assert!(!raw.is_null(), "from_raw methodID argument");
+ Self { internal: raw }
+ }
+
+ /// Unwrap to the internal jni type.
+ pub fn into_raw(self) -> jmethodID {
+ self.internal
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jstring.rs b/0.20.0/src/wrapper/objects/jstring.rs
new file mode 100644
index 0000000..a5df7aa
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jstring.rs
@@ -0,0 +1,52 @@
+use crate::{
+ objects::JObject,
+ sys::{jobject, jstring},
+};
+
+/// Lifetime'd representation of a `jstring`. Just a `JObject` wrapped in a new
+/// class.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct JString<'a>(JObject<'a>);
+
+impl<'a> ::std::ops::Deref for JString<'a> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'a> From<JString<'a>> for JObject<'a> {
+ fn from(other: JString) -> JObject {
+ other.0
+ }
+}
+
+impl<'a> From<JObject<'a>> for JString<'a> {
+ fn from(other: JObject) -> Self {
+ unsafe { Self::from_raw(other.into_raw()) }
+ }
+}
+
+impl<'a> std::default::Default for JString<'a> {
+ fn default() -> Self {
+ Self(JObject::null())
+ }
+}
+
+impl<'a> JString<'a> {
+ /// Creates a [`JString`] that wraps the given `raw` [`jstring`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer or `null`
+ pub unsafe fn from_raw(raw: jstring) -> Self {
+ Self(JObject::from_raw(raw as jobject))
+ }
+
+ /// Unwrap to the raw jni type.
+ pub fn into_raw(self) -> jstring {
+ self.0.into_raw() as jstring
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jthrowable.rs b/0.20.0/src/wrapper/objects/jthrowable.rs
new file mode 100644
index 0000000..f77f6e3
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jthrowable.rs
@@ -0,0 +1,52 @@
+use crate::{
+ objects::JObject,
+ sys::{jobject, jthrowable},
+};
+
+/// Lifetime'd representation of a `jthrowable`. Just a `JObject` wrapped in a
+/// new class.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct JThrowable<'a>(JObject<'a>);
+
+impl<'a> ::std::ops::Deref for JThrowable<'a> {
+ type Target = JObject<'a>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<'a> From<JThrowable<'a>> for JObject<'a> {
+ fn from(other: JThrowable) -> JObject {
+ other.0
+ }
+}
+
+impl<'a> From<JObject<'a>> for JThrowable<'a> {
+ fn from(other: JObject) -> Self {
+ unsafe { Self::from_raw(other.into_raw()) }
+ }
+}
+
+impl<'a> std::default::Default for JThrowable<'a> {
+ fn default() -> Self {
+ Self(JObject::null())
+ }
+}
+
+impl<'a> JThrowable<'a> {
+ /// Creates a [`JThrowable`] that wraps the given `raw` [`jthrowable`]
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer or `null`
+ pub unsafe fn from_raw(raw: jthrowable) -> Self {
+ Self(JObject::from_raw(raw as jobject))
+ }
+
+ /// Unwrap to the raw jni type.
+ pub fn into_raw(self) -> jthrowable {
+ self.0.into_raw() as jthrowable
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/jvalue.rs b/0.20.0/src/wrapper/objects/jvalue.rs
new file mode 100644
index 0000000..54829c0
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/jvalue.rs
@@ -0,0 +1,353 @@
+use std::convert::TryFrom;
+use std::mem::transmute;
+
+use log::trace;
+
+use crate::{errors::*, objects::JObject, signature::Primitive, sys::*};
+
+/// Rusty version of the JNI C `jvalue` enum. Used in Java method call arguments
+/// and returns.
+#[allow(missing_docs)]
+#[derive(Clone, Copy, Debug)]
+pub enum JValue<'a> {
+ Object(JObject<'a>),
+ Byte(jbyte),
+ Char(jchar),
+ Short(jshort),
+ Int(jint),
+ Long(jlong),
+ Bool(jboolean),
+ Float(jfloat),
+ Double(jdouble),
+ Void,
+}
+
+impl<'a> From<JValue<'a>> for jvalue {
+ fn from(other: JValue) -> jvalue {
+ other.to_jni()
+ }
+}
+
+impl<'a> JValue<'a> {
+ /// Convert the enum to its jni-compatible equivalent.
+ pub fn to_jni(self) -> jvalue {
+ let val: jvalue = match self {
+ JValue::Object(obj) => jvalue {
+ l: unsafe { transmute(obj) },
+ },
+ JValue::Byte(byte) => jvalue { b: byte },
+ JValue::Char(char) => jvalue { c: char },
+ JValue::Short(short) => jvalue { s: short },
+ JValue::Int(int) => jvalue { i: int },
+ JValue::Long(long) => jvalue { j: long },
+ JValue::Bool(boolean) => jvalue { b: boolean as i8 },
+ JValue::Float(float) => jvalue { f: float },
+ JValue::Double(double) => jvalue { d: double },
+ JValue::Void => jvalue {
+ l: ::std::ptr::null_mut(),
+ },
+ };
+ trace!("converted {:?} to jvalue {:?}", self, unsafe {
+ ::std::mem::transmute::<_, u64>(val)
+ });
+ val
+ }
+
+ /// Get the type name for the enum variant.
+ pub fn type_name(&self) -> &'static str {
+ match *self {
+ JValue::Void => "void",
+ JValue::Object(_) => "object",
+ JValue::Byte(_) => "byte",
+ JValue::Char(_) => "char",
+ JValue::Short(_) => "short",
+ JValue::Int(_) => "int",
+ JValue::Long(_) => "long",
+ JValue::Bool(_) => "bool",
+ JValue::Float(_) => "float",
+ JValue::Double(_) => "double",
+ }
+ }
+
+ /// Get the primitive type for the enum variant. If it's not a primitive
+ /// (i.e. an Object), returns None.
+ pub fn primitive_type(&self) -> Option<Primitive> {
+ Some(match *self {
+ JValue::Object(_) => return None,
+ JValue::Void => Primitive::Void,
+ JValue::Byte(_) => Primitive::Byte,
+ JValue::Char(_) => Primitive::Char,
+ JValue::Short(_) => Primitive::Short,
+ JValue::Int(_) => Primitive::Int,
+ JValue::Long(_) => Primitive::Long,
+ JValue::Bool(_) => Primitive::Boolean,
+ JValue::Float(_) => Primitive::Float,
+ JValue::Double(_) => Primitive::Double,
+ })
+ }
+
+ /// Try to unwrap to an Object.
+ pub fn l(self) -> Result<JObject<'a>> {
+ match self {
+ JValue::Object(obj) => Ok(obj),
+ _ => Err(Error::WrongJValueType("object", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a boolean.
+ pub fn z(self) -> Result<bool> {
+ match self {
+ JValue::Bool(b) => Ok(b == JNI_TRUE),
+ _ => Err(Error::WrongJValueType("bool", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a byte.
+ pub fn b(self) -> Result<jbyte> {
+ match self {
+ JValue::Byte(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jbyte", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a char.
+ pub fn c(self) -> Result<jchar> {
+ match self {
+ JValue::Char(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jchar", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a double.
+ pub fn d(self) -> Result<jdouble> {
+ match self {
+ JValue::Double(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jdouble", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a float.
+ pub fn f(self) -> Result<jfloat> {
+ match self {
+ JValue::Float(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jfloat", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to an int.
+ pub fn i(self) -> Result<jint> {
+ match self {
+ JValue::Int(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jint", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a long.
+ pub fn j(self) -> Result<jlong> {
+ match self {
+ JValue::Long(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jlong", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a short.
+ pub fn s(self) -> Result<jshort> {
+ match self {
+ JValue::Short(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("jshort", self.type_name())),
+ }
+ }
+
+ /// Try to unwrap to a void.
+ pub fn v(self) -> Result<()> {
+ match self {
+ JValue::Void => Ok(()),
+ _ => Err(Error::WrongJValueType("void", self.type_name())),
+ }
+ }
+}
+
+impl<'a, T: Into<JObject<'a>>> From<T> for JValue<'a> {
+ fn from(other: T) -> Self {
+ JValue::Object(other.into())
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for JObject<'a> {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Object(o) => Ok(o),
+ _ => Err(Error::WrongJValueType("object", value.type_name())),
+ }
+ }
+}
+
+impl<'a> From<bool> for JValue<'a> {
+ fn from(other: bool) -> Self {
+ JValue::Bool(if other { JNI_TRUE } else { JNI_FALSE })
+ }
+}
+
+// jbool
+impl<'a> From<jboolean> for JValue<'a> {
+ fn from(other: jboolean) -> Self {
+ JValue::Bool(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jboolean {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Bool(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("bool", value.type_name())),
+ }
+ }
+}
+
+// jchar
+impl<'a> From<jchar> for JValue<'a> {
+ fn from(other: jchar) -> Self {
+ JValue::Char(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jchar {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Char(c) => Ok(c),
+ _ => Err(Error::WrongJValueType("char", value.type_name())),
+ }
+ }
+}
+
+// jshort
+impl<'a> From<jshort> for JValue<'a> {
+ fn from(other: jshort) -> Self {
+ JValue::Short(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jshort {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Short(s) => Ok(s),
+ _ => Err(Error::WrongJValueType("short", value.type_name())),
+ }
+ }
+}
+
+// jfloat
+impl<'a> From<jfloat> for JValue<'a> {
+ fn from(other: jfloat) -> Self {
+ JValue::Float(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jfloat {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Float(f) => Ok(f),
+ _ => Err(Error::WrongJValueType("float", value.type_name())),
+ }
+ }
+}
+
+// jdouble
+impl<'a> From<jdouble> for JValue<'a> {
+ fn from(other: jdouble) -> Self {
+ JValue::Double(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jdouble {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Double(d) => Ok(d),
+ _ => Err(Error::WrongJValueType("double", value.type_name())),
+ }
+ }
+}
+
+// jint
+impl<'a> From<jint> for JValue<'a> {
+ fn from(other: jint) -> Self {
+ JValue::Int(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jint {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Int(i) => Ok(i),
+ _ => Err(Error::WrongJValueType("int", value.type_name())),
+ }
+ }
+}
+
+// jlong
+impl<'a> From<jlong> for JValue<'a> {
+ fn from(other: jlong) -> Self {
+ JValue::Long(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jlong {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Long(l) => Ok(l),
+ _ => Err(Error::WrongJValueType("long", value.type_name())),
+ }
+ }
+}
+
+// jbyte
+impl<'a> From<jbyte> for JValue<'a> {
+ fn from(other: jbyte) -> Self {
+ JValue::Byte(other)
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for jbyte {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Byte(b) => Ok(b),
+ _ => Err(Error::WrongJValueType("byte", value.type_name())),
+ }
+ }
+}
+
+// jvoid
+impl<'a> From<()> for JValue<'a> {
+ fn from(_: ()) -> Self {
+ JValue::Void
+ }
+}
+
+impl<'a> TryFrom<JValue<'a>> for () {
+ type Error = Error;
+
+ fn try_from(value: JValue<'a>) -> Result<Self> {
+ match value {
+ JValue::Void => Ok(()),
+ _ => Err(Error::WrongJValueType("void", value.type_name())),
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/objects/mod.rs b/0.20.0/src/wrapper/objects/mod.rs
new file mode 100644
index 0000000..3209140
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/mod.rs
@@ -0,0 +1,56 @@
+// wrappers arount jni pointer types that add lifetimes and other functionality.
+mod jvalue;
+pub use self::jvalue::*;
+
+mod jmethodid;
+pub use self::jmethodid::*;
+
+mod jstaticmethodid;
+pub use self::jstaticmethodid::*;
+
+mod jfieldid;
+pub use self::jfieldid::*;
+
+mod jstaticfieldid;
+pub use self::jstaticfieldid::*;
+
+mod jobject;
+pub use self::jobject::*;
+
+mod jthrowable;
+pub use self::jthrowable::*;
+
+mod jclass;
+pub use self::jclass::*;
+
+mod jstring;
+pub use self::jstring::*;
+
+mod jmap;
+pub use self::jmap::*;
+
+mod jlist;
+pub use self::jlist::*;
+
+mod jbytebuffer;
+pub use self::jbytebuffer::*;
+
+// For storing a reference to a java object
+mod global_ref;
+pub use self::global_ref::*;
+
+// For automatic local ref deletion
+mod auto_local;
+pub use self::auto_local::*;
+
+mod release_mode;
+pub use self::release_mode::*;
+
+// For automatic pointer-based generic array release
+mod auto_array;
+pub use self::auto_array::*;
+
+// For automatic pointer-based primitive array release
+mod auto_primitive_array;
+
+pub use self::auto_primitive_array::*;
diff --git a/0.20.0/src/wrapper/objects/release_mode.rs b/0.20.0/src/wrapper/objects/release_mode.rs
new file mode 100644
index 0000000..ff81df7
--- /dev/null
+++ b/0.20.0/src/wrapper/objects/release_mode.rs
@@ -0,0 +1,15 @@
+use crate::sys::JNI_ABORT;
+
+/// ReleaseMode
+///
+/// This defines the release mode of AutoArray (and AutoPrimitiveArray) resources, and
+/// related release array functions.
+#[derive(Clone, Copy, Debug)]
+#[repr(i32)]
+pub enum ReleaseMode {
+ /// Copy back the content and free the elems buffer. For read-only access, prefer
+ /// [`NoCopyBack`](ReleaseMode::NoCopyBack).
+ CopyBack = 0,
+ /// Free the buffer without copying back the possible changes.
+ NoCopyBack = JNI_ABORT,
+}
diff --git a/0.20.0/src/wrapper/signature.rs b/0.20.0/src/wrapper/signature.rs
new file mode 100644
index 0000000..46444cc
--- /dev/null
+++ b/0.20.0/src/wrapper/signature.rs
@@ -0,0 +1,274 @@
+use std::{fmt, str::FromStr};
+
+use combine::{
+ between, many, many1, parser, satisfy, token, ParseError, Parser, StdParseResult, Stream,
+};
+
+use crate::errors::*;
+
+/// A primitive java type. These are the things that can be represented without
+/// an object.
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+pub enum Primitive {
+ Boolean, // Z
+ Byte, // B
+ Char, // C
+ Double, // D
+ Float, // F
+ Int, // I
+ Long, // J
+ Short, // S
+ Void, // V
+}
+
+impl fmt::Display for Primitive {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Primitive::Boolean => write!(f, "Z"),
+ Primitive::Byte => write!(f, "B"),
+ Primitive::Char => write!(f, "C"),
+ Primitive::Double => write!(f, "D"),
+ Primitive::Float => write!(f, "F"),
+ Primitive::Int => write!(f, "I"),
+ Primitive::Long => write!(f, "J"),
+ Primitive::Short => write!(f, "S"),
+ Primitive::Void => write!(f, "V"),
+ }
+ }
+}
+
+/// Enum representing any java type in addition to method signatures.
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum JavaType {
+ Primitive(Primitive),
+ Object(String),
+ Array(Box<JavaType>),
+ Method(Box<TypeSignature>),
+}
+
+impl FromStr for JavaType {
+ type Err = Error;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ parser(parse_type)
+ .parse(s)
+ .map(|res| res.0)
+ .map_err(|e| Error::ParseFailed(e, s.to_owned()))
+ }
+}
+
+impl fmt::Display for JavaType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ JavaType::Primitive(ref ty) => ty.fmt(f),
+ JavaType::Object(ref name) => write!(f, "L{};", name),
+ JavaType::Array(ref ty) => write!(f, "[{}", ty),
+ JavaType::Method(ref m) => m.fmt(f),
+ }
+ }
+}
+
+/// Enum representing any java type that may be used as a return value
+///
+/// This type intentionally avoids capturing any heap allocated types (to avoid
+/// allocations while making JNI method calls) and so it doesn't fully qualify
+/// the object or array types with a String like `JavaType::Object` does.
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum ReturnType {
+ Primitive(Primitive),
+ Object,
+ Array,
+}
+
+impl FromStr for ReturnType {
+ type Err = Error;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ parser(parse_return)
+ .parse(s)
+ .map(|res| res.0)
+ .map_err(|e| Error::ParseFailed(e, s.to_owned()))
+ }
+}
+
+impl fmt::Display for ReturnType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ReturnType::Primitive(ref ty) => ty.fmt(f),
+ ReturnType::Object => write!(f, "L;"),
+ ReturnType::Array => write!(f, "["),
+ }
+ }
+}
+
+/// A method type signature. This is the structure representation of something
+/// like `(Ljava/lang/String;)Z`. Used by the `call_(object|static)_method`
+/// functions on jnienv to ensure safety.
+#[allow(missing_docs)]
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub struct TypeSignature {
+ pub args: Vec<JavaType>,
+ pub ret: ReturnType,
+}
+
+impl TypeSignature {
+ /// Parse a signature string into a TypeSignature enum.
+ // Clippy suggests implementing `FromStr` or renaming it which is not possible in our case.
+ #[allow(clippy::should_implement_trait)]
+ pub fn from_str<S: AsRef<str>>(s: S) -> Result<TypeSignature> {
+ Ok(match parser(parse_sig).parse(s.as_ref()).map(|res| res.0) {
+ Ok(JavaType::Method(sig)) => *sig,
+ Err(e) => return Err(Error::ParseFailed(e, s.as_ref().to_owned())),
+ _ => unreachable!(),
+ })
+ }
+}
+
+impl fmt::Display for TypeSignature {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "(")?;
+ for a in &self.args {
+ write!(f, "{}", a)?;
+ }
+ write!(f, ")")?;
+ write!(f, "{}", self.ret)?;
+ Ok(())
+ }
+}
+
+fn parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ let boolean = token('Z').map(|_| Primitive::Boolean);
+ let byte = token('B').map(|_| Primitive::Byte);
+ let char_type = token('C').map(|_| Primitive::Char);
+ let double = token('D').map(|_| Primitive::Double);
+ let float = token('F').map(|_| Primitive::Float);
+ let int = token('I').map(|_| Primitive::Int);
+ let long = token('J').map(|_| Primitive::Long);
+ let short = token('S').map(|_| Primitive::Short);
+ let void = token('V').map(|_| Primitive::Void);
+
+ (boolean
+ .or(byte)
+ .or(char_type)
+ .or(double)
+ .or(float)
+ .or(int)
+ .or(long)
+ .or(short)
+ .or(void))
+ .parse_stream(input)
+ .into()
+}
+
+fn parse_array<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ let marker = token('[');
+ (marker, parser(parse_type))
+ .map(|(_, ty)| JavaType::Array(Box::new(ty)))
+ .parse_stream(input)
+ .into()
+}
+
+fn parse_object<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ let marker = token('L');
+ let end = token(';');
+ let obj = between(marker, end, many1(satisfy(|c| c != ';')));
+
+ obj.map(JavaType::Object).parse_stream(input).into()
+}
+
+fn parse_type<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ parser(parse_primitive)
+ .map(JavaType::Primitive)
+ .or(parser(parse_array))
+ .or(parser(parse_object))
+ .or(parser(parse_sig))
+ .parse_stream(input)
+ .into()
+}
+
+fn parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ parser(parse_primitive)
+ .map(ReturnType::Primitive)
+ .or(parser(parse_array).map(|_| ReturnType::Array))
+ .or(parser(parse_object).map(|_| ReturnType::Object))
+ .parse_stream(input)
+ .into()
+}
+
+fn parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ between(token('('), token(')'), many(parser(parse_type)))
+ .parse_stream(input)
+ .into()
+}
+
+fn parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
+where
+ S::Error: ParseError<char, S::Range, S::Position>,
+{
+ (parser(parse_args), parser(parse_return))
+ .map(|(a, r)| TypeSignature { args: a, ret: r })
+ .map(|sig| JavaType::Method(Box::new(sig)))
+ .parse_stream(input)
+ .into()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_parser() {
+ let inputs = [
+ "(Ljava/lang/String;I)V",
+ "[Lherp;",
+ // fails because the return type does not contain the class name: "(IBVZ)L;"
+ // "(IBVZ)Ljava/lang/String;",
+ ];
+
+ for each in inputs.iter() {
+ let res = JavaType::from_str(*each).unwrap();
+ println!("{:#?}", res);
+ let s = format!("{}", res);
+ assert_eq!(s, *each);
+ let res2 = JavaType::from_str(*each).unwrap();
+ println!("{:#?}", res2);
+ assert_eq!(res2, res);
+ }
+ }
+
+ #[test]
+ fn test_parser_invalid_signature() {
+ let signature = "()Ljava/lang/List"; // no semicolon
+ let res = JavaType::from_str(signature);
+
+ match res {
+ Ok(any) => {
+ panic!("Unexpected result: {}", any);
+ }
+ Err(err) => {
+ assert!(err.to_string().contains("input: ()Ljava/lang/List"));
+ }
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/strings/ffi_str.rs b/0.20.0/src/wrapper/strings/ffi_str.rs
new file mode 100644
index 0000000..7af5e48
--- /dev/null
+++ b/0.20.0/src/wrapper/strings/ffi_str.rs
@@ -0,0 +1,109 @@
+use std::{
+ borrow::{Borrow, Cow, ToOwned},
+ ffi,
+ os::raw::c_char,
+};
+
+use cesu8::{from_java_cesu8, to_java_cesu8};
+use log::debug;
+
+use crate::wrapper::strings::ffi_str;
+
+/// Wrapper for `std::ffi::CString` that also takes care of encoding between
+/// UTF-8 and Java's Modified UTF-8. As with `CString`, this implements `Deref`
+/// to `&JNIStr`.
+pub struct JNIString {
+ internal: ffi::CString,
+}
+
+/// Wrapper for `std::ffi::CStr` that also takes care of encoding between
+/// UTF-8 and Java's Modified UTF-8.
+pub struct JNIStr {
+ internal: ffi::CStr,
+}
+
+impl ::std::ops::Deref for JNIString {
+ type Target = JNIStr;
+
+ fn deref(&self) -> &Self::Target {
+ unsafe { &*(self.internal.as_bytes_with_nul() as *const [u8] as *const ffi_str::JNIStr) }
+ }
+}
+
+impl ::std::ops::Deref for JNIStr {
+ type Target = ffi::CStr;
+
+ fn deref(&self) -> &Self::Target {
+ &self.internal
+ }
+}
+
+impl<T> From<T> for JNIString
+where
+ T: AsRef<str>,
+{
+ fn from(other: T) -> Self {
+ let enc = to_java_cesu8(other.as_ref()).into_owned();
+ JNIString {
+ internal: unsafe { ffi::CString::from_vec_unchecked(enc) },
+ }
+ }
+}
+
+impl<'a> From<&'a JNIStr> for Cow<'a, str> {
+ fn from(other: &'a JNIStr) -> Cow<'a, str> {
+ let bytes = other.to_bytes();
+ match from_java_cesu8(bytes) {
+ Ok(s) => s,
+ Err(e) => {
+ debug!("error decoding java cesu8: {:#?}", e);
+ String::from_utf8_lossy(bytes)
+ }
+ }
+ }
+}
+
+impl From<JNIString> for String {
+ fn from(other: JNIString) -> String {
+ Cow::from(other.borrowed()).into_owned()
+ }
+}
+
+impl JNIString {
+ /// Get the borrowed version of the JNIString. Equivalent to
+ /// `CString::borrowed`.
+ pub fn borrowed(&self) -> &JNIStr {
+ self
+ }
+}
+
+impl JNIStr {
+ /// Construct a reference to a `JNIStr` from a pointer. Equivalent to `CStr::from_ptr`.
+ ///
+ /// # Safety
+ ///
+ /// Expects a valid pointer to a null-terminated C string and does not perform any lifetime
+ /// checks for the resulting value.
+ pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a JNIStr {
+ &*(ffi::CStr::from_ptr(ptr) as *const ffi::CStr as *const ffi_str::JNIStr)
+ }
+}
+
+// impls for CoW
+impl Borrow<JNIStr> for JNIString {
+ fn borrow(&self) -> &JNIStr {
+ self
+ }
+}
+
+impl ToOwned for JNIStr {
+ type Owned = JNIString;
+
+ fn to_owned(&self) -> JNIString {
+ unsafe {
+ JNIString {
+ internal: ffi::CString::from_vec_unchecked(self.to_bytes().to_vec()),
+ }
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/strings/java_str.rs b/0.20.0/src/wrapper/strings/java_str.rs
new file mode 100644
index 0000000..f964df2
--- /dev/null
+++ b/0.20.0/src/wrapper/strings/java_str.rs
@@ -0,0 +1,71 @@
+use std::{borrow::Cow, os::raw::c_char};
+
+use log::warn;
+
+use crate::{errors::*, objects::JString, strings::JNIStr, JNIEnv};
+
+/// Reference to a string in the JVM. Holds a pointer to the array
+/// returned by GetStringUTFChars. Calls ReleaseStringUTFChars on Drop.
+/// Can be converted to a `&JNIStr` with the same cost as the `&CStr.from_ptr`
+/// conversion.
+pub struct JavaStr<'a: 'b, 'b> {
+ internal: *const c_char,
+ obj: JString<'a>,
+ env: &'b JNIEnv<'a>,
+}
+
+impl<'a: 'b, 'b> JavaStr<'a, 'b> {
+ /// Build a `JavaStr` from an object and a reference to the environment. You
+ /// probably want to use `JNIEnv::get_string` instead.
+ pub fn from_env(env: &'b JNIEnv<'a>, obj: JString<'a>) -> Result<Self> {
+ let ptr = env.get_string_utf_chars(obj)?;
+ let java_str = JavaStr {
+ internal: ptr,
+ env,
+ obj,
+ };
+ Ok(java_str)
+ }
+
+ /// Extract the raw C string pointer from the JavaStr. This will be
+ /// encoded using the JVM internal `CESU-8`-style.
+ pub fn get_raw(&self) -> *const c_char {
+ self.internal
+ }
+}
+
+impl<'a: 'b, 'b> ::std::ops::Deref for JavaStr<'a, 'b> {
+ type Target = JNIStr;
+ fn deref(&self) -> &Self::Target {
+ self.into()
+ }
+}
+
+impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for &'c JNIStr {
+ fn from(other: &'c JavaStr) -> &'c JNIStr {
+ unsafe { JNIStr::from_ptr(other.internal) }
+ }
+}
+
+impl<'a: 'b, 'b: 'c, 'c> From<&'c JavaStr<'a, 'b>> for Cow<'c, str> {
+ fn from(other: &'c JavaStr) -> Cow<'c, str> {
+ let jni_str: &JNIStr = other;
+ jni_str.into()
+ }
+}
+
+impl<'a: 'b, 'b> From<JavaStr<'a, 'b>> for String {
+ fn from(other: JavaStr) -> String {
+ let cow: Cow<str> = (&other).into();
+ cow.into_owned()
+ }
+}
+
+impl<'a: 'b, 'b> Drop for JavaStr<'a, 'b> {
+ fn drop(&mut self) {
+ match unsafe { self.env.release_string_utf_chars(self.obj, self.internal) } {
+ Ok(()) => {}
+ Err(e) => warn!("error dropping java str: {}", e),
+ }
+ }
+}
diff --git a/0.20.0/src/wrapper/strings/mod.rs b/0.20.0/src/wrapper/strings/mod.rs
new file mode 100644
index 0000000..2e03363
--- /dev/null
+++ b/0.20.0/src/wrapper/strings/mod.rs
@@ -0,0 +1,6 @@
+// String types for sending to/from the jvm
+mod ffi_str;
+pub use self::ffi_str::*;
+
+mod java_str;
+pub use self::java_str::*;
diff --git a/0.20.0/src/wrapper/version.rs b/0.20.0/src/wrapper/version.rs
new file mode 100644
index 0000000..4629ea8
--- /dev/null
+++ b/0.20.0/src/wrapper/version.rs
@@ -0,0 +1,43 @@
+use crate::sys::{
+ JNI_VERSION_1_1, JNI_VERSION_1_2, JNI_VERSION_1_4, JNI_VERSION_1_6, JNI_VERSION_1_8,
+};
+
+/// JNI Version
+///
+/// This maps to the `jni_sys::JNI_VERSION_1_*` constants.
+#[derive(Debug, Copy, Clone)]
+#[allow(missing_docs)]
+pub enum JNIVersion {
+ V1,
+ V2,
+ V4,
+ V6,
+ V8,
+ Invalid(i32),
+}
+
+impl From<i32> for JNIVersion {
+ fn from(other: i32) -> Self {
+ match other {
+ JNI_VERSION_1_1 => JNIVersion::V1,
+ JNI_VERSION_1_2 => JNIVersion::V2,
+ JNI_VERSION_1_4 => JNIVersion::V4,
+ JNI_VERSION_1_6 => JNIVersion::V6,
+ JNI_VERSION_1_8 => JNIVersion::V8,
+ v => JNIVersion::Invalid(v),
+ }
+ }
+}
+
+impl From<JNIVersion> for i32 {
+ fn from(other: JNIVersion) -> Self {
+ match other {
+ JNIVersion::V1 => JNI_VERSION_1_1,
+ JNIVersion::V2 => JNI_VERSION_1_2,
+ JNIVersion::V4 => JNI_VERSION_1_4,
+ JNIVersion::V6 => JNI_VERSION_1_6,
+ JNIVersion::V8 => JNI_VERSION_1_8,
+ JNIVersion::Invalid(v) => v,
+ }
+ }
+}
diff --git a/0.20.0/test_profile b/0.20.0/test_profile
new file mode 100644
index 0000000..61dc8c8
--- /dev/null
+++ b/0.20.0/test_profile
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# Sets the LD_LIBRARY_PATH required for running ITs dependent on libjvm.
+# See CONTRIBUTING.md for details.
+
+JAVA_HOME="${JAVA_HOME:-$(java -XshowSettings:properties -version \
+ 2>&1 > /dev/null |\
+ grep 'java.home' |\
+ awk '{print $3}')}"
+
+if [[ "$(uname -s)" == "Darwin" ]]; then
+ LIB_NAME="libjli"
+else
+ LIB_NAME="libjvm"
+fi
+
+# As JDK 8 and 9+ use different relative paths for libjvm, find the library:
+LIBJVM_PATH="$(find "${JAVA_HOME}" -type f -name "${LIB_NAME}.*" -print0 -quit | xargs -0 -n1 dirname)"
+
+export LD_LIBRARY_PATH="${LIBJVM_PATH}"
+
+# on macOS, cargo use DYLD_FALLBACK_LIBRARY_PATH to locate dynamic libraries.
+# See https://doc.rust-lang.org/cargo/reference/environment-variables.html#dynamic-library-paths
+export DYLD_FALLBACK_LIBRARY_PATH="${LIBJVM_PATH}":$DYLD_FALLBACK_LIBRARY_PATH
diff --git a/0.20.0/tests/executor.rs b/0.20.0/tests/executor.rs
new file mode 100644
index 0000000..fbd02d4
--- /dev/null
+++ b/0.20.0/tests/executor.rs
@@ -0,0 +1,75 @@
+#![cfg(feature = "invocation")]
+
+use std::{
+ sync::{Arc, Barrier},
+ thread::spawn,
+};
+
+use jni::{sys::jint, Executor};
+
+mod util;
+use util::{jvm, AtomicIntegerProxy};
+
+#[test]
+fn single_thread() {
+ let executor = Executor::new(jvm().clone());
+ test_single_thread(executor);
+}
+
+#[test]
+fn serialized_threads() {
+ let executor = Executor::new(jvm().clone());
+ test_serialized_threads(executor);
+}
+
+#[test]
+fn concurrent_threads() {
+ let executor = Executor::new(jvm().clone());
+ const THREAD_NUM: usize = 8;
+ test_concurrent_threads(executor, THREAD_NUM)
+}
+
+fn test_single_thread(executor: Executor) {
+ let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
+ assert_eq!(0, atomic.get().unwrap());
+ assert_eq!(1, atomic.increment_and_get().unwrap());
+ assert_eq!(3, atomic.add_and_get(2).unwrap());
+ assert_eq!(3, atomic.get().unwrap());
+}
+
+fn test_serialized_threads(executor: Executor) {
+ let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
+ assert_eq!(0, atomic.get().unwrap());
+ let jh = spawn(move || {
+ assert_eq!(1, atomic.increment_and_get().unwrap());
+ assert_eq!(3, atomic.add_and_get(2).unwrap());
+ atomic
+ });
+ let mut atomic = jh.join().unwrap();
+ assert_eq!(3, atomic.get().unwrap());
+}
+
+fn test_concurrent_threads(executor: Executor, thread_num: usize) {
+ const ITERS_PER_THREAD: usize = 10_000;
+
+ let mut atomic = AtomicIntegerProxy::new(executor, 0).unwrap();
+ let barrier = Arc::new(Barrier::new(thread_num));
+ let mut threads = Vec::new();
+
+ for _ in 0..thread_num {
+ let barrier = Arc::clone(&barrier);
+ let mut atomic = atomic.clone();
+ let jh = spawn(move || {
+ barrier.wait();
+ for _ in 0..ITERS_PER_THREAD {
+ atomic.increment_and_get().unwrap();
+ }
+ });
+ threads.push(jh);
+ }
+ for jh in threads {
+ jh.join().unwrap();
+ }
+ let expected = (ITERS_PER_THREAD * thread_num) as jint;
+ assert_eq!(expected, atomic.get().unwrap());
+}
diff --git a/0.20.0/tests/executor_nested_attach.rs b/0.20.0/tests/executor_nested_attach.rs
new file mode 100644
index 0000000..b0ef6ae
--- /dev/null
+++ b/0.20.0/tests/executor_nested_attach.rs
@@ -0,0 +1,62 @@
+#![cfg(feature = "invocation")]
+
+use std::{sync::Arc, thread::spawn};
+
+use jni::{
+ errors::{Error, JniError},
+ Executor, JavaVM,
+};
+
+mod util;
+use util::jvm;
+
+/// Checks if nested attaches are working properly and threads detach themselves
+/// on exit.
+#[test]
+fn nested_attach() {
+ let executor = Executor::new(jvm().clone());
+
+ assert_eq!(jvm().threads_attached(), 0);
+ let thread = spawn(|| {
+ assert_eq!(jvm().threads_attached(), 0);
+ check_nested_attach(jvm(), executor);
+ assert_eq!(jvm().threads_attached(), 1);
+ });
+ thread.join().unwrap();
+ assert_eq!(jvm().threads_attached(), 0);
+}
+
+/// Checks if nested `with_attached` calls does not detach the thread before the outer-most
+/// call is finished.
+fn check_nested_attach(vm: &Arc<JavaVM>, executor: Executor) {
+ check_detached(vm);
+ executor
+ .with_attached(|_| {
+ check_attached(vm);
+ executor.with_attached(|_| {
+ check_attached(vm);
+ Ok(())
+ })?;
+ check_attached(vm);
+ Ok(())
+ })
+ .unwrap();
+}
+
+fn check_attached(vm: &JavaVM) {
+ assert!(is_attached(vm));
+}
+
+fn check_detached(vm: &JavaVM) {
+ assert!(!is_attached(vm));
+}
+
+fn is_attached(vm: &JavaVM) -> bool {
+ vm.get_env()
+ .map(|_| true)
+ .or_else(|jni_err| match jni_err {
+ Error::JniCall(JniError::ThreadDetached) => Ok(false),
+ _ => Err(jni_err),
+ })
+ .expect("An unexpected JNI error occurred")
+}
diff --git a/0.20.0/tests/java_integers.rs b/0.20.0/tests/java_integers.rs
new file mode 100644
index 0000000..0d7c6fb
--- /dev/null
+++ b/0.20.0/tests/java_integers.rs
@@ -0,0 +1,41 @@
+#![cfg(feature = "invocation")]
+
+use jni::{objects::JObject, objects::JValue};
+
+mod util;
+use util::{attach_current_thread, print_exception};
+
+#[test]
+fn test_java_integers() {
+ let env = attach_current_thread();
+
+ let array_length = 50;
+
+ for value in -10..10 {
+ env.with_local_frame(16, || {
+ let integer_value =
+ env.new_object("java/lang/Integer", "(I)V", &[JValue::Int(value)])?;
+
+ let values_array =
+ env.new_object_array(array_length, "java/lang/Integer", integer_value)?;
+ let values_array = unsafe { JObject::from_raw(values_array) };
+
+ let result = env
+ .call_static_method(
+ "java/util/Arrays",
+ "binarySearch",
+ "([Ljava/lang/Object;Ljava/lang/Object;)I",
+ &[JValue::Object(values_array), JValue::Object(integer_value)],
+ )?
+ .i()?;
+
+ assert!(0 <= result && result < array_length);
+
+ Ok(JObject::null())
+ })
+ .unwrap_or_else(|e| {
+ print_exception(&env);
+ panic!("{:#?}", e);
+ });
+ }
+}
diff --git a/0.20.0/tests/jmap.rs b/0.20.0/tests/jmap.rs
new file mode 100644
index 0000000..f19005c
--- /dev/null
+++ b/0.20.0/tests/jmap.rs
@@ -0,0 +1,41 @@
+#![cfg(feature = "invocation")]
+
+use jni::objects::{JMap, JObject};
+
+mod util;
+use util::{attach_current_thread, unwrap};
+
+#[test]
+pub fn jmap_push_and_iterate() {
+ let env = attach_current_thread();
+ let data = &["hello", "world", "from", "test"];
+
+ // Create a new map. Use LinkedHashMap to have predictable iteration order
+ let map_object = unwrap(&env, env.new_object("java/util/LinkedHashMap", "()V", &[]));
+ let map = unwrap(&env, JMap::from_env(&env, map_object));
+
+ // Push all strings
+ unwrap(
+ &env,
+ data.iter().try_for_each(|s| {
+ env.new_string(s)
+ .map(JObject::from)
+ .and_then(|s| map.put(s, s).map(|_| ()))
+ }),
+ );
+
+ // Collect the keys using the JMap iterator
+ let mut collected = Vec::new();
+ unwrap(
+ &env,
+ map.iter().and_then(|mut iter| {
+ iter.try_for_each(|e| {
+ env.get_string(e.0.into())
+ .map(|s| collected.push(String::from(s)))
+ })
+ }),
+ );
+
+ let orig = data.to_vec();
+ assert_eq!(orig, collected);
+}
diff --git a/0.20.0/tests/jni_api.rs b/0.20.0/tests/jni_api.rs
new file mode 100644
index 0000000..050f8ec
--- /dev/null
+++ b/0.20.0/tests/jni_api.rs
@@ -0,0 +1,958 @@
+#![cfg(feature = "invocation")]
+
+use std::str::FromStr;
+
+use jni::{
+ descriptors::Desc,
+ errors::Error,
+ objects::{
+ AutoArray, AutoLocal, JByteBuffer, JList, JObject, JString, JThrowable, JValue, ReleaseMode,
+ },
+ signature::JavaType,
+ strings::JNIString,
+ sys::{jboolean, jbyte, jchar, jdouble, jfloat, jint, jlong, jobject, jshort, jsize},
+ JNIEnv,
+};
+
+mod util;
+use util::{attach_current_thread, unwrap};
+
+static ARRAYLIST_CLASS: &str = "java/util/ArrayList";
+static EXCEPTION_CLASS: &str = "java/lang/Exception";
+static ARITHMETIC_EXCEPTION_CLASS: &str = "java/lang/ArithmeticException";
+static RUNTIME_EXCEPTION_CLASS: &str = "java/lang/RuntimeException";
+static INTEGER_CLASS: &str = "java/lang/Integer";
+static MATH_CLASS: &str = "java/lang/Math";
+static STRING_CLASS: &str = "java/lang/String";
+static MATH_ABS_METHOD_NAME: &str = "abs";
+static MATH_TO_INT_METHOD_NAME: &str = "toIntExact";
+static MATH_ABS_SIGNATURE: &str = "(I)I";
+static MATH_TO_INT_SIGNATURE: &str = "(J)I";
+static TEST_EXCEPTION_MESSAGE: &str = "Default exception thrown";
+static TESTING_OBJECT_STR: &str = "TESTING OBJECT";
+
+#[test]
+pub fn call_method_returning_null() {
+ let env = attach_current_thread();
+ // Create an Exception with no message
+ let obj = AutoLocal::new(
+ &env,
+ unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])),
+ );
+ // Call Throwable#getMessage must return null
+ let message = unwrap(
+ &env,
+ env.call_method(&obj, "getMessage", "()Ljava/lang/String;", &[]),
+ );
+ let message_ref = env.auto_local(unwrap(&env, message.l()));
+
+ assert!(message_ref.as_obj().is_null());
+}
+
+#[test]
+pub fn is_instance_of_same_class() {
+ let env = attach_current_thread();
+ let obj = AutoLocal::new(
+ &env,
+ unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])),
+ );
+ assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS)));
+}
+
+#[test]
+pub fn is_instance_of_superclass() {
+ let env = attach_current_thread();
+ let obj = AutoLocal::new(
+ &env,
+ unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])),
+ );
+ assert!(unwrap(&env, env.is_instance_of(&obj, EXCEPTION_CLASS)));
+}
+
+#[test]
+pub fn is_instance_of_subclass() {
+ let env = attach_current_thread();
+ let obj = AutoLocal::new(
+ &env,
+ unwrap(&env, env.new_object(EXCEPTION_CLASS, "()V", &[])),
+ );
+ assert!(!unwrap(
+ &env,
+ env.is_instance_of(&obj, ARITHMETIC_EXCEPTION_CLASS)
+ ));
+}
+
+#[test]
+pub fn is_instance_of_not_superclass() {
+ let env = attach_current_thread();
+ let obj = AutoLocal::new(
+ &env,
+ unwrap(&env, env.new_object(ARITHMETIC_EXCEPTION_CLASS, "()V", &[])),
+ );
+ assert!(!unwrap(&env, env.is_instance_of(&obj, ARRAYLIST_CLASS)));
+}
+
+#[test]
+pub fn is_instance_of_null() {
+ let env = attach_current_thread();
+ let obj = JObject::null();
+ assert!(unwrap(&env, env.is_instance_of(obj, ARRAYLIST_CLASS)));
+ assert!(unwrap(&env, env.is_instance_of(obj, EXCEPTION_CLASS)));
+ assert!(unwrap(
+ &env,
+ env.is_instance_of(obj, ARITHMETIC_EXCEPTION_CLASS)
+ ));
+}
+
+#[test]
+pub fn is_same_object_diff_references() {
+ let env = attach_current_thread();
+ let string = env.new_string(TESTING_OBJECT_STR).unwrap();
+ let ref_from_string = unwrap(&env, env.new_local_ref::<JObject>(string.into()));
+ assert!(unwrap(&env, env.is_same_object(string, ref_from_string)));
+ unwrap(&env, env.delete_local_ref(ref_from_string));
+}
+
+#[test]
+pub fn is_same_object_same_reference() {
+ let env = attach_current_thread();
+ let string = env.new_string(TESTING_OBJECT_STR).unwrap();
+ assert!(unwrap(&env, env.is_same_object(string, string)));
+}
+
+#[test]
+pub fn is_not_same_object() {
+ let env = attach_current_thread();
+ let string = env.new_string(TESTING_OBJECT_STR).unwrap();
+ let same_src_str = env.new_string(TESTING_OBJECT_STR).unwrap();
+ assert!(!unwrap(&env, env.is_same_object(string, same_src_str)));
+}
+
+#[test]
+pub fn is_not_same_object_null() {
+ let env = attach_current_thread();
+ assert!(unwrap(
+ &env,
+ env.is_same_object(JObject::null(), JObject::null())
+ ));
+}
+
+#[test]
+pub fn get_static_public_field() {
+ let env = attach_current_thread();
+
+ let min_int_value = env
+ .get_static_field(INTEGER_CLASS, "MIN_VALUE", "I")
+ .unwrap()
+ .i()
+ .unwrap();
+
+ assert_eq!(min_int_value, i32::min_value());
+}
+
+#[test]
+pub fn get_static_public_field_by_id() {
+ let env = attach_current_thread();
+
+ // One can't pass a JavaType::Primitive(Primitive::Int) to
+ // `get_static_field_id` unfortunately: #137
+ let field_type = "I";
+ let field_id = env
+ .get_static_field_id(INTEGER_CLASS, "MIN_VALUE", field_type)
+ .unwrap();
+
+ let field_type = JavaType::from_str(field_type).unwrap();
+ let min_int_value = env
+ .get_static_field_unchecked(INTEGER_CLASS, field_id, field_type)
+ .unwrap()
+ .i()
+ .unwrap();
+
+ assert_eq!(min_int_value, i32::min_value());
+}
+
+#[test]
+pub fn pop_local_frame_pending_exception() {
+ let env = attach_current_thread();
+
+ env.push_local_frame(16).unwrap();
+
+ env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception")
+ .unwrap();
+
+ // Pop the local frame with a pending exception
+ env.pop_local_frame(JObject::null())
+ .expect("JNIEnv#pop_local_frame must work in case of pending exception");
+
+ env.exception_clear().unwrap();
+}
+
+#[test]
+pub fn push_local_frame_pending_exception() {
+ let env = attach_current_thread();
+
+ env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception")
+ .unwrap();
+
+ // Push a new local frame with a pending exception
+ env.push_local_frame(16)
+ .expect("JNIEnv#push_local_frame must work in case of pending exception");
+
+ env.exception_clear().unwrap();
+
+ env.pop_local_frame(JObject::null()).unwrap();
+}
+
+#[test]
+pub fn push_local_frame_too_many_refs() {
+ let env = attach_current_thread();
+
+ // Try to push a new local frame with a ridiculous size
+ let frame_size = i32::max_value();
+ env.push_local_frame(frame_size)
+ .expect_err("push_local_frame(2B) must Err");
+
+ env.pop_local_frame(JObject::null()).unwrap();
+}
+
+#[test]
+pub fn with_local_frame() {
+ let env = attach_current_thread();
+
+ let s = env
+ .with_local_frame(16, || {
+ let res = env.new_string("Test").unwrap();
+ Ok(res.into())
+ })
+ .unwrap();
+
+ let s = env
+ .get_string(s.into())
+ .expect("The object returned from the local frame must remain valid");
+ assert_eq!(s.to_str().unwrap(), "Test");
+}
+
+#[test]
+pub fn with_local_frame_pending_exception() {
+ let env = attach_current_thread();
+
+ env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception")
+ .unwrap();
+
+ // Try to allocate a frame of locals
+ env.with_local_frame(16, || Ok(JObject::null()))
+ .expect("JNIEnv#with_local_frame must work in case of pending exception");
+
+ env.exception_clear().unwrap();
+}
+
+#[test]
+pub fn call_static_method_ok() {
+ let env = attach_current_thread();
+
+ let x = JValue::from(-10);
+ let val: jint = env
+ .call_static_method(MATH_CLASS, MATH_ABS_METHOD_NAME, MATH_ABS_SIGNATURE, &[x])
+ .expect("JNIEnv#call_static_method_unsafe should return JValue")
+ .i()
+ .unwrap();
+
+ assert_eq!(val, 10);
+}
+
+#[test]
+pub fn call_static_method_throws() {
+ let env = attach_current_thread();
+
+ let x = JValue::Long(4_000_000_000);
+ let is_java_exception = env
+ .call_static_method(
+ MATH_CLASS,
+ MATH_TO_INT_METHOD_NAME,
+ MATH_TO_INT_SIGNATURE,
+ &[x],
+ )
+ .map_err(|error| matches!(error, Error::JavaException))
+ .expect_err("JNIEnv#call_static_method_unsafe should return error");
+
+ assert!(
+ is_java_exception,
+ "ErrorKind::JavaException expected as error"
+ );
+ assert_pending_java_exception(&env);
+}
+
+#[test]
+pub fn call_static_method_wrong_arg() {
+ let env = attach_current_thread();
+
+ let x = JValue::Double(4.567_891_23);
+ env.call_static_method(
+ MATH_CLASS,
+ MATH_TO_INT_METHOD_NAME,
+ MATH_TO_INT_SIGNATURE,
+ &[x],
+ )
+ .expect_err("JNIEnv#call_static_method_unsafe should return error");
+
+ assert_pending_java_exception(&env);
+}
+
+#[test]
+pub fn java_byte_array_from_slice() {
+ let env = attach_current_thread();
+ let buf: &[u8] = &[1, 2, 3];
+ let java_array = env
+ .byte_array_from_slice(buf)
+ .expect("JNIEnv#byte_array_from_slice must create a java array from slice");
+ let obj = AutoLocal::new(&env, unsafe { JObject::from_raw(java_array) });
+
+ assert!(!obj.as_obj().is_null());
+ let mut res: [i8; 3] = [0; 3];
+ env.get_byte_array_region(java_array, 0, &mut res).unwrap();
+ assert_eq!(res[0], 1);
+ assert_eq!(res[1], 2);
+ assert_eq!(res[2], 3);
+}
+
+macro_rules! test_get_array_elements {
+ ( $jni_get:tt, $jni_type:ty, $new_array:tt, $get_array:tt, $set_array:tt ) => {
+ #[test]
+ pub fn $jni_get() {
+ let env = attach_current_thread();
+
+ // Create original Java array
+ let buf: &[$jni_type] = &[0 as $jni_type, 1 as $jni_type];
+ let java_array = env
+ .$new_array(2)
+ .expect(stringify!(JNIEnv#$new_array must create a Java $jni_type array with given size));
+
+ // Insert array elements
+ let _ = env.$set_array(java_array, 0, buf);
+
+ // Use a scope to test Drop
+ {
+ // Get byte array elements auto wrapper
+ let auto_ptr: AutoArray<$jni_type> = {
+ // Make sure the lifetime is tied to the environment,
+ // not the particular JNIEnv reference
+ let temporary_env: JNIEnv = *env;
+ temporary_env.$jni_get(java_array, ReleaseMode::CopyBack).unwrap()
+ };
+
+ // Check array size
+ assert_eq!(auto_ptr.size().unwrap(), 2);
+
+ // Check pointer access
+ let ptr = auto_ptr.as_ptr();
+ assert_eq!(unsafe { *ptr.offset(0) } as i32, 0);
+ assert_eq!(unsafe { *ptr.offset(1) } as i32, 1);
+
+ // Check pointer From access
+ let ptr: *mut $jni_type = std::convert::From::from(&auto_ptr);
+ assert_eq!(unsafe { *ptr.offset(0) } as i32, 0);
+ assert_eq!(unsafe { *ptr.offset(1) } as i32, 1);
+
+ // Check pointer into() access
+ let ptr: *mut $jni_type = (&auto_ptr).into();
+ assert_eq!(unsafe { *ptr.offset(0) } as i32, 0);
+ assert_eq!(unsafe { *ptr.offset(1) } as i32, 1);
+
+ // Modify
+ unsafe {
+ *ptr.offset(0) += 1 as $jni_type;
+ *ptr.offset(1) -= 1 as $jni_type;
+ }
+
+ // Commit would be necessary here, if there were no closure
+ //auto_ptr.commit().unwrap();
+ }
+
+ // Confirm modification of original Java array
+ let mut res: [$jni_type; 2] = [0 as $jni_type; 2];
+ env.$get_array(java_array, 0, &mut res).unwrap();
+ assert_eq!(res[0] as i32, 1);
+ assert_eq!(res[1] as i32, 0);
+ }
+ };
+}
+
+// Test generic get_array_elements
+test_get_array_elements!(
+ get_array_elements,
+ jint,
+ new_int_array,
+ get_int_array_region,
+ set_int_array_region
+);
+
+// Test type-specific array accessors
+test_get_array_elements!(
+ get_int_array_elements,
+ jint,
+ new_int_array,
+ get_int_array_region,
+ set_int_array_region
+);
+
+test_get_array_elements!(
+ get_long_array_elements,
+ jlong,
+ new_long_array,
+ get_long_array_region,
+ set_long_array_region
+);
+
+test_get_array_elements!(
+ get_byte_array_elements,
+ jbyte,
+ new_byte_array,
+ get_byte_array_region,
+ set_byte_array_region
+);
+
+test_get_array_elements!(
+ get_boolean_array_elements,
+ jboolean,
+ new_boolean_array,
+ get_boolean_array_region,
+ set_boolean_array_region
+);
+
+test_get_array_elements!(
+ get_char_array_elements,
+ jchar,
+ new_char_array,
+ get_char_array_region,
+ set_char_array_region
+);
+
+test_get_array_elements!(
+ get_short_array_elements,
+ jshort,
+ new_short_array,
+ get_short_array_region,
+ set_short_array_region
+);
+
+test_get_array_elements!(
+ get_float_array_elements,
+ jfloat,
+ new_float_array,
+ get_float_array_region,
+ set_float_array_region
+);
+
+test_get_array_elements!(
+ get_double_array_elements,
+ jdouble,
+ new_double_array,
+ get_double_array_region,
+ set_double_array_region
+);
+
+#[test]
+#[ignore] // Disabled until issue #283 is resolved
+pub fn get_long_array_elements_commit() {
+ let env = attach_current_thread();
+
+ // Create original Java array
+ let buf: &[i64] = &[1, 2, 3];
+ let java_array = env
+ .new_long_array(3)
+ .expect("JNIEnv#new_long_array must create a java array with given size");
+
+ // Insert array elements
+ let _ = env.set_long_array_region(java_array, 0, buf);
+
+ // Get long array elements auto wrapper
+ let auto_ptr = env
+ .get_long_array_elements(java_array, ReleaseMode::CopyBack)
+ .unwrap();
+
+ // Copying the array depends on the VM vendor/version/GC combinations.
+ // If the wrapped array is not being copied, we can skip the test.
+ if !auto_ptr.is_copy() {
+ return;
+ }
+
+ // Check pointer access
+ let ptr = auto_ptr.as_ptr();
+
+ // Modify
+ unsafe {
+ *ptr.offset(0) += 1;
+ *ptr.offset(1) += 1;
+ *ptr.offset(2) += 1;
+ }
+
+ // Check that original Java array is unmodified
+ let mut res: [i64; 3] = [0; 3];
+ env.get_long_array_region(java_array, 0, &mut res).unwrap();
+ assert_eq!(res[0], 1);
+ assert_eq!(res[1], 2);
+ assert_eq!(res[2], 3);
+
+ auto_ptr.commit().unwrap();
+
+ // Confirm modification of original Java array
+ env.get_long_array_region(java_array, 0, &mut res).unwrap();
+ assert_eq!(res[0], 2);
+ assert_eq!(res[1], 3);
+ assert_eq!(res[2], 4);
+}
+
+#[test]
+pub fn get_primitive_array_critical() {
+ let env = attach_current_thread();
+
+ // Create original Java array
+ let buf: &[u8] = &[1, 2, 3];
+ let java_array = env
+ .byte_array_from_slice(buf)
+ .expect("JNIEnv#byte_array_from_slice must create a java array from slice");
+
+ // Use a scope to test Drop
+ {
+ // Get primitive array elements auto wrapper
+ let auto_ptr = env
+ .get_primitive_array_critical(java_array, ReleaseMode::CopyBack)
+ .unwrap();
+
+ // Check array size
+ assert_eq!(auto_ptr.size().unwrap(), 3);
+
+ // Get pointer
+ let ptr = auto_ptr.as_ptr();
+
+ // Convert void pointer to an unsigned byte array, without copy
+ let mut vec;
+ unsafe { vec = Vec::from_raw_parts(ptr as *mut u8, 3, 3) }
+
+ // Check
+ assert_eq!(vec[0], 1);
+ assert_eq!(vec[1], 2);
+ assert_eq!(vec[2], 3);
+
+ // Modify
+ vec[0] += 1;
+ vec[1] += 1;
+ vec[2] += 1;
+
+ // Release
+ // Make sure vec's destructor doesn't free the data it thinks it owns when it goes out
+ // of scope (avoid double free)
+ std::mem::forget(vec);
+ }
+
+ // Confirm modification of original Java array
+ let mut res: [i8; 3] = [0; 3];
+ env.get_byte_array_region(java_array, 0, &mut res).unwrap();
+ assert_eq!(res[0], 2);
+ assert_eq!(res[1], 3);
+ assert_eq!(res[2], 4);
+}
+
+#[test]
+pub fn get_object_class() {
+ let env = attach_current_thread();
+ let string = env.new_string("test").unwrap();
+ let result = env.get_object_class(string);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+}
+
+#[test]
+pub fn get_object_class_null_arg() {
+ let env = attach_current_thread();
+ let null_obj = JObject::null();
+ let result = env
+ .get_object_class(null_obj)
+ .map_err(|error| matches!(error, Error::NullPtr(_)))
+ .expect_err("JNIEnv#get_object_class should return error for null argument");
+ assert!(result, "ErrorKind::NullPtr expected as error");
+}
+
+#[test]
+pub fn new_direct_byte_buffer() {
+ let env = attach_current_thread();
+ let vec: Vec<u8> = vec![0, 1, 2, 3];
+ let (addr, len) = {
+ // (would use buf.into_raw_parts() on nightly)
+ let buf = vec.leak();
+ (buf.as_mut_ptr(), buf.len())
+ };
+ let result = unsafe { env.new_direct_byte_buffer(addr, len) };
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+}
+
+#[test]
+pub fn new_direct_byte_buffer_invalid_addr() {
+ let env = attach_current_thread();
+ let result = unsafe { env.new_direct_byte_buffer(std::ptr::null_mut(), 5) };
+ assert!(result.is_err());
+}
+
+#[test]
+pub fn get_direct_buffer_capacity_ok() {
+ let env = attach_current_thread();
+ let vec: Vec<u8> = vec![0, 1, 2, 3];
+ let (addr, len) = {
+ // (would use buf.into_raw_parts() on nightly)
+ let buf = vec.leak();
+ (buf.as_mut_ptr(), buf.len())
+ };
+ let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap();
+ assert!(!result.is_null());
+
+ let capacity = env.get_direct_buffer_capacity(result).unwrap();
+ assert_eq!(capacity, 4);
+}
+
+#[test]
+pub fn get_direct_buffer_capacity_wrong_arg() {
+ let env = attach_current_thread();
+ let wrong_obj = unsafe { JByteBuffer::from_raw(env.new_string("wrong").unwrap().into_raw()) };
+ let capacity = env.get_direct_buffer_capacity(wrong_obj);
+ assert!(capacity.is_err());
+}
+
+#[test]
+pub fn get_direct_buffer_capacity_null_arg() {
+ let env = attach_current_thread();
+ let result = env.get_direct_buffer_capacity(JObject::null().into());
+ assert!(result.is_err());
+}
+
+#[test]
+pub fn get_direct_buffer_address_ok() {
+ let env = attach_current_thread();
+ let vec: Vec<u8> = vec![0, 1, 2, 3];
+ let (addr, len) = {
+ // (would use buf.into_raw_parts() on nightly)
+ let buf = vec.leak();
+ (buf.as_mut_ptr(), buf.len())
+ };
+ let result = unsafe { env.new_direct_byte_buffer(addr, len) }.unwrap();
+ assert!(!result.is_null());
+
+ let dest_buffer = env.get_direct_buffer_address(result).unwrap();
+ assert_eq!(addr, dest_buffer);
+}
+
+#[test]
+pub fn get_direct_buffer_address_wrong_arg() {
+ let env = attach_current_thread();
+ let wrong_obj: JObject = env.new_string("wrong").unwrap().into();
+ let result = env.get_direct_buffer_address(wrong_obj.into());
+ assert!(result.is_err());
+}
+
+#[test]
+pub fn get_direct_buffer_address_null_arg() {
+ let env = attach_current_thread();
+ let result = env.get_direct_buffer_address(JObject::null().into());
+ assert!(result.is_err());
+}
+
+// Group test for testing the family of new_PRIMITIVE_array functions with correct arguments
+#[test]
+pub fn new_primitive_array_ok() {
+ let env = attach_current_thread();
+ const SIZE: jsize = 16;
+
+ let result = env.new_boolean_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_byte_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_char_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_short_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_int_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_long_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_float_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+
+ let result = env.new_double_array(SIZE);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+}
+
+// Group test for testing the family of new_PRIMITIVE_array functions with wrong arguments
+#[test]
+pub fn new_primitive_array_wrong() {
+ let env = attach_current_thread();
+ const WRONG_SIZE: jsize = -1;
+
+ let result = env.new_boolean_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_boolean_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_byte_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_byte_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_char_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_char_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_short_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_short_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_int_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_int_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_long_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_long_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_float_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_float_array should throw exception");
+ assert_pending_java_exception(&env);
+
+ let result = env.new_double_array(WRONG_SIZE);
+ assert_exception(&result, "JNIEnv#new_double_array should throw exception");
+ assert_pending_java_exception(&env);
+}
+
+#[test]
+fn get_super_class_ok() {
+ let env = attach_current_thread();
+ let result = env.get_superclass(ARRAYLIST_CLASS);
+ assert!(result.is_ok());
+ assert!(!result.unwrap().is_null());
+}
+
+#[test]
+fn get_super_class_null() {
+ let env = attach_current_thread();
+ let result = env.get_superclass("java/lang/Object");
+ assert!(result.is_ok());
+ assert!(result.unwrap().is_null());
+}
+
+#[test]
+fn convert_byte_array() {
+ let env = attach_current_thread();
+ let src: Vec<u8> = vec![1, 2, 3, 4];
+ let java_byte_array = env.byte_array_from_slice(&src).unwrap();
+
+ let dest = env.convert_byte_array(java_byte_array);
+ assert!(dest.is_ok());
+ assert_eq!(dest.unwrap(), src);
+}
+
+#[test]
+fn local_ref_null() {
+ let env = attach_current_thread();
+ let null_obj = JObject::null();
+
+ let result = env.new_local_ref::<JObject>(null_obj);
+ assert!(result.is_ok());
+ assert!(result.unwrap().is_null());
+
+ // try to delete null reference
+ let result = env.delete_local_ref(null_obj);
+ assert!(result.is_ok());
+}
+
+#[test]
+fn new_global_ref_null() {
+ let env = attach_current_thread();
+ let null_obj = JObject::null();
+ let result = env.new_global_ref(null_obj);
+ assert!(result.is_ok());
+ assert!(result.unwrap().as_obj().is_null());
+}
+
+#[test]
+fn auto_local_null() {
+ let env = attach_current_thread();
+ let null_obj = JObject::null();
+ {
+ let auto_ref = AutoLocal::new(&env, null_obj);
+ assert!(auto_ref.as_obj().is_null());
+ }
+ assert!(null_obj.is_null());
+}
+
+#[test]
+fn short_lifetime_with_local_frame() {
+ let env = attach_current_thread();
+ let object = short_lifetime_with_local_frame_sub_fn(&env);
+ assert!(object.is_ok());
+}
+
+fn short_lifetime_with_local_frame_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result<JObject<'a>, Error> {
+ env.with_local_frame(16, || {
+ env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(5)])
+ })
+}
+
+#[test]
+fn short_lifetime_list() {
+ let env = attach_current_thread();
+ let first_list_object = short_lifetime_list_sub_fn(&env).unwrap();
+ let value = env.call_method(first_list_object, "intValue", "()I", &[]);
+ assert_eq!(value.unwrap().i().unwrap(), 1);
+}
+
+fn short_lifetime_list_sub_fn<'a>(env: &'_ JNIEnv<'a>) -> Result<JObject<'a>, Error> {
+ let list_object = env.new_object(ARRAYLIST_CLASS, "()V", &[])?;
+ let list = JList::from_env(env, list_object)?;
+ let element = env.new_object(INTEGER_CLASS, "(I)V", &[JValue::from(1)])?;
+ list.add(element)?;
+ short_lifetime_list_sub_fn_get_first_element(&list)
+}
+
+fn short_lifetime_list_sub_fn_get_first_element<'a>(
+ list: &'_ JList<'a, '_>,
+) -> Result<JObject<'a>, Error> {
+ let mut iterator = list.iter()?;
+ Ok(iterator.next().unwrap())
+}
+
+#[test]
+fn get_object_array_element() {
+ let env = attach_current_thread();
+ let array = env
+ .new_object_array(1, STRING_CLASS, JObject::null())
+ .unwrap();
+ assert!(!array.is_null());
+ assert!(env.get_object_array_element(array, 0).unwrap().is_null());
+ let test_str = env.new_string("test").unwrap();
+ env.set_object_array_element(array, 0, test_str).unwrap();
+ assert!(!env.get_object_array_element(array, 0).unwrap().is_null());
+}
+
+#[test]
+pub fn throw_new() {
+ let env = attach_current_thread();
+
+ let result = env.throw_new(RUNTIME_EXCEPTION_CLASS, "Test Exception");
+ assert!(result.is_ok());
+ assert_pending_java_exception_detailed(
+ &env,
+ Some(RUNTIME_EXCEPTION_CLASS),
+ Some("Test Exception"),
+ );
+}
+
+#[test]
+pub fn throw_new_fail() {
+ let env = attach_current_thread();
+
+ let result = env.throw_new("java/lang/NonexistentException", "Test Exception");
+ assert!(result.is_err());
+ // Just to clear the java.lang.NoClassDefFoundError
+ assert_pending_java_exception(&env);
+}
+
+#[test]
+pub fn throw_defaults() {
+ let env = attach_current_thread();
+
+ test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE);
+ test_throwable_descriptor_with_default_type(&env, TEST_EXCEPTION_MESSAGE.to_owned());
+ test_throwable_descriptor_with_default_type(&env, JNIString::from(TEST_EXCEPTION_MESSAGE));
+}
+
+#[test]
+pub fn test_conversion() {
+ let env = attach_current_thread();
+ let orig_obj: JObject = env.new_string("Hello, world!").unwrap().into();
+
+ let string = JString::from(orig_obj);
+ let actual = JObject::from(string);
+ assert!(unwrap(&env, env.is_same_object(orig_obj, actual)));
+
+ let global_ref = env.new_global_ref(orig_obj).unwrap();
+ let actual = JObject::from(&global_ref);
+ assert!(unwrap(&env, env.is_same_object(orig_obj, actual)));
+
+ let auto_local = env.auto_local(orig_obj);
+ let actual = JObject::from(&auto_local);
+ assert!(unwrap(&env, env.is_same_object(orig_obj, actual)));
+}
+
+fn test_throwable_descriptor_with_default_type<'a, D>(env: &JNIEnv<'a>, descriptor: D)
+where
+ D: Desc<'a, JThrowable<'a>>,
+{
+ let result = descriptor.lookup(env);
+ assert!(result.is_ok());
+ let exception = result.unwrap();
+
+ assert_exception_type(env, exception, RUNTIME_EXCEPTION_CLASS);
+ assert_exception_message(env, exception, TEST_EXCEPTION_MESSAGE);
+}
+
+// Helper method that asserts that result is Error and the cause is JavaException.
+fn assert_exception(res: &Result<jobject, Error>, expect_message: &str) {
+ assert!(res.is_err());
+ assert!(res
+ .as_ref()
+ .map_err(|error| matches!(error, Error::JavaException))
+ .expect_err(expect_message));
+}
+
+// Shortcut to `assert_pending_java_exception_detailed()` without checking for expected type and
+// message of exception.
+fn assert_pending_java_exception(env: &JNIEnv) {
+ assert_pending_java_exception_detailed(env, None, None)
+}
+
+// Helper method that asserts there is a pending Java exception of `expected_type` with
+// `expected_message` and clears it if any.
+fn assert_pending_java_exception_detailed(
+ env: &JNIEnv,
+ expected_type: Option<&str>,
+ expected_message: Option<&str>,
+) {
+ assert!(env.exception_check().unwrap());
+ let exception = env.exception_occurred().expect("Unable to get exception");
+ env.exception_clear().unwrap();
+
+ if let Some(expected_type) = expected_type {
+ assert_exception_type(env, exception, expected_type);
+ }
+
+ if let Some(expected_message) = expected_message {
+ assert_exception_message(env, exception, expected_message);
+ }
+}
+
+// Asserts that exception is of `expected_type` type.
+fn assert_exception_type(env: &JNIEnv, exception: JThrowable, expected_type: &str) {
+ assert!(env.is_instance_of(exception, expected_type).unwrap());
+}
+
+// Asserts that exception's message is `expected_message`.
+fn assert_exception_message(env: &JNIEnv, exception: JThrowable, expected_message: &str) {
+ let message = env
+ .call_method(exception, "getMessage", "()Ljava/lang/String;", &[])
+ .unwrap()
+ .l()
+ .unwrap();
+ let msg_rust: String = env.get_string(message.into()).unwrap().into();
+ assert_eq!(msg_rust, expected_message);
+}
diff --git a/0.20.0/tests/jni_global_refs.rs b/0.20.0/tests/jni_global_refs.rs
new file mode 100644
index 0000000..2adc628
--- /dev/null
+++ b/0.20.0/tests/jni_global_refs.rs
@@ -0,0 +1,80 @@
+#![cfg(feature = "invocation")]
+
+use std::{
+ sync::{Arc, Barrier},
+ thread::spawn,
+};
+
+use jni::{
+ objects::{AutoLocal, JValue},
+ sys::jint,
+};
+
+mod util;
+use util::{attach_current_thread, unwrap};
+
+#[test]
+pub fn global_ref_works_in_other_threads() {
+ const ITERS_PER_THREAD: usize = 10_000;
+
+ let env = attach_current_thread();
+ let mut join_handlers = Vec::new();
+
+ let atomic_integer = {
+ let local_ref = AutoLocal::new(
+ &env,
+ unwrap(
+ &env,
+ env.new_object(
+ "java/util/concurrent/atomic/AtomicInteger",
+ "(I)V",
+ &[JValue::from(0)],
+ ),
+ ),
+ );
+ unwrap(&env, env.new_global_ref(&local_ref))
+ };
+
+ // Test with a different number of threads (from 2 to 8)
+ for thread_num in 2..9 {
+ let barrier = Arc::new(Barrier::new(thread_num));
+
+ for _ in 0..thread_num {
+ let barrier = barrier.clone();
+ let atomic_integer = atomic_integer.clone();
+
+ let jh = spawn(move || {
+ let env = attach_current_thread();
+ barrier.wait();
+ for _ in 0..ITERS_PER_THREAD {
+ unwrap(
+ &env,
+ unwrap(
+ &env,
+ env.call_method(&atomic_integer, "incrementAndGet", "()I", &[]),
+ )
+ .i(),
+ );
+ }
+ });
+ join_handlers.push(jh);
+ }
+
+ for jh in join_handlers.drain(..) {
+ jh.join().unwrap();
+ }
+
+ let expected = (ITERS_PER_THREAD * thread_num) as jint;
+ assert_eq!(
+ expected,
+ unwrap(
+ &env,
+ unwrap(
+ &env,
+ env.call_method(&atomic_integer, "getAndSet", "(I)I", &[JValue::from(0)])
+ )
+ .i()
+ )
+ );
+ }
+}
diff --git a/0.20.0/tests/threads_attach_guard.rs b/0.20.0/tests/threads_attach_guard.rs
new file mode 100644
index 0000000..d43f807
--- /dev/null
+++ b/0.20.0/tests/threads_attach_guard.rs
@@ -0,0 +1,18 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{attach_current_thread, call_java_abs, jvm};
+
+#[test]
+fn thread_attach_guard_detaches_on_drop() {
+ assert_eq!(jvm().threads_attached(), 0);
+ {
+ let guard = attach_current_thread();
+ assert_eq!(jvm().threads_attached(), 1);
+ let val = call_java_abs(&guard, -1);
+ assert_eq!(val, 1);
+ }
+ assert_eq!(jvm().threads_attached(), 0);
+ // Verify that this thread is really detached.
+ assert!(jvm().get_env().is_err());
+}
diff --git a/0.20.0/tests/threads_detach.rs b/0.20.0/tests/threads_detach.rs
new file mode 100644
index 0000000..922bca7
--- /dev/null
+++ b/0.20.0/tests/threads_detach.rs
@@ -0,0 +1,19 @@
+#![cfg(feature = "invocation")]
+
+use std::thread::spawn;
+
+mod util;
+use util::{attach_current_thread_permanently, call_java_abs, jvm};
+
+#[test]
+fn thread_detaches_when_finished() {
+ let thread = spawn(|| {
+ let env = attach_current_thread_permanently();
+ let val = call_java_abs(&env, -2);
+ assert_eq!(val, 2);
+ assert_eq!(jvm().threads_attached(), 1);
+ });
+
+ thread.join().unwrap();
+ assert_eq!(jvm().threads_attached(), 0);
+}
diff --git a/0.20.0/tests/threads_detach_daemon.rs b/0.20.0/tests/threads_detach_daemon.rs
new file mode 100644
index 0000000..9531a35
--- /dev/null
+++ b/0.20.0/tests/threads_detach_daemon.rs
@@ -0,0 +1,19 @@
+#![cfg(feature = "invocation")]
+
+use std::thread::spawn;
+
+mod util;
+use util::{attach_current_thread_as_daemon, call_java_abs, jvm};
+
+#[test]
+fn daemon_thread_detaches_when_finished() {
+ let thread = spawn(|| {
+ let env = attach_current_thread_as_daemon();
+ let val = call_java_abs(&env, -3);
+ assert_eq!(val, 3);
+ assert_eq!(jvm().threads_attached(), 1);
+ });
+
+ thread.join().unwrap();
+ assert_eq!(jvm().threads_attached(), 0);
+}
diff --git a/0.20.0/tests/threads_explicit_detach.rs b/0.20.0/tests/threads_explicit_detach.rs
new file mode 100644
index 0000000..3010ca3
--- /dev/null
+++ b/0.20.0/tests/threads_explicit_detach.rs
@@ -0,0 +1,17 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{attach_current_thread, call_java_abs, detach_current_thread, jvm};
+
+#[test]
+pub fn explicit_detach_detaches_thread_attached_locally() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let guard = attach_current_thread();
+ let val = call_java_abs(&guard, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ detach_current_thread();
+ assert_eq!(jvm().threads_attached(), 0);
+ assert!(jvm().get_env().is_err());
+}
diff --git a/0.20.0/tests/threads_explicit_detach_daemon.rs b/0.20.0/tests/threads_explicit_detach_daemon.rs
new file mode 100644
index 0000000..76d4f86
--- /dev/null
+++ b/0.20.0/tests/threads_explicit_detach_daemon.rs
@@ -0,0 +1,17 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{attach_current_thread_as_daemon, call_java_abs, detach_current_thread, jvm};
+
+#[test]
+pub fn explicit_detach_detaches_thread_attached_as_daemon() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let guard = attach_current_thread_as_daemon();
+ let val = call_java_abs(&guard, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ detach_current_thread();
+ assert_eq!(jvm().threads_attached(), 0);
+ assert!(jvm().get_env().is_err());
+}
diff --git a/0.20.0/tests/threads_explicit_detach_permanent.rs b/0.20.0/tests/threads_explicit_detach_permanent.rs
new file mode 100644
index 0000000..a58937f
--- /dev/null
+++ b/0.20.0/tests/threads_explicit_detach_permanent.rs
@@ -0,0 +1,17 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{attach_current_thread_permanently, call_java_abs, detach_current_thread, jvm};
+
+#[test]
+pub fn explicit_detach_detaches_thread_attached_permanently() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let guard = attach_current_thread_permanently();
+ let val = call_java_abs(&guard, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ detach_current_thread();
+ assert_eq!(jvm().threads_attached(), 0);
+ assert!(jvm().get_env().is_err());
+}
diff --git a/0.20.0/tests/threads_nested_attach_daemon.rs b/0.20.0/tests/threads_nested_attach_daemon.rs
new file mode 100644
index 0000000..2515f61
--- /dev/null
+++ b/0.20.0/tests/threads_nested_attach_daemon.rs
@@ -0,0 +1,48 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{
+ attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
+ call_java_abs, jvm,
+};
+
+#[test]
+pub fn nested_attaches_should_not_detach_daemon_thread() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let env = attach_current_thread_as_daemon();
+ let val = call_java_abs(&env, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Create nested AttachGuard.
+ {
+ let env_nested = attach_current_thread();
+ let val = call_java_abs(&env_nested, -2);
+ assert_eq!(val, 2);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+
+ // Call a Java method after nested guard has been dropped to check that
+ // this thread has not been detached.
+ let val = call_java_abs(&env, -3);
+ assert_eq!(val, 3);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_permanently is a no-op.
+ {
+ let env_nested = attach_current_thread_permanently();
+ let val = call_java_abs(&env_nested, -4);
+ assert_eq!(val, 4);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_as_daemon is a no-op.
+ {
+ let env_nested = attach_current_thread_as_daemon();
+ let val = call_java_abs(&env_nested, -5);
+ assert_eq!(val, 5);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+}
diff --git a/0.20.0/tests/threads_nested_attach_guard.rs b/0.20.0/tests/threads_nested_attach_guard.rs
new file mode 100644
index 0000000..c9d4aca
--- /dev/null
+++ b/0.20.0/tests/threads_nested_attach_guard.rs
@@ -0,0 +1,54 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{
+ attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
+ call_java_abs, jvm,
+};
+
+#[test]
+pub fn nested_attaches_should_not_detach_guarded_thread() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let env = attach_current_thread();
+ let val = call_java_abs(&env, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Create nested AttachGuard.
+ {
+ let env_nested = attach_current_thread();
+ let val = call_java_abs(&env_nested, -2);
+ assert_eq!(val, 2);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+
+ // Call a Java method after nested guard has been dropped to check that
+ // this thread has not been detached.
+ let val = call_java_abs(&env, -3);
+ assert_eq!(val, 3);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_permanently is a no-op.
+ {
+ let env_nested = attach_current_thread_permanently();
+ let val = call_java_abs(&env_nested, -4);
+ assert_eq!(val, 4);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_as_daemon is a no-op.
+ {
+ let env_nested = attach_current_thread_as_daemon();
+ let val = call_java_abs(&env_nested, -5);
+ assert_eq!(val, 5);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Check that after guard is dropped the thread is properly detached
+ // despite nested "permanent" attaches.
+ drop(env);
+ assert_eq!(jvm().threads_attached(), 0);
+ assert!(jvm().get_env().is_err());
+}
diff --git a/0.20.0/tests/threads_nested_attach_permanently.rs b/0.20.0/tests/threads_nested_attach_permanently.rs
new file mode 100644
index 0000000..42ad9ec
--- /dev/null
+++ b/0.20.0/tests/threads_nested_attach_permanently.rs
@@ -0,0 +1,49 @@
+#![cfg(feature = "invocation")]
+
+mod util;
+use util::{
+ attach_current_thread, attach_current_thread_as_daemon, attach_current_thread_permanently,
+ call_java_abs, jvm,
+};
+
+#[test]
+pub fn nested_attaches_should_not_detach_permanent_thread() {
+ assert_eq!(jvm().threads_attached(), 0);
+ let env = attach_current_thread_permanently();
+ let val = call_java_abs(&env, -1);
+ assert_eq!(val, 1);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Create nested AttachGuard.
+ {
+ let env_nested = attach_current_thread();
+ let val = call_java_abs(&env_nested, -2);
+ assert_eq!(val, 2);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+
+ // Call a Java method after nested guard has been dropped to check that
+ // this thread has not been detached.
+ let val = call_java_abs(&env, -3);
+ assert_eq!(val, 3);
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_permanently is a no-op.
+ {
+ let env_nested = attach_current_thread_permanently();
+ let val = call_java_abs(&env_nested, -4);
+ assert_eq!(val, 4);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+
+ // Nested attach_as_daemon is a no-op.
+ {
+ let env_nested = attach_current_thread_as_daemon();
+ let val = call_java_abs(&env_nested, -5);
+ assert_eq!(val, 5);
+ assert_eq!(jvm().threads_attached(), 1);
+ }
+ assert_eq!(jvm().threads_attached(), 1);
+ assert!(jvm().get_env().is_ok());
+}
diff --git a/0.20.0/tests/util/example_proxy.rs b/0.20.0/tests/util/example_proxy.rs
new file mode 100644
index 0000000..05f7d6a
--- /dev/null
+++ b/0.20.0/tests/util/example_proxy.rs
@@ -0,0 +1,52 @@
+#![allow(dead_code)]
+
+use jni::{
+ errors::*,
+ objects::{GlobalRef, JValue},
+ sys::jint,
+ Executor, JNIEnv,
+};
+
+/// A test example of a native-to-JNI proxy
+#[derive(Clone)]
+pub struct AtomicIntegerProxy {
+ exec: Executor,
+ obj: GlobalRef,
+}
+
+impl AtomicIntegerProxy {
+ /// Creates a new instance of `AtomicIntegerProxy`
+ pub fn new(exec: Executor, init_value: jint) -> Result<Self> {
+ let obj = exec.with_attached(|env: &JNIEnv| {
+ env.new_global_ref(env.new_object(
+ "java/util/concurrent/atomic/AtomicInteger",
+ "(I)V",
+ &[JValue::from(init_value)],
+ )?)
+ })?;
+ Ok(AtomicIntegerProxy { exec, obj })
+ }
+
+ /// Gets a current value from java object
+ pub fn get(&mut self) -> Result<jint> {
+ self.exec
+ .with_attached(|env| env.call_method(&self.obj, "get", "()I", &[])?.i())
+ }
+
+ /// Increments a value of java object and then gets it
+ pub fn increment_and_get(&mut self) -> Result<jint> {
+ self.exec.with_attached(|env| {
+ env.call_method(&self.obj, "incrementAndGet", "()I", &[])?
+ .i()
+ })
+ }
+
+ /// Adds some value to the value of java object and then gets a resulting value
+ pub fn add_and_get(&mut self, delta: jint) -> Result<jint> {
+ let delta = JValue::from(delta);
+ self.exec.with_attached(|env| {
+ env.call_method(&self.obj, "addAndGet", "(I)I", &[delta])?
+ .i()
+ })
+ }
+}
diff --git a/0.20.0/tests/util/mod.rs b/0.20.0/tests/util/mod.rs
new file mode 100644
index 0000000..fb07386
--- /dev/null
+++ b/0.20.0/tests/util/mod.rs
@@ -0,0 +1,85 @@
+use std::sync::{Arc, Once};
+
+use jni::{
+ errors::Result, objects::JValue, sys::jint, AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion,
+ JavaVM,
+};
+
+mod example_proxy;
+pub use self::example_proxy::AtomicIntegerProxy;
+
+pub fn jvm() -> &'static Arc<JavaVM> {
+ static mut JVM: Option<Arc<JavaVM>> = None;
+ static INIT: Once = Once::new();
+
+ INIT.call_once(|| {
+ let jvm_args = InitArgsBuilder::new()
+ .version(JNIVersion::V8)
+ .option("-Xcheck:jni")
+ .build()
+ .unwrap_or_else(|e| panic!("{:#?}", e));
+
+ let jvm = JavaVM::new(jvm_args).unwrap_or_else(|e| panic!("{:#?}", e));
+
+ unsafe {
+ JVM = Some(Arc::new(jvm));
+ }
+ });
+
+ unsafe { JVM.as_ref().unwrap() }
+}
+
+#[allow(dead_code)]
+pub fn call_java_abs(env: &JNIEnv, value: i32) -> i32 {
+ env.call_static_method(
+ "java/lang/Math",
+ "abs",
+ "(I)I",
+ &[JValue::from(value as jint)],
+ )
+ .unwrap()
+ .i()
+ .unwrap()
+}
+
+#[allow(dead_code)]
+pub fn attach_current_thread() -> AttachGuard<'static> {
+ jvm()
+ .attach_current_thread()
+ .expect("failed to attach jvm thread")
+}
+
+#[allow(dead_code)]
+pub fn attach_current_thread_as_daemon() -> JNIEnv<'static> {
+ jvm()
+ .attach_current_thread_as_daemon()
+ .expect("failed to attach jvm daemon thread")
+}
+
+#[allow(dead_code)]
+pub fn attach_current_thread_permanently() -> JNIEnv<'static> {
+ jvm()
+ .attach_current_thread_permanently()
+ .expect("failed to attach jvm thread permanently")
+}
+
+#[allow(dead_code)]
+pub fn detach_current_thread() {
+ jvm().detach_current_thread()
+}
+
+pub fn print_exception(env: &JNIEnv) {
+ let exception_occurred = env.exception_check().unwrap_or_else(|e| panic!("{:?}", e));
+ if exception_occurred {
+ env.exception_describe()
+ .unwrap_or_else(|e| panic!("{:?}", e));
+ }
+}
+
+#[allow(dead_code)]
+pub fn unwrap<T>(env: &JNIEnv, res: Result<T>) -> T {
+ res.unwrap_or_else(|e| {
+ print_exception(env);
+ panic!("{:#?}", e);
+ })
+}