├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── java ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ └── java │ └── io │ └── github │ └── gedgygedgy │ └── rust │ ├── future │ ├── Future.java │ ├── FutureException.java │ └── SimpleFuture.java │ ├── ops │ ├── FnAdapter.java │ ├── FnBiFunction.java │ ├── FnBiFunctionImpl.java │ ├── FnFunction.java │ ├── FnFunctionImpl.java │ ├── FnRunnable.java │ └── FnRunnableImpl.java │ ├── panic │ └── PanicException.java │ ├── stream │ ├── QueueStream.java │ ├── Stream.java │ └── StreamPoll.java │ ├── task │ ├── PollResult.java │ └── Waker.java │ └── thread │ ├── LocalThreadChecker.java │ └── LocalThreadException.java └── rust ├── arrays.rs ├── classcache.rs ├── exceptions.rs ├── future.rs ├── lib.rs ├── ops.rs ├── stream.rs ├── task.rs └── uuid.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.1 (2023-07-04) 2 | 3 | ## Bugfixes 4 | 5 | - Fix issue with package not loading to Maven 6 | - Fix NoSuchElementException not being handled correctly 7 | 8 | # v0.1.0 (2022-07-30) 9 | 10 | - Initial release -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jni-utils" 3 | version = "0.1.1" 4 | authors = ["Gedgy Gedgy ", "Kyle Machulis "] 5 | edition = "2021" 6 | license = "BSD-3-Clause" 7 | description = "Extra Utilities for JNI in Rust" 8 | readme = "README.md" 9 | repository = "https://github.com/deviceplug/jni-utils-rs" 10 | homepage = "https://github.com/deviceplug/jni-utils-rs" 11 | 12 | 13 | [lib] 14 | path = "rust/lib.rs" 15 | 16 | [features] 17 | build-java-support = [] 18 | 19 | [dependencies] 20 | # This needs to stay at 0.19.0 until we either update for the new jni-rs or move off this library 21 | # entirely. 22 | jni = "0.19.0" 23 | static_assertions = "1.1.0" 24 | uuid = "1.4.0" 25 | futures = "0.3.28" 26 | dashmap = "5.4.0" 27 | once_cell = "1.18.0" 28 | log = "0.4.19" 29 | 30 | [dev-dependencies] 31 | lazy_static = "1.4.0" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-2022 Gedgy Gedgy, Kyle Machulis 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extra Utilities for JNI in Rust 2 | 3 | This crate builds on top of the [`jni`](https://github.com/jni-rs/jni-rs) crate 4 | and provides higher-level concepts to more easily deal with JNI. While the 5 | `jni` crate implements low-level bindings to JNI, `jni-utils` is more focused 6 | on higher-level constructs that get used frequently. Some of the features 7 | provided by `jni-utils` include: 8 | 9 | * Asynchronous calls to Java code using the `JFuture` and `JStream` types 10 | * Conversion between various commonly-used Rust types and their corresponding 11 | Java types 12 | * Emulation of `try`/`catch` blocks with the `try_block` function 13 | 14 | The overriding principle of `jni-utils` is that switches between Rust and Java 15 | code should be minimized, and that it is easier to call Java code from Rust 16 | than it is to call Rust code from Java. Calling Rust from Java requires 17 | creating a class with a `native` method and exporting it from Rust, either by a 18 | combination of `#[nomangle]` and `extern "C"` to export the function as a 19 | symbol in a shared library, or by calling `JNIEnv::register_native_methods()`. 20 | In contrast, calling Java from Rust only requires calling 21 | `JNIEnv::call_method()` (though you can cache the method ID and use 22 | `JNIEnv::call_method_unchecked()` for a performance improvement.) 23 | 24 | To that end, `jni-utils` seeks to minimize the number of holes that must be 25 | poked through the Rust-Java boundary, and the number of `native` 26 | exported-to-Java Rust functions that must be written. In particular, the async 27 | API has been developed to minimize such exports by allowing Java code to wake 28 | an `await` without creating a new `native` function. 29 | 30 | Some features of `jni-utils` require the accompanying Java support library, 31 | which includes some native methods. Therefore, `jni_utils::init()` should be 32 | called before using `jni-utils`. 33 | 34 | ## Library History and Notes 35 | 36 | While I (qDot/Kyle Machulis) am now maintaining this library, the original author of most of this was gedgygedgy. The original repo is at https://github.com/gedgygedgy/jni-utils-rs. The author disappeared in August 2021, and I've not been able to make contact with them. That said, the work they did on btleplug and other projects was mostly finished, and the license was permissive enough to redistribute, so I'm taking over maintainership to try to get it out to the world. 37 | 38 | I'm by no means a JNI expert (though thanks to updating this for Tokio support, I now know way more than I did before. Or ever wanted to.), so while I'm happy to try and take PRs and fix bugs, I can't promise too much. 39 | 40 | ## Building 41 | 42 | The crate and the Java support library can be built together or separately. 43 | 44 | ### Simple way 45 | 46 | The crate includes a feature to automatically build the Java support library: 47 | 48 | ```console 49 | $ cargo build --features=build-java-support 50 | ``` 51 | 52 | The Java support library JAR will be placed in `target//java/libs`. 53 | 54 | ### Advanced way 55 | 56 | The crate and the Java support library can be built separately: 57 | 58 | ```console 59 | $ cargo build 60 | $ cd java 61 | $ ./gradlew build 62 | ``` 63 | 64 | ## Using 65 | 66 | Your Rust crate will need to link against the `jni-utils` crate, and your Java 67 | program will need an `implementation` dependency on the Java support library. 68 | Add this to your `build.gradle`: 69 | 70 | ```gradle 71 | dependencies { 72 | implementation 'io.github.gedgygedgy.rust:jni-utils:0.1.0' 73 | } 74 | ``` 75 | 76 | ## Using with Tokio 77 | 78 | This library should work with the Tokio async runtime without changes. However, when adding any new 79 | features to this crate, they will need to be included in the class cache, as creating new threads in 80 | tokio seemed to have an issue with class caches not being updated. 81 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "build-java-support")] 2 | fn build_java() { 3 | use std::{env, path::PathBuf, process::Command}; 4 | 5 | let mut java_src_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); 6 | java_src_dir.push("java"); 7 | 8 | let mut java_src_gradlew = java_src_dir.clone(); 9 | java_src_gradlew.push( 10 | #[cfg(target_os = "windows")] 11 | "gradlew.bat", 12 | #[cfg(not(target_os = "windows"))] 13 | "gradlew", 14 | ); 15 | 16 | let mut java_build_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 17 | java_build_dir.pop(); 18 | java_build_dir.pop(); 19 | java_build_dir.pop(); 20 | java_build_dir.push("java"); 21 | 22 | let result = Command::new(java_src_gradlew) 23 | .args(&[ 24 | format!("-PbuildDir={}", java_build_dir.to_str().unwrap()), 25 | "-p".to_string(), 26 | java_src_dir.to_str().unwrap().to_string(), 27 | "build".to_string(), 28 | ]) 29 | .spawn() 30 | .unwrap() 31 | .wait() 32 | .unwrap(); 33 | if !result.success() { 34 | panic!("Gradle failed"); 35 | } 36 | } 37 | 38 | fn main() { 39 | #[cfg(feature = "build-java-support")] 40 | build_java(); 41 | } 42 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | -------------------------------------------------------------------------------- /java/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'maven-publish' 4 | } 5 | 6 | java { 7 | withJavadocJar() 8 | withSourcesJar() 9 | } 10 | 11 | publishing { 12 | publications { 13 | mavenJava(MavenPublication) { 14 | from components.java 15 | pom { 16 | name = 'Extra Utilities for JNI in Rust' 17 | description = 'This is the Java support library for jni-utils.' 18 | url = 'https://github.com/gedgygedgy/jni-utils-rs' 19 | licenses { 20 | license { 21 | name = '3-Clause BSD License' 22 | url = 'https://opensource.org/licenses/BSD-3-Clause' 23 | } 24 | } 25 | developers { 26 | developer { 27 | id = 'gedgygedgy' 28 | name = 'Gedgy Gedgy' 29 | email = 'gedgygedgy@protonmail.com' 30 | organization = 'Gedgy Gedgy' 31 | organizationUrl = 'https://github.com/gedgygedgy' 32 | } 33 | } 34 | scm { 35 | connection = 'scm:git:git://github.com/gedgygedgy/jni-utils-rs.git' 36 | developerConnection = 'scm:git:ssh://github.com:gedgygedgy/jni-utils-rs.git' 37 | url = 'https://github.com/gedgygedgy/jni-utils-rs' 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | group = 'io.github.gedgygedgy.rust' 45 | version = '0.1.1-SNAPSHOT' 46 | 47 | sourceCompatibility = 1.8 48 | -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deviceplug/jni-utils-rs/1737fd69e8d980ec045df6daafef250f2dd07ebd/java/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /java/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /java/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /java/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jni-utils' 2 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/future/Future.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.future; 2 | 3 | import io.github.gedgygedgy.rust.task.PollResult; 4 | import io.github.gedgygedgy.rust.task.Waker; 5 | 6 | /** 7 | * Interface for allowing Rust code to interact with Java code in an 8 | * asynchronous manner. The intention of this interface is for asynchronous 9 | * Rust code to directly call Java code that returns a {@link Future}, and 10 | * then poll the {@link Future} using a {@code jni_utils::future::JFuture}. 11 | * In this way, the complexities of interacting with asynchronous Java code can 12 | * be abstracted into a simple {@code jni_utils::future::JFuture} that Rust 13 | * code can {@code await} on. 14 | *

15 | * In general, you will probably want to use {@link SimpleFuture} for most use 16 | * cases. 17 | */ 18 | public interface Future { 19 | /** 20 | * Polls the {@link Future} for a result. You generally won't need to call 21 | * this directly, as {@code jni_utils::future::JFuture} takes care of this 22 | * for you. 23 | * 24 | * @param waker Waker to wake with when the {@link Future} has a result. 25 | * @return The future's result, or {@code null} if no result is available 26 | * yet. 27 | */ 28 | PollResult poll(Waker waker); 29 | } 30 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/future/FutureException.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.future; 2 | 3 | /** 4 | * Exception class for {@link Future} implementations to throw from 5 | * {@link io.github.gedgygedgy.rust.task.PollResult#get} if the result of the 6 | * future is an exception. Implementations should set the real exception as the 7 | * cause of this exception. 8 | */ 9 | public class FutureException extends RuntimeException { 10 | public FutureException(Throwable cause) { 11 | super(cause); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/future/SimpleFuture.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.future; 2 | 3 | import io.github.gedgygedgy.rust.task.PollResult; 4 | import io.github.gedgygedgy.rust.task.Waker; 5 | 6 | /** 7 | * Simple implementation of {@link Future} which can be woken with a result. 8 | * In general, methods which create a {@link SimpleFuture} should return it as 9 | * a {@link Future} to keep calling code from waking it. 10 | */ 11 | public class SimpleFuture implements Future { 12 | private Waker waker = null; 13 | private PollResult result; 14 | private final Object lock = new Object(); 15 | 16 | /** 17 | * Creates a new {@link SimpleFuture} object. 18 | */ 19 | public SimpleFuture() {} 20 | 21 | @Override 22 | public PollResult poll(Waker waker) { 23 | PollResult result = null; 24 | Waker oldWaker = null; 25 | synchronized (this.lock) { 26 | if (this.result != null) { 27 | result = this.result; 28 | } else { 29 | oldWaker = this.waker; 30 | this.waker = waker; 31 | } 32 | } 33 | if (oldWaker != null) { 34 | oldWaker.close(); 35 | } 36 | if (result != null) { 37 | waker.close(); 38 | } 39 | return result; 40 | } 41 | 42 | private void wakeInternal(PollResult result) { 43 | Waker waker = null; 44 | synchronized (this.lock) { 45 | assert this.result == null; 46 | this.result = result; 47 | waker = this.waker; 48 | } 49 | if (waker != null) { 50 | waker.wake(); 51 | } 52 | } 53 | 54 | /** 55 | * Wakes the {@link SimpleFuture} with a result. 56 | * 57 | * @param result Result to wake with. This can be {@code null}. 58 | */ 59 | public void wake(T result) { 60 | this.wakeInternal(() -> { 61 | return result; 62 | }); 63 | } 64 | 65 | /** 66 | * Wakes the {@link SimpleFuture} with an exception. When code calls 67 | * {@link PollResult#get} on the resulting {@link PollResult}, a 68 | * {@link FutureException} will be thrown with the given exception as the 69 | * cause. 70 | * 71 | * @param result Exception to wake with. 72 | */ 73 | public void wakeWithThrowable(Throwable result) { 74 | this.wakeInternal(() -> { 75 | throw new FutureException(result); 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import io.github.gedgygedgy.rust.thread.LocalThreadChecker; 4 | 5 | import java.io.Closeable; 6 | 7 | final class FnAdapter implements Closeable { 8 | private final LocalThreadChecker threadChecker; 9 | private long data; 10 | 11 | private FnAdapter(boolean local) { 12 | this.threadChecker = new LocalThreadChecker(local); 13 | } 14 | 15 | public R call(O self, T arg1, U arg2) { 16 | this.threadChecker.check(); 17 | return this.callInternal(self, arg1, arg2); 18 | } 19 | 20 | private native R callInternal(O self, T arg1, U arg2); 21 | 22 | @Override 23 | public void close() { 24 | this.threadChecker.check(); 25 | this.closeInternal(); 26 | } 27 | 28 | private native void closeInternal(); 29 | } 30 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnBiFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | import java.util.function.BiFunction; 5 | 6 | /** 7 | * Wraps a closure in a Java object. 8 | *

9 | * Instances of this class cannot be obtained directly from Java. Instead, call 10 | * {@code jni_utils::ops::fn_bi_function()}, 11 | * {@code jni_utils::ops::fn_mut_bi_function()}, or 12 | * {@code jni_utils::ops::fn_once_bi_function()} from Rust code to obtain an 13 | * instance of this class. 14 | */ 15 | public interface FnBiFunction extends BiFunction, Closeable { 16 | /** 17 | * Runs the closure associated with this object. 18 | *

19 | * If the closure is a {@code std::ops::Fn} or {@code std::ops::FnMut}, 20 | * calling this method twice will call the associated closure twice. If 21 | * the closure is a {@code std::ops::FnOnce}, the second call will return 22 | * {@code null}. If {@link close} has already been called, this method 23 | * returns {@code null}. 24 | */ 25 | @Override 26 | public R apply(T t, U u); 27 | 28 | /** 29 | * Disposes of the closure associated with this object. 30 | *

31 | * This method is idempotent - if it's called twice, the second call is a 32 | * no-op. 33 | */ 34 | @Override 35 | public void close(); 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnBiFunctionImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | 5 | final class FnBiFunctionImpl implements FnBiFunction { 6 | private final FnAdapter, T, U, R> adapter; 7 | 8 | private FnBiFunctionImpl(FnAdapter, T, U, R> adapter) { 9 | this.adapter = adapter; 10 | } 11 | 12 | @Override 13 | public R apply(T t, U u) { 14 | return this.adapter.call(this, t, u); 15 | } 16 | 17 | @Override 18 | public void close() { 19 | this.adapter.close(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | import java.util.function.Function; 5 | 6 | /** 7 | * Wraps a closure in a Java object. 8 | *

9 | * Instances of this class cannot be obtained directly from Java. Instead, call 10 | * {@code jni_utils::ops::fn_function()}, 11 | * {@code jni_utils::ops::fn_mut_function()}, or 12 | * {@code jni_utils::ops::fn_once_function()} from Rust code to obtain an 13 | * instance of this class. 14 | */ 15 | public interface FnFunction extends Function, Closeable { 16 | /** 17 | * Runs the closure associated with this object. 18 | *

19 | * If the closure is a {@code std::ops::Fn} or {@code std::ops::FnMut}, 20 | * calling this method twice will call the associated closure twice. If 21 | * the closure is a {@code std::ops::FnOnce}, the second call will return 22 | * {@code null}. If {@link close} has already been called, this method 23 | * returns {@code null}. 24 | */ 25 | @Override 26 | public R apply(T t); 27 | 28 | /** 29 | * Disposes of the closure associated with this object. 30 | *

31 | * This method is idempotent - if it's called twice, the second call is a 32 | * no-op. 33 | */ 34 | @Override 35 | public void close(); 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnFunctionImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | 5 | final class FnFunctionImpl implements FnFunction { 6 | private final FnAdapter, T, Void, R> adapter; 7 | 8 | private FnFunctionImpl(FnAdapter, T, Void, R> adapter) { 9 | this.adapter = adapter; 10 | } 11 | 12 | @Override 13 | public R apply(T t) { 14 | return this.adapter.call(this, t, null); 15 | } 16 | 17 | @Override 18 | public void close() { 19 | this.adapter.close(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnRunnable.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | 5 | /** 6 | * Wraps a closure in a Java object. 7 | *

8 | * Instances of this class cannot be obtained directly from Java. Instead, call 9 | * {@code jni_utils::ops::fn_runnable()}, 10 | * {@code jni_utils::ops::fn_mut_runnable()}, or 11 | * {@code jni_utils::ops::fn_once_runnable()} from Rust code to obtain an 12 | * instance of this class. 13 | */ 14 | public interface FnRunnable extends Runnable, Closeable { 15 | /** 16 | * Runs the closure associated with this object. 17 | *

18 | * If the closure is a {@code std::ops::Fn} or {@code std::ops::FnMut}, 19 | * calling this method twice will call the associated closure twice. If 20 | * the closure is a {@code std::ops::FnOnce}, this method is idempotent - 21 | * calling it a second time will have no effect. If {@link close} has 22 | * already been called, this method is a no-op. 23 | */ 24 | @Override 25 | public void run(); 26 | 27 | /** 28 | * Disposes of the closure associated with this object. 29 | *

30 | * This method is idempotent - if it's called twice, the second call is a 31 | * no-op. 32 | */ 33 | @Override 34 | public void close(); 35 | } 36 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/ops/FnRunnableImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.ops; 2 | 3 | import java.io.Closeable; 4 | 5 | final class FnRunnableImpl implements FnRunnable { 6 | private final FnAdapter adapter; 7 | 8 | private FnRunnableImpl(FnAdapter adapter) { 9 | this.adapter = adapter; 10 | } 11 | 12 | @Override 13 | public void run() { 14 | this.adapter.call(this, null, null); 15 | } 16 | 17 | @Override 18 | public void close() { 19 | this.adapter.close(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/panic/PanicException.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.panic; 2 | 3 | /** 4 | * Exception that represents a Rust panic. This exception can be used to carry 5 | * Rust panics across the JNI boundary. 6 | *

7 | * Instances of this class cannot be obtained directly from Java. Instead, call 8 | * {@code jni_utils::exceptions::JPanicException::new())} to create a new 9 | * instance of this class. 10 | */ 11 | public final class PanicException extends RuntimeException { 12 | @SuppressWarnings("unused") // Native code uses this field. 13 | private long any; 14 | 15 | private PanicException(String message) { 16 | super(message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/stream/QueueStream.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.stream; 2 | 3 | import io.github.gedgygedgy.rust.task.PollResult; 4 | import io.github.gedgygedgy.rust.task.Waker; 5 | 6 | import java.util.LinkedList; 7 | import java.util.Queue; 8 | 9 | /** 10 | * Simple implementation of {@link Stream} which can be woken with items. 11 | * In general, methods which create a {@link QueueStream} should return it as 12 | * a {@link Stream} to keep calling code from waking it. 13 | */ 14 | public class QueueStream implements Stream { 15 | private Waker waker = null; 16 | private final Queue result = new LinkedList<>(); 17 | private boolean finished = false; 18 | private final Object lock = new Object(); 19 | 20 | /** 21 | * Creates a new {@link QueueStream} object. 22 | */ 23 | public QueueStream() {} 24 | 25 | @Override 26 | public PollResult> pollNext(Waker waker) { 27 | PollResult> result = null; 28 | Waker oldWaker = null; 29 | synchronized (this.lock) { 30 | if (!this.result.isEmpty()) { 31 | result = () -> () -> this.result.remove(); 32 | } else if (this.finished) { 33 | result = () -> null; 34 | } else { 35 | oldWaker = this.waker; 36 | this.waker = waker; 37 | } 38 | } 39 | if (oldWaker != null) { 40 | oldWaker.close(); 41 | } 42 | if (result != null) { 43 | waker.close(); 44 | } 45 | return result; 46 | } 47 | 48 | private void doEvent(Runnable r) { 49 | Waker waker = null; 50 | synchronized (this.lock) { 51 | assert !this.finished; 52 | r.run(); 53 | waker = this.waker; 54 | } 55 | if (waker != null) { 56 | waker.wake(); 57 | } 58 | } 59 | 60 | /** 61 | * Adds a new item to the queue of items to be returned by 62 | * {@link pollNext}. This can be anything, including {@code null}. 63 | * 64 | * @param item Item to add to the queue. 65 | */ 66 | public void add(T item) { 67 | this.doEvent(() -> { 68 | synchronized (this.lock) { 69 | this.result.add(item); 70 | } 71 | }); 72 | } 73 | 74 | /** 75 | * Marks the queue as finished. After the queue is finished, no new items 76 | * can be added. Once all existing items have been drained from the queue, 77 | * the {@link PollResult} returned from {@link pollNext} will return 78 | * {@code null} from its own {@link PollResult#get}. 79 | */ 80 | public void finish() { 81 | this.doEvent(() -> this.finished = true); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/stream/Stream.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.stream; 2 | 3 | import io.github.gedgygedgy.rust.task.PollResult; 4 | import io.github.gedgygedgy.rust.task.Waker; 5 | 6 | /** 7 | * Interface for allowing Rust code to interact with Java code in an 8 | * asynchronous manner. The intention of this interface is for asynchronous 9 | * Rust code to directly call Java code that returns a {@link Stream}, and 10 | * then poll the {@link Stream} using a {@code jni_utils::stream::JStream}. 11 | * In this way, the complexities of interacting with asynchronous Java code can 12 | * be abstracted into a simple {@code jni_utils::stream::JStream} that Rust 13 | * code can {@code await} on. 14 | *

15 | * In general, you will probably want to use {@link QueueStream} for most use 16 | * cases. 17 | */ 18 | public interface Stream { 19 | /** 20 | * Polls the {@link Stream} for a new item. You generally won't need to 21 | * call this directly, as {@code jni_utils::future::JFuture} takes care of 22 | * this for you. 23 | *

24 | * If no item is available, and it is not known whether or not more items 25 | * will be available in the future, this method should return {@code null}. 26 | * If an item is available, this method should return a {@link PollResult} 27 | * whose {@link PollResult#get} returns a non-{@code null} 28 | * {@link StreamPoll}. If no item is available, and no items will ever be 29 | * available, this method should return a {@link PollResult} whose 30 | * {@link PollResult#get} returns {@code null}. 31 | * 32 | * @param waker Waker to wake with when the {@link Stream} has a result. 33 | * @return The next item. 34 | */ 35 | PollResult> pollNext(Waker waker); 36 | } 37 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/stream/StreamPoll.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.stream; 2 | 3 | /** 4 | * Represents the result of polling an async stream. 5 | *

6 | * See {@link Stream#pollNext} for a description of how to use this interface. 7 | */ 8 | public interface StreamPoll { 9 | /** 10 | * Gets the stream item. This can be anything, including {@code null}. 11 | * 12 | * @return The stream item. 13 | */ 14 | T get(); 15 | } 16 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/task/PollResult.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.task; 2 | 3 | /** 4 | * Represents the result of polling an async future. 5 | *

6 | * If a future result is available, the future should return an instance of 7 | * this interface which returns the result from the {@link get} method. 8 | * If no result is available, the future should return {@code null} (NOT a 9 | * {@link PollResult} whose {@link get} returns {@code null}.) A 10 | * non-{@code null} {@link PollResult} indicates that the future has completed. 11 | */ 12 | public interface PollResult { 13 | /** 14 | * Gets the future result. This can be anything, including {@code null}. 15 | * 16 | * @return The future result. 17 | */ 18 | public T get(); 19 | } 20 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/task/Waker.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.task; 2 | 3 | import io.github.gedgygedgy.rust.ops.FnRunnable; 4 | 5 | import java.io.Closeable; 6 | 7 | /** 8 | * Wraps a {@code std::task::Waker} in a Java object. 9 | *

10 | * Instances of this class cannot be obtained directly from Java. Instead, call 11 | * {@code jni_utils::task::waker()} from Rust code to obtain an instance of 12 | * this class. (This generally shouldn't be necessary, since 13 | * {@code jni_utils::future::JFuture} and {@code jni_utils::stream::JStream} 14 | * take care of this for you.) 15 | */ 16 | public final class Waker implements Closeable { 17 | private final FnRunnable wakeRunnable; 18 | 19 | private Waker(FnRunnable wakeRunnable) { 20 | this.wakeRunnable = wakeRunnable; 21 | } 22 | 23 | /** 24 | * Wakes the {@code std::task::Waker} associated with this object. 25 | *

26 | * If the {@code std::task::Waker} has already been woken, this method 27 | * does nothing. 28 | */ 29 | public void wake() { 30 | this.wakeRunnable.run(); 31 | } 32 | 33 | /** 34 | * Frees the {@code std::task::Waker} associated with this object. 35 | */ 36 | @Override 37 | public void close() { 38 | this.wakeRunnable.close(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/thread/LocalThreadChecker.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.thread; 2 | 3 | public class LocalThreadChecker { 4 | private final Thread origin; 5 | 6 | public LocalThreadChecker(boolean local) { 7 | this.origin = local ? Thread.currentThread() : null; 8 | } 9 | 10 | public void check() { 11 | if (this.origin != null && this.origin != Thread.currentThread()) { 12 | throw new LocalThreadException(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/src/main/java/io/github/gedgygedgy/rust/thread/LocalThreadException.java: -------------------------------------------------------------------------------- 1 | package io.github.gedgygedgy.rust.thread; 2 | 3 | /** 4 | * Thrown when a non-{@code Send} Rust object is accessed from outside its 5 | * origin thread. 6 | */ 7 | public class LocalThreadException extends IllegalStateException { 8 | /** 9 | * Creates a new exception. 10 | */ 11 | public LocalThreadException() {} 12 | } 13 | -------------------------------------------------------------------------------- /rust/arrays.rs: -------------------------------------------------------------------------------- 1 | use jni::{ 2 | errors::Result, 3 | sys::{jbyte, jbyteArray, jint}, 4 | JNIEnv, 5 | }; 6 | use std::slice; 7 | 8 | /// Create a new Java byte array from the given slice. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `env` - Java environment in which to create the new byte array. 13 | /// * `slice` - Slice to convert into a byte array. 14 | pub fn slice_to_byte_array<'a, 'b>(env: &'a JNIEnv<'a>, slice: &'b [u8]) -> Result { 15 | let obj = env.new_byte_array(slice.len() as jint)?; 16 | let slice = unsafe { &*(slice as *const [u8] as *const [jbyte]) }; 17 | env.set_byte_array_region(obj, 0, slice)?; 18 | Ok(obj) 19 | } 20 | 21 | /// Get a [`Vec`] of bytes from the given Java byte array. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `env` - Java environment to use. 26 | /// * `obj` - Byte array to convert into a [`Vec`]. 27 | pub fn byte_array_to_vec<'a>(env: &'a JNIEnv<'a>, obj: jbyteArray) -> Result> { 28 | let size = env.get_array_length(obj)? as usize; 29 | let mut result = Vec::with_capacity(size); 30 | unsafe { 31 | let result_slice = slice::from_raw_parts_mut(result.as_mut_ptr() as *mut jbyte, size); 32 | env.get_byte_array_region(obj, 0, result_slice)?; 33 | result.set_len(size); 34 | } 35 | Ok(result) 36 | } 37 | 38 | #[cfg(test)] 39 | mod test { 40 | use crate::test_utils; 41 | 42 | #[test] 43 | fn test_slice_to_byte_array() { 44 | test_utils::JVM_ENV.with(|env| { 45 | let obj = super::slice_to_byte_array(env, &[1, 2, 3, 4, 5]).unwrap(); 46 | assert_eq!(env.get_array_length(obj).unwrap(), 5); 47 | 48 | let mut bytes = [0i8; 5]; 49 | env.get_byte_array_region(obj, 0, &mut bytes).unwrap(); 50 | assert_eq!(bytes, [1, 2, 3, 4, 5]); 51 | }); 52 | } 53 | 54 | #[test] 55 | fn test_byte_array_to_vec() { 56 | test_utils::JVM_ENV.with(|env| { 57 | let obj = env.new_byte_array(5).unwrap(); 58 | env.set_byte_array_region(obj, 0, &[1, 2, 3, 4, 5]).unwrap(); 59 | 60 | let vec = super::byte_array_to_vec(env, obj).unwrap(); 61 | assert_eq!(vec, vec![1, 2, 3, 4, 5]); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rust/classcache.rs: -------------------------------------------------------------------------------- 1 | use jni::{JNIEnv, objects::GlobalRef}; 2 | use once_cell::sync::OnceCell; 3 | use dashmap::DashMap; 4 | use ::jni::{errors::Result}; 5 | 6 | static CLASSCACHE: OnceCell> = OnceCell::new(); 7 | 8 | pub fn find_add_class(env: &JNIEnv, classname: &str) -> Result<()> { 9 | let cache = CLASSCACHE.get_or_init(|| DashMap::new()); 10 | cache.insert(classname.to_owned(), env.new_global_ref(env.find_class(classname).unwrap()).unwrap()); 11 | Ok(()) 12 | } 13 | 14 | pub fn get_class(classname: &str) -> Option { 15 | let cache = CLASSCACHE.get_or_init(|| DashMap::new()); 16 | cache.get(classname).and_then(|pair| Some(pair.value().clone())) 17 | } 18 | -------------------------------------------------------------------------------- /rust/exceptions.rs: -------------------------------------------------------------------------------- 1 | use jni::{ 2 | descriptors::Desc, 3 | errors::Error, 4 | objects::{JClass, JObject, JThrowable}, 5 | JNIEnv, 6 | }; 7 | use std::{ 8 | any::Any, 9 | convert::TryFrom, 10 | panic::{catch_unwind, resume_unwind, UnwindSafe}, 11 | sync::MutexGuard, 12 | }; 13 | 14 | /// Result from [`try_block`]. This object can be chained into 15 | /// [`catch`](TryCatchResult::catch) calls to catch exceptions. When finished 16 | /// with the try/catch sequence, the result can be obtained from 17 | /// [`result`](TryCatchResult::result). 18 | pub struct TryCatchResult<'a: 'b, 'b, T> { 19 | env: &'b JNIEnv<'a>, 20 | try_result: Result, Error>, 21 | catch_result: Option>, 22 | } 23 | 24 | /// Attempt to execute a block of JNI code. If the code causes an exception 25 | /// to be thrown, it will be stored in the resulting [`TryCatchResult`] for 26 | /// matching with [`catch`](TryCatchResult::catch). If an exception was already 27 | /// being thrown before [`try_block`] is called, the given block will not be 28 | /// executed, nor will any of the [`catch`](TryCatchResult::catch) blocks. 29 | /// 30 | /// # Arguments 31 | /// 32 | /// * `env` - Java environment to use. 33 | /// * `block` - Block of JNI code to run. 34 | pub fn try_block<'a: 'b, 'b, T>( 35 | env: &'b JNIEnv<'a>, 36 | block: impl FnOnce() -> Result, 37 | ) -> TryCatchResult<'a, 'b, T> { 38 | TryCatchResult { 39 | env, 40 | try_result: (|| { 41 | if env.exception_check()? { 42 | Err(Error::JavaException) 43 | } else { 44 | Ok(block()) 45 | } 46 | })(), 47 | catch_result: None, 48 | } 49 | } 50 | 51 | impl<'a: 'b, 'b, T> TryCatchResult<'a, 'b, T> { 52 | /// Attempt to catch an exception thrown by [`try_block`]. If the thrown 53 | /// exception matches the given class, the block is executed. If no 54 | /// exception was thrown by [`try_block`], or if the exception does not 55 | /// match the given class, the block is not executed. 56 | /// 57 | /// # Arguments 58 | /// 59 | /// * `class` - Exception class to match. 60 | /// * `block` - Block of JNI code to run. 61 | pub fn catch( 62 | self, 63 | class: impl Desc<'a, JClass<'a>>, 64 | block: impl FnOnce(JThrowable<'a>) -> Result, 65 | ) -> Self { 66 | match (self.try_result, self.catch_result) { 67 | (Err(e), _) => Self { 68 | env: self.env, 69 | try_result: Err(e), 70 | catch_result: None, 71 | }, 72 | (Ok(Ok(r)), _) => Self { 73 | env: self.env, 74 | try_result: Ok(Ok(r)), 75 | catch_result: None, 76 | }, 77 | (Ok(Err(e)), Some(r)) => Self { 78 | env: self.env, 79 | try_result: Ok(Err(e)), 80 | catch_result: Some(r), 81 | }, 82 | (Ok(Err(Error::JavaException)), None) => { 83 | let env = self.env; 84 | let catch_result = (|| { 85 | if env.exception_check()? { 86 | let ex = env.exception_occurred()?; 87 | let _auto_local = env.auto_local(ex.clone()); 88 | env.exception_clear()?; 89 | if env.is_instance_of(ex, class)? { 90 | return block(ex).map(|o| Some(o)); 91 | } 92 | env.throw(ex)?; 93 | } 94 | Ok(None) 95 | })() 96 | .transpose(); 97 | Self { 98 | env, 99 | try_result: Ok(Err(Error::JavaException)), 100 | catch_result, 101 | } 102 | } 103 | (Ok(Err(e)), None) => Self { 104 | env: self.env, 105 | try_result: Ok(Err(e)), 106 | catch_result: None, 107 | }, 108 | } 109 | } 110 | 111 | /// Get the result of the try/catch sequence. If [`try_block`] succeeded, 112 | /// or if one of the [`catch`](TryCatchResult::catch) calls succeeded, its 113 | /// result is returned. 114 | pub fn result(self) -> Result { 115 | match (self.try_result, self.catch_result) { 116 | (Err(e), _) => Err(e), 117 | (Ok(Ok(r)), _) => Ok(r), 118 | (Ok(Err(_)), Some(r)) => r, 119 | (Ok(Err(e)), None) => Err(e), 120 | } 121 | } 122 | } 123 | 124 | /// Wrapper for [`JObject`]s that implement 125 | /// `io.github.gedgygedgy.rust.panic.PanicException`. Provides methods to get 126 | /// and take the associated [`Any`]. 127 | /// 128 | /// Looks up the class and method IDs on creation rather than for every method 129 | /// call. 130 | pub struct JPanicException<'a: 'b, 'b> { 131 | internal: JThrowable<'a>, 132 | env: &'b JNIEnv<'a>, 133 | } 134 | 135 | impl<'a: 'b, 'b> JPanicException<'a, 'b> { 136 | /// Create a [`JPanicException`] from the environment and an object. This 137 | /// looks up the necessary class and method IDs to call all of the methods 138 | /// on it so that extra work doesn't need to be done on every method call. 139 | /// 140 | /// # Arguments 141 | /// 142 | /// * `env` - Java environment to use. 143 | /// * `obj` - Object to wrap. 144 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JThrowable<'a>) -> Result { 145 | Ok(Self { internal: obj, env }) 146 | } 147 | 148 | /// Create a new `PanicException` from the given [`Any`]. 149 | /// 150 | /// # Arguments 151 | /// 152 | /// * `env` - Java environment to use. 153 | /// * `any` - [`Any`] to put in the `PanicException`. 154 | pub fn new(env: &'b JNIEnv<'a>, any: Box) -> Result { 155 | let msg = if let Some(s) = any.downcast_ref::<&str>() { 156 | env.new_string(s)? 157 | } else if let Some(s) = any.downcast_ref::() { 158 | env.new_string(s)? 159 | } else { 160 | JObject::null().into() 161 | }; 162 | 163 | let obj = env.new_object( 164 | "io/github/gedgygedgy/rust/panic/PanicException", 165 | "(Ljava/lang/String;)V", 166 | &[msg.into()], 167 | )?; 168 | env.set_rust_field(obj, "any", any)?; 169 | Self::from_env(env, obj.into()) 170 | } 171 | 172 | /// Borrows the [`Any`] associated with the exception. 173 | pub fn get(&self) -> Result>, Error> { 174 | self.env.get_rust_field(self.internal, "any") 175 | } 176 | 177 | /// Takes the [`Any`] associated with the exception. 178 | pub fn take(&self) -> Result, Error> { 179 | self.env.take_rust_field(self.internal, "any") 180 | } 181 | 182 | /// Resumes unwinding using the [`Any`] associated with the exception. 183 | pub fn resume_unwind(&self) -> Result<(), Error> { 184 | resume_unwind(self.take()?); 185 | } 186 | } 187 | 188 | impl<'a: 'b, 'b> TryFrom> for Box { 189 | type Error = Error; 190 | 191 | fn try_from(ex: JPanicException<'a, 'b>) -> Result { 192 | ex.take() 193 | } 194 | } 195 | 196 | impl<'a: 'b, 'b> From> for JThrowable<'a> { 197 | fn from(ex: JPanicException<'a, 'b>) -> Self { 198 | ex.internal 199 | } 200 | } 201 | 202 | impl<'a: 'b, 'b> ::std::ops::Deref for JPanicException<'a, 'b> { 203 | type Target = JThrowable<'a>; 204 | 205 | fn deref(&self) -> &Self::Target { 206 | &self.internal 207 | } 208 | } 209 | 210 | /// Calls the given closure. If it panics, catch the unwind, wrap it in a 211 | /// `io.github.gedgygedgy.rust.panic.PanicException`, and throw it. 212 | /// 213 | /// # Arguments 214 | /// 215 | /// * `env` - Java environment to use. 216 | /// * `f` - Closure to call. 217 | pub fn throw_unwind<'a: 'b, 'b, R>( 218 | env: &'b JNIEnv<'a>, 219 | f: impl FnOnce() -> R + UnwindSafe, 220 | ) -> Result> { 221 | catch_unwind(f).map_err(|e| { 222 | let old_ex = if env.exception_check()? { 223 | let ex = env.exception_occurred()?; 224 | env.exception_clear()?; 225 | Some(ex) 226 | } else { 227 | None 228 | }; 229 | let ex = JPanicException::new(env, e)?; 230 | 231 | if let Some(old_ex) = old_ex { 232 | env.call_method( 233 | ex.clone(), 234 | "addSuppressed", 235 | "(Ljava/lang/Throwable;)V", 236 | &[old_ex.into()], 237 | )?; 238 | } 239 | let ex: JThrowable = ex.into(); 240 | env.throw(ex)?; 241 | Ok(()) 242 | }) 243 | } 244 | 245 | #[cfg(test)] 246 | mod test { 247 | use jni::{errors::Error, objects::JThrowable, JNIEnv}; 248 | 249 | use super::try_block; 250 | use crate::test_utils; 251 | 252 | fn test_catch<'a: 'b, 'b>( 253 | env: &'b JNIEnv<'a>, 254 | throw_class: Option<&str>, 255 | try_result: Result, 256 | rethrow: bool, 257 | ) -> Result { 258 | let old_ex = if env.exception_check().unwrap() { 259 | let ex = env.exception_occurred().unwrap(); 260 | env.exception_clear().unwrap(); 261 | Some(ex) 262 | } else { 263 | None 264 | }; 265 | let illegal_argument_exception = env 266 | .find_class("java/lang/IllegalArgumentException") 267 | .unwrap(); 268 | if let Some(ex) = old_ex { 269 | env.throw(ex).unwrap(); 270 | } 271 | 272 | let ex = throw_class.map(|c| { 273 | let ex: JThrowable = env.new_object(c, "()V", &[]).unwrap().into(); 274 | ex 275 | }); 276 | 277 | try_block(env, || { 278 | if let Some(t) = ex { 279 | env.throw(t).unwrap(); 280 | } 281 | try_result 282 | }) 283 | .catch(illegal_argument_exception, |caught| { 284 | assert!(!env.exception_check().unwrap()); 285 | assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); 286 | Ok(1) 287 | }) 288 | .catch("java/lang/ArrayIndexOutOfBoundsException", |caught| { 289 | assert!(!env.exception_check().unwrap()); 290 | assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); 291 | if rethrow { 292 | Err(Error::JavaException) 293 | } else { 294 | Ok(2) 295 | } 296 | }) 297 | .catch("java/lang/IndexOutOfBoundsException", |caught| { 298 | assert!(!env.exception_check().unwrap()); 299 | assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); 300 | if rethrow { 301 | env.throw(caught).unwrap(); 302 | Err(Error::JavaException) 303 | } else { 304 | Ok(3) 305 | } 306 | }) 307 | .catch("java/lang/StringIndexOutOfBoundsException", |caught| { 308 | assert!(!env.exception_check().unwrap()); 309 | assert!(env.is_same_object(ex.unwrap(), caught).unwrap()); 310 | Ok(4) 311 | }) 312 | .result() 313 | } 314 | 315 | #[test] 316 | fn test_catch_first() { 317 | test_utils::JVM_ENV.with(|env| { 318 | assert_eq!( 319 | test_catch( 320 | &env, 321 | Some("java/lang/IllegalArgumentException"), 322 | Err(Error::JavaException), 323 | false, 324 | ) 325 | .unwrap(), 326 | 1 327 | ); 328 | assert!(!env.exception_check().unwrap()); 329 | }); 330 | } 331 | 332 | #[test] 333 | fn test_catch_second() { 334 | test_utils::JVM_ENV.with(|env| { 335 | assert_eq!( 336 | test_catch( 337 | &env, 338 | Some("java/lang/ArrayIndexOutOfBoundsException"), 339 | Err(Error::JavaException), 340 | false, 341 | ) 342 | .unwrap(), 343 | 2 344 | ); 345 | assert!(!env.exception_check().unwrap()); 346 | }); 347 | } 348 | 349 | #[test] 350 | fn test_catch_third() { 351 | test_utils::JVM_ENV.with(|env| { 352 | assert_eq!( 353 | test_catch( 354 | &env, 355 | Some("java/lang/StringIndexOutOfBoundsException"), 356 | Err(Error::JavaException), 357 | false, 358 | ) 359 | .unwrap(), 360 | 3 361 | ); 362 | assert!(!env.exception_check().unwrap()); 363 | }); 364 | } 365 | 366 | #[test] 367 | fn test_catch_ok() { 368 | test_utils::JVM_ENV.with(|env| { 369 | assert_eq!(test_catch(&env, None, Ok(0), false).unwrap(), 0); 370 | assert!(!env.exception_check().unwrap()); 371 | }); 372 | } 373 | 374 | #[test] 375 | fn test_catch_none() { 376 | test_utils::JVM_ENV.with(|env| { 377 | if let Error::JavaException = test_catch( 378 | &env, 379 | Some("java/lang/SecurityException"), 380 | Err(Error::JavaException), 381 | false, 382 | ) 383 | .unwrap_err() 384 | { 385 | assert!(env.exception_check().unwrap()); 386 | let ex = env.exception_occurred().unwrap(); 387 | env.exception_clear().unwrap(); 388 | assert!(env 389 | .is_instance_of(ex, "java/lang/SecurityException") 390 | .unwrap()); 391 | } else { 392 | panic!("No JavaException"); 393 | } 394 | }); 395 | } 396 | 397 | #[test] 398 | fn test_catch_other() { 399 | test_utils::JVM_ENV.with(|env| { 400 | if let Error::InvalidCtorReturn = 401 | test_catch(env, None, Err(Error::InvalidCtorReturn), false).unwrap_err() 402 | { 403 | assert!(!env.exception_check().unwrap()); 404 | } else { 405 | panic!("InvalidCtorReturn not found"); 406 | } 407 | }); 408 | } 409 | 410 | #[test] 411 | fn test_catch_bogus_exception() { 412 | test_utils::JVM_ENV.with(|env| { 413 | if let Error::JavaException = 414 | test_catch(env, None, Err(Error::JavaException), false).unwrap_err() 415 | { 416 | assert!(!env.exception_check().unwrap()); 417 | } else { 418 | panic!("JavaException not found"); 419 | } 420 | }); 421 | } 422 | 423 | #[test] 424 | fn test_catch_prior_exception() { 425 | test_utils::JVM_ENV.with(|env| { 426 | let ex: JThrowable = env 427 | .new_object("java/lang/IllegalArgumentException", "()V", &[]) 428 | .unwrap() 429 | .into(); 430 | env.throw(ex).unwrap(); 431 | 432 | if let Error::JavaException = test_catch(&env, None, Ok(0), false).unwrap_err() { 433 | assert!(env.exception_check().unwrap()); 434 | let actual_ex = env.exception_occurred().unwrap(); 435 | env.exception_clear().unwrap(); 436 | assert!(env.is_same_object(actual_ex, ex).unwrap()); 437 | } else { 438 | panic!("JavaException not found"); 439 | } 440 | }); 441 | } 442 | 443 | #[test] 444 | fn test_catch_rethrow() { 445 | test_utils::JVM_ENV.with(|env| { 446 | if let Error::JavaException = test_catch( 447 | &env, 448 | Some("java/lang/StringIndexOutOfBoundsException"), 449 | Err(Error::JavaException), 450 | true, 451 | ) 452 | .unwrap_err() 453 | { 454 | assert!(env.exception_check().unwrap()); 455 | let ex = env.exception_occurred().unwrap(); 456 | env.exception_clear().unwrap(); 457 | assert!(env 458 | .is_instance_of(ex, "java/lang/StringIndexOutOfBoundsException") 459 | .unwrap()); 460 | } else { 461 | panic!("JavaException not found"); 462 | } 463 | }); 464 | } 465 | 466 | #[test] 467 | fn test_catch_bogus_rethrow() { 468 | test_utils::JVM_ENV.with(|env| { 469 | if let Error::JavaException = test_catch( 470 | &env, 471 | Some("java/lang/ArrayIndexOutOfBoundsException"), 472 | Err(Error::JavaException), 473 | true, 474 | ) 475 | .unwrap_err() 476 | { 477 | assert!(!env.exception_check().unwrap()); 478 | } else { 479 | panic!("JavaException not found"); 480 | } 481 | }); 482 | } 483 | 484 | #[test] 485 | fn test_panic_exception_static_str() { 486 | test_utils::JVM_ENV.with(|env| { 487 | use jni::{objects::JString, strings::JavaStr}; 488 | 489 | const STATIC_MSG: &'static str = "This is a &'static str"; 490 | let ex = super::JPanicException::new(env, Box::new(STATIC_MSG)).unwrap(); 491 | 492 | { 493 | let any = ex.get().unwrap(); 494 | assert_eq!(*any.downcast_ref::<&str>().unwrap(), STATIC_MSG); 495 | } 496 | 497 | let msg: JString = env 498 | .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) 499 | .unwrap() 500 | .l() 501 | .unwrap() 502 | .into(); 503 | let str = JavaStr::from_env(env, msg).unwrap(); 504 | assert_eq!(str.to_str().unwrap(), STATIC_MSG); 505 | }); 506 | } 507 | 508 | #[test] 509 | fn test_panic_exception_string() { 510 | test_utils::JVM_ENV.with(|env| { 511 | use jni::{objects::JString, strings::JavaStr}; 512 | use std::any::Any; 513 | 514 | const STRING_MSG: &'static str = "This is a String"; 515 | let ex = super::JPanicException::new(env, Box::new(STRING_MSG.to_string())).unwrap(); 516 | 517 | { 518 | let any = ex.get().unwrap(); 519 | assert_eq!(*any.downcast_ref::().unwrap(), STRING_MSG); 520 | } 521 | 522 | let msg: JString = env 523 | .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) 524 | .unwrap() 525 | .l() 526 | .unwrap() 527 | .into(); 528 | let str = JavaStr::from_env(env, msg).unwrap(); 529 | assert_eq!(str.to_str().unwrap(), STRING_MSG); 530 | 531 | let any: Box = ex.take().unwrap(); 532 | assert_eq!(*any.downcast::().unwrap(), STRING_MSG); 533 | }); 534 | } 535 | 536 | #[test] 537 | fn test_panic_exception_other() { 538 | test_utils::JVM_ENV.with(|env| { 539 | use jni::objects::JObject; 540 | use std::{any::Any, convert::TryInto}; 541 | 542 | let ex = super::JPanicException::new(env, Box::new(42)).unwrap(); 543 | 544 | { 545 | let any = ex.get().unwrap(); 546 | assert_eq!(*any.downcast_ref::().unwrap(), 42); 547 | } 548 | 549 | let msg = env 550 | .call_method(ex.clone(), "getMessage", "()Ljava/lang/String;", &[]) 551 | .unwrap() 552 | .l() 553 | .unwrap(); 554 | assert!(env.is_same_object(msg, JObject::null()).unwrap()); 555 | 556 | let any: Box = ex.try_into().unwrap(); 557 | assert_eq!(*any.downcast::().unwrap(), 42); 558 | }); 559 | } 560 | 561 | #[test] 562 | fn test_throw_unwind_ok() { 563 | test_utils::JVM_ENV.with(|env| { 564 | let result = super::throw_unwind(env, || 42).unwrap(); 565 | assert_eq!(result, 42); 566 | assert!(!env.exception_check().unwrap()); 567 | }); 568 | } 569 | 570 | #[test] 571 | fn test_throw_unwind_panic() { 572 | test_utils::JVM_ENV.with(|env| { 573 | super::throw_unwind(env, || panic!("This is a panic")) 574 | .unwrap_err() 575 | .unwrap(); 576 | assert!(env.exception_check().unwrap()); 577 | let ex = env.exception_occurred().unwrap(); 578 | env.exception_clear().unwrap(); 579 | assert!(env 580 | .is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") 581 | .unwrap()); 582 | 583 | let suppressed_list = env 584 | .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) 585 | .unwrap() 586 | .l() 587 | .unwrap(); 588 | assert_eq!( 589 | env.get_array_length(suppressed_list.into_inner()).unwrap(), 590 | 0 591 | ); 592 | 593 | let ex = super::JPanicException::from_env(env, ex).unwrap(); 594 | let any = ex.take().unwrap(); 595 | let str = any.downcast::<&str>().unwrap(); 596 | assert_eq!(*str, "This is a panic"); 597 | }); 598 | } 599 | 600 | #[test] 601 | fn test_throw_unwind_panic_suppress() { 602 | test_utils::JVM_ENV.with(|env| { 603 | let old_ex: JThrowable = env 604 | .new_object("java/lang/Exception", "()V", &[]) 605 | .unwrap() 606 | .into(); 607 | env.throw(old_ex).unwrap(); 608 | 609 | super::throw_unwind(env, || panic!("This is a panic")) 610 | .unwrap_err() 611 | .unwrap(); 612 | assert!(env.exception_check().unwrap()); 613 | let ex = env.exception_occurred().unwrap(); 614 | env.exception_clear().unwrap(); 615 | assert!(env 616 | .is_instance_of(ex, "io/github/gedgygedgy/rust/panic/PanicException") 617 | .unwrap()); 618 | 619 | let suppressed_list = env 620 | .call_method(ex, "getSuppressed", "()[Ljava/lang/Throwable;", &[]) 621 | .unwrap() 622 | .l() 623 | .unwrap(); 624 | assert_eq!( 625 | env.get_array_length(suppressed_list.into_inner()).unwrap(), 626 | 1 627 | ); 628 | let suppressed_ex = env 629 | .get_object_array_element(suppressed_list.into_inner(), 0) 630 | .unwrap(); 631 | assert!(env.is_same_object(old_ex, suppressed_ex).unwrap()); 632 | 633 | let ex = super::JPanicException::from_env(env, ex).unwrap(); 634 | let any = ex.take().unwrap(); 635 | let str = any.downcast::<&str>().unwrap(); 636 | assert_eq!(*str, "This is a panic"); 637 | }); 638 | } 639 | 640 | #[test] 641 | #[should_panic(expected = "This is a panic")] 642 | fn test_panic_exception_resume_unwind() { 643 | test_utils::JVM_ENV.with(|env| { 644 | let ex = super::JPanicException::new(env, Box::new("This is a panic")).unwrap(); 645 | ex.resume_unwind().unwrap(); 646 | }); 647 | } 648 | } 649 | -------------------------------------------------------------------------------- /rust/future.rs: -------------------------------------------------------------------------------- 1 | use crate::task::JPollResult; 2 | use ::jni::{ 3 | errors::{Error, Result}, 4 | objects::{GlobalRef, JMethodID, JObject, JClass}, 5 | signature::JavaType, 6 | JNIEnv, JavaVM, 7 | }; 8 | use static_assertions::assert_impl_all; 9 | use std::{ 10 | convert::TryFrom, 11 | future::Future, 12 | pin::Pin, 13 | task::{Context, Poll}, 14 | }; 15 | 16 | /// Wrapper for [`JObject`]s that implement 17 | /// `io.github.gedgygedgy.rust.future.Future`. Implements 18 | /// [`Future`](std::future::Future) to allow asynchronous Rust code to wait for 19 | /// a result from Java code. 20 | /// 21 | /// Looks up the class and method IDs on creation rather than for every method 22 | /// call. 23 | /// 24 | /// For a [`Send`] version of this, use [`JSendFuture`]. 25 | pub struct JFuture<'a: 'b, 'b> { 26 | internal: JObject<'a>, 27 | poll: JMethodID<'a>, 28 | env: &'b JNIEnv<'a>, 29 | } 30 | 31 | impl<'a: 'b, 'b> JFuture<'a, 'b> { 32 | /// Create a [`JFuture`] from the environment and an object. This looks up 33 | /// the necessary class and method IDs to call all of the methods on it so 34 | /// that extra work doesn't need to be done on every method call. 35 | /// 36 | /// # Arguments 37 | /// 38 | /// * `env` - Java environment to use. 39 | /// * `obj` - Object to wrap. 40 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { 41 | let poll = env.get_method_id( 42 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/future/Future").unwrap().as_obj()), 43 | "poll", 44 | "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", 45 | )?; 46 | Ok(Self { 47 | internal: obj, 48 | poll, 49 | env, 50 | }) 51 | } 52 | 53 | /// Get the `io.github.gedgygedgy.rust.task.PollResult` from this future. 54 | /// Returns `null` if the future is not ready yet. 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `waker` - Waker object to wake later on if the result is not ready. 59 | pub fn poll(&self, waker: JObject<'a>) -> Result> { 60 | let result = self 61 | .env 62 | .call_method_unchecked( 63 | self.internal, 64 | self.poll, 65 | JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".into()), 66 | &[waker.into()], 67 | )? 68 | .l()?; 69 | JPollResult::from_env(self.env, result) 70 | } 71 | 72 | /// Turn the [`JFuture`] into a [`Future`] that can be `await`ed on. 73 | pub fn into_future(self) -> JFutureIntoFuture<'a, 'b> { 74 | JFutureIntoFuture(self) 75 | } 76 | } 77 | 78 | impl<'a: 'b, 'b> ::std::ops::Deref for JFuture<'a, 'b> { 79 | type Target = JObject<'a>; 80 | 81 | fn deref(&self) -> &Self::Target { 82 | &self.internal 83 | } 84 | } 85 | 86 | impl<'a: 'b, 'b> From> for JObject<'a> { 87 | fn from(other: JFuture<'a, 'b>) -> JObject<'a> { 88 | other.internal 89 | } 90 | } 91 | 92 | /// Result of calling [`JFuture::into_future`]. This object can be `await`ed 93 | /// to get a [`JPollResult`]. 94 | pub struct JFutureIntoFuture<'a: 'b, 'b>(JFuture<'a, 'b>); 95 | 96 | impl<'a: 'b, 'b> JFutureIntoFuture<'a, 'b> { 97 | // Switch the Result and Poll return value to make this easier to implement using ?. 98 | fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { 99 | use crate::task::waker; 100 | let result = self.0.poll(waker(self.0.env, context.waker().clone())?)?; 101 | Ok( 102 | if self.0.env.is_same_object(result.clone(), JObject::null())? { 103 | Poll::Pending 104 | } else { 105 | Poll::Ready(result) 106 | }, 107 | ) 108 | } 109 | } 110 | 111 | impl<'a: 'b, 'b> Future for JFutureIntoFuture<'a, 'b> { 112 | type Output = Result>; 113 | 114 | fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { 115 | match self.poll_internal(context) { 116 | Ok(Poll::Ready(result)) => Poll::Ready(Ok(result)), 117 | Ok(Poll::Pending) => Poll::Pending, 118 | Err(err) => Poll::Ready(Err(err)), 119 | } 120 | } 121 | } 122 | 123 | impl<'a: 'b, 'b> From> for JFuture<'a, 'b> { 124 | fn from(fut: JFutureIntoFuture<'a, 'b>) -> Self { 125 | fut.0 126 | } 127 | } 128 | 129 | impl<'a: 'b, 'b> std::ops::Deref for JFutureIntoFuture<'a, 'b> { 130 | type Target = JFuture<'a, 'b>; 131 | 132 | fn deref(&self) -> &Self::Target { 133 | &self.0 134 | } 135 | } 136 | 137 | /// [`Send`] version of [`JFuture`]. Instead of storing a [`JNIEnv`], it stores 138 | /// a [`JavaVM`] and calls [`JavaVM::get_env`] when [`Future::poll`] is called. 139 | pub struct JSendFuture { 140 | internal: GlobalRef, 141 | vm: JavaVM, 142 | } 143 | 144 | impl<'a: 'b, 'b> TryFrom> for JSendFuture { 145 | type Error = Error; 146 | 147 | fn try_from(future: JFuture<'a, 'b>) -> Result { 148 | Ok(Self { 149 | internal: future.env.new_global_ref(future.internal)?, 150 | vm: future.env.get_java_vm()?, 151 | }) 152 | } 153 | } 154 | 155 | impl ::std::ops::Deref for JSendFuture { 156 | type Target = GlobalRef; 157 | 158 | fn deref(&self) -> &Self::Target { 159 | &self.internal 160 | } 161 | } 162 | 163 | impl JSendFuture { 164 | fn poll_internal(&self, context: &mut Context<'_>) -> Result>> { 165 | let env = self.vm.get_env()?; 166 | let jfuture = JFuture::from_env(&env, self.internal.as_obj())?.into_future(); 167 | jfuture 168 | .poll_internal(context) 169 | .map(|result| result.map(|result| Ok(env.new_global_ref(result)?))) 170 | } 171 | } 172 | 173 | impl Future for JSendFuture { 174 | type Output = Result; 175 | 176 | fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll { 177 | match self.poll_internal(context) { 178 | Ok(result) => result, 179 | Err(err) => Poll::Ready(Err(err)), 180 | } 181 | } 182 | } 183 | 184 | assert_impl_all!(JSendFuture: Send); 185 | 186 | #[cfg(test)] 187 | mod test { 188 | use super::{JFuture, JSendFuture}; 189 | use crate::{task::JPollResult, test_utils}; 190 | use std::{ 191 | future::Future, 192 | pin::Pin, 193 | task::{Context, Poll}, 194 | }; 195 | 196 | #[test] 197 | fn test_jfuture() { 198 | use std::sync::Arc; 199 | 200 | test_utils::JVM_ENV.with(|env| { 201 | let data = Arc::new(test_utils::TestWakerData::new()); 202 | assert_eq!(Arc::strong_count(&data), 1); 203 | assert_eq!(data.value(), false); 204 | 205 | let waker = test_utils::test_waker(&data); 206 | assert_eq!(Arc::strong_count(&data), 2); 207 | assert_eq!(data.value(), false); 208 | 209 | let future_obj = env 210 | .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) 211 | .unwrap(); 212 | let mut future = JFuture::from_env(env, future_obj).unwrap().into_future(); 213 | 214 | assert!( 215 | Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)).is_pending() 216 | ); 217 | assert_eq!(Arc::strong_count(&data), 3); 218 | assert_eq!(data.value(), false); 219 | 220 | assert!( 221 | Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)).is_pending() 222 | ); 223 | assert_eq!(Arc::strong_count(&data), 3); 224 | assert_eq!(data.value(), false); 225 | 226 | let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 227 | env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) 228 | .unwrap(); 229 | assert_eq!(Arc::strong_count(&data), 2); 230 | assert_eq!(data.value(), true); 231 | 232 | let poll = Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)); 233 | if let Poll::Ready(result) = poll { 234 | assert!(env 235 | .is_same_object(result.unwrap().get().unwrap(), obj) 236 | .unwrap()); 237 | } else { 238 | panic!("Poll result should be ready"); 239 | } 240 | assert_eq!(Arc::strong_count(&data), 2); 241 | assert_eq!(data.value(), true); 242 | 243 | let poll = Future::poll(Pin::new(&mut future), &mut Context::from_waker(&waker)); 244 | if let Poll::Ready(result) = poll { 245 | assert!(env 246 | .is_same_object(result.unwrap().get().unwrap(), obj) 247 | .unwrap()); 248 | } else { 249 | panic!("Poll result should be ready"); 250 | } 251 | assert_eq!(Arc::strong_count(&data), 2); 252 | assert_eq!(data.value(), true); 253 | }); 254 | } 255 | 256 | #[test] 257 | fn test_jfuture_await() { 258 | use futures::{executor::block_on, join}; 259 | 260 | test_utils::JVM_ENV.with(|env| { 261 | let future_obj = env 262 | .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) 263 | .unwrap(); 264 | let future = JFuture::from_env(env, future_obj).unwrap(); 265 | let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 266 | 267 | block_on(async { 268 | join!( 269 | async { 270 | env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) 271 | .unwrap(); 272 | }, 273 | async { 274 | assert!(env 275 | .is_same_object(future.into_future().await.unwrap().get().unwrap(), obj) 276 | .unwrap()); 277 | } 278 | ); 279 | }); 280 | }); 281 | } 282 | 283 | #[test] 284 | fn test_jfuture_await_throw() { 285 | use futures::{executor::block_on, join}; 286 | 287 | test_utils::JVM_ENV.with(|env| { 288 | let future_obj = env 289 | .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) 290 | .unwrap(); 291 | let future = JFuture::from_env(env, future_obj).unwrap(); 292 | let ex = env.new_object("java/lang/Exception", "()V", &[]).unwrap(); 293 | 294 | block_on(async { 295 | join!( 296 | async { 297 | env.call_method( 298 | future_obj, 299 | "wakeWithThrowable", 300 | "(Ljava/lang/Throwable;)V", 301 | &[ex.into()], 302 | ) 303 | .unwrap(); 304 | }, 305 | async { 306 | future.into_future().await.unwrap().get().unwrap_err(); 307 | let future_ex = env.exception_occurred().unwrap(); 308 | env.exception_clear().unwrap(); 309 | let actual_ex = env 310 | .call_method(future_ex, "getCause", "()Ljava/lang/Throwable;", &[]) 311 | .unwrap() 312 | .l() 313 | .unwrap(); 314 | assert!(env.is_same_object(actual_ex, ex).unwrap()); 315 | } 316 | ); 317 | }); 318 | }); 319 | } 320 | 321 | #[test] 322 | fn test_jsendfuture_await() { 323 | use futures::{executor::block_on, join}; 324 | use std::convert::TryInto; 325 | 326 | test_utils::JVM_ENV.with(|env| { 327 | let future_obj = env 328 | .new_object("io/github/gedgygedgy/rust/future/SimpleFuture", "()V", &[]) 329 | .unwrap(); 330 | let future = JFuture::from_env(env, future_obj).unwrap(); 331 | let future: JSendFuture = future.try_into().unwrap(); 332 | let obj = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 333 | 334 | block_on(async { 335 | join!( 336 | async { 337 | env.call_method(future_obj, "wake", "(Ljava/lang/Object;)V", &[obj.into()]) 338 | .unwrap(); 339 | }, 340 | async { 341 | let global_ref = future.await.unwrap(); 342 | let jpoll = JPollResult::from_env(env, global_ref.as_obj()).unwrap(); 343 | assert!(env.is_same_object(jpoll.get().unwrap(), obj).unwrap()); 344 | } 345 | ); 346 | }); 347 | }); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /rust/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Extra Utilities for JNI in Rust 2 | //! 3 | //! This crate builds on top of the [`jni`](::jni) crate and provides 4 | //! higher-level concepts to more easily deal with JNI. While the 5 | //! [`jni`](::jni) crate implements low-level bindings to JNI, 6 | //! [`jni-utils`](crate) is more focused on higher-level constructs that get 7 | //! used frequently. Some of the features provided by [`jni-utils`](crate) 8 | //! include: 9 | //! 10 | //! * Asynchronous calls to Java code using the [`JFuture`](future::JFuture) 11 | //! and [`JStream`](stream::JStream) types 12 | //! * Conversion between various commonly-used Rust types and their 13 | //! corresponding Java types 14 | //! * Emulation of `try`/`catch` blocks with the 15 | //! [`try_block`](exceptions::try_block) function 16 | //! 17 | //! The overriding principle of [`jni-utils`](crate) is that switches between 18 | //! Rust and Java code should be minimized, and that it is easier to call Java 19 | //! code from Rust than it is to call Rust code from Java. Calling Rust from 20 | //! Java requires creating a class with a `native` method and exporting it from 21 | //! Rust, either by a combination of `#[nomangle]` and `extern "C"` to export 22 | //! the function as a symbol in a shared library, or by calling 23 | //! [`JNIEnv::register_native_methods()`](::jni::JNIEnv::register_native_methods). 24 | //! In contrast, calling Java from Rust only requires calling 25 | //! [`JNIEnv::call_method()`](::jni::JNIEnv::call_method) (though you can cache 26 | //! the method ID and use 27 | //! [`JNIEnv::call_method_unchecked()`](::jni::JNIEnv::call_method_unchecked) 28 | //! for a performance improvement.) 29 | //! 30 | //! To that end, [`jni-utils`](crate) seeks to minimize the number of holes 31 | //! that must be poked through the Rust-Java boundary, and the number of 32 | //! `native` exported-to-Java Rust functions that must be written. In 33 | //! particular, the async API has been developed to minimize such exports by 34 | //! allowing Java code to wake an `await` without creating a new `native` 35 | //! function. 36 | //! 37 | //! Some features of [`jni-utils`](crate) require the accompanying Java support 38 | //! library, which includes some native methods. Therefore, 39 | //! [`jni_utils::init()`](crate::init) should be called before using 40 | //! [`jni-utils`](crate). 41 | 42 | use ::jni::{errors::Result, JNIEnv}; 43 | 44 | pub mod arrays; 45 | pub mod classcache; 46 | pub mod exceptions; 47 | pub mod future; 48 | pub mod ops; 49 | pub mod stream; 50 | pub mod task; 51 | pub mod uuid; 52 | 53 | /// Initialize [`jni-utils`](crate) by registering required native methods. 54 | /// This should be called before using [`jni-utils`](crate). 55 | /// 56 | /// # Arguments 57 | /// 58 | /// * `env` - Java environment with which to register native methods. 59 | pub fn init(env: &JNIEnv) -> Result<()> { 60 | ops::jni::init(env)?; 61 | Ok(()) 62 | } 63 | 64 | #[cfg(test)] 65 | pub(crate) mod test_utils { 66 | use jni::{objects::GlobalRef, JNIEnv, JavaVM}; 67 | use lazy_static::lazy_static; 68 | use std::{ 69 | sync::{Arc, Mutex}, 70 | task::{Wake, Waker}, 71 | }; 72 | 73 | pub struct TestWakerData(Mutex); 74 | 75 | impl TestWakerData { 76 | pub fn new() -> Self { 77 | Self(Mutex::new(false)) 78 | } 79 | 80 | pub fn value(&self) -> bool { 81 | *self.0.lock().unwrap() 82 | } 83 | 84 | pub fn set_value(&self, value: bool) { 85 | let mut guard = self.0.lock().unwrap(); 86 | *guard = value; 87 | } 88 | } 89 | 90 | impl Wake for TestWakerData { 91 | fn wake(self: Arc) { 92 | Self::wake_by_ref(&self); 93 | } 94 | 95 | fn wake_by_ref(self: &Arc) { 96 | self.set_value(true); 97 | } 98 | } 99 | 100 | pub fn test_waker(data: &Arc) -> Waker { 101 | Waker::from(data.clone()) 102 | } 103 | 104 | struct GlobalJVM { 105 | jvm: JavaVM, 106 | class_loader: GlobalRef, 107 | } 108 | 109 | thread_local! { 110 | pub static JVM_ENV: JNIEnv<'static> = { 111 | let env = JVM.jvm.attach_current_thread_permanently().unwrap(); 112 | 113 | let thread = env 114 | .call_static_method( 115 | "java/lang/Thread", 116 | "currentThread", 117 | "()Ljava/lang/Thread;", 118 | &[], 119 | ) 120 | .unwrap() 121 | .l() 122 | .unwrap(); 123 | env.call_method( 124 | thread, 125 | "setContextClassLoader", 126 | "(Ljava/lang/ClassLoader;)V", 127 | &[JVM.class_loader.as_obj().into()] 128 | ).unwrap(); 129 | 130 | env 131 | } 132 | } 133 | 134 | lazy_static! { 135 | static ref JVM: GlobalJVM = { 136 | use jni::InitArgsBuilder; 137 | use std::{env, path::PathBuf}; 138 | 139 | let mut jni_utils_jar = PathBuf::from(env::current_exe().unwrap()); 140 | jni_utils_jar.pop(); 141 | jni_utils_jar.pop(); 142 | jni_utils_jar.push("java"); 143 | jni_utils_jar.push("libs"); 144 | jni_utils_jar.push("jni-utils-0.1.0-SNAPSHOT.jar"); 145 | 146 | let jvm_args = InitArgsBuilder::new() 147 | .option(&format!( 148 | "-Djava.class.path={}", 149 | jni_utils_jar.to_str().unwrap() 150 | )) 151 | .build() 152 | .unwrap(); 153 | let jvm = JavaVM::new(jvm_args).unwrap(); 154 | 155 | let env = jvm.attach_current_thread_permanently().unwrap(); 156 | crate::init(&env).unwrap(); 157 | 158 | let thread = env 159 | .call_static_method( 160 | "java/lang/Thread", 161 | "currentThread", 162 | "()Ljava/lang/Thread;", 163 | &[], 164 | ) 165 | .unwrap() 166 | .l() 167 | .unwrap(); 168 | let class_loader = env 169 | .call_method( 170 | thread, 171 | "getContextClassLoader", 172 | "()Ljava/lang/ClassLoader;", 173 | &[], 174 | ) 175 | .unwrap() 176 | .l() 177 | .unwrap(); 178 | let class_loader = env.new_global_ref(class_loader).unwrap(); 179 | 180 | GlobalJVM { jvm, class_loader } 181 | }; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /rust/ops.rs: -------------------------------------------------------------------------------- 1 | use ::jni::{errors::Result, objects::{JObject, JClass}, JNIEnv}; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | macro_rules! define_fn_adapter { 5 | ( 6 | fn_once: $fo:ident, 7 | fn_once_local: $fol:ident, 8 | fn_once_internal: $foi:ident, 9 | fn_mut: $fm:ident, 10 | fn_mut_local: $fml:ident, 11 | fn_mut_internal: $fmi:ident, 12 | fn: $f:ident, 13 | fn_local: $fl:ident, 14 | fn_internal: $fi:ident, 15 | impl_class: $ic:literal, 16 | doc_class: $dc:literal, 17 | doc_method: $dm:literal, 18 | doc_fn_once: $dfo:literal, 19 | doc_fn: $df:literal, 20 | doc_noop: $dnoop:literal, 21 | signature: $closure_name:ident: impl for<'c, 'd> Fn$args:tt -> $ret:ty, 22 | closure: $closure:expr, 23 | ) => { 24 | fn $foi<'a: 'b, 'b>( 25 | env: &'b JNIEnv<'a>, 26 | $closure_name: impl for<'c, 'd> FnOnce$args -> $ret + 'static, 27 | local: bool, 28 | ) -> Result> { 29 | let adapter = env.auto_local(fn_once_adapter(env, $closure, local)?); 30 | env.new_object( 31 | JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), 32 | "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", 33 | &[(&adapter).into()], 34 | ) 35 | } 36 | 37 | #[doc = "Create an `"] 38 | #[doc = $dc] 39 | #[doc = "` from a given [`FnOnce`]. The closure can later be called "] 40 | #[doc = "by calling the object's `"] 41 | #[doc = $dm] 42 | #[doc = "` method. The closure can be freed without calling it by "] 43 | #[doc = "calling the object's `close()` method."] 44 | #[doc = "\n\n"] 45 | #[doc = "If the closure panics, the unwind will be caught and thrown "] 46 | #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] 47 | #[doc = "\n\n"] 48 | #[doc = "It is safe to call the object's `"] 49 | #[doc = $dm] 50 | #[doc = "` method recursively, but the second call will "] 51 | #[doc = $dnoop] 52 | #[doc = "."] 53 | pub fn $fo<'a: 'b, 'b>( 54 | env: &'b JNIEnv<'a>, 55 | f: impl for<'c, 'd> FnOnce$args -> $ret + Send + 'static, 56 | ) -> Result> { 57 | $foi(env, f, false) 58 | } 59 | 60 | #[doc = "Create an `"] 61 | #[doc = $dc] 62 | #[doc = "` from a given [`FnOnce`] without checking if it is "] 63 | #[doc = "[`Send`]. Attempting to call `"] 64 | #[doc = $dm] 65 | #[doc = "` or `close()` on the resulting object from a thread other "] 66 | #[doc = "than its origin thread will result in an "] 67 | #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] 68 | #[doc = "being thrown."] 69 | pub fn $fol<'a: 'b, 'b>( 70 | env: &'b JNIEnv<'a>, 71 | f: impl for<'c, 'd> FnOnce$args -> $ret + 'static, 72 | ) -> Result> { 73 | $foi(env, f, true) 74 | } 75 | 76 | fn $fmi<'a: 'b, 'b>( 77 | env: &'b JNIEnv<'a>, 78 | mut $closure_name: impl for<'c, 'd> FnMut$args -> $ret + 'static, 79 | local: bool, 80 | ) -> Result> { 81 | let adapter = env.auto_local(fn_mut_adapter(env, $closure, local)?); 82 | env.new_object( 83 | JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), 84 | "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", 85 | &[(&adapter).into()], 86 | ) 87 | } 88 | 89 | #[doc = "Create an `"] 90 | #[doc = $dc] 91 | #[doc = "` from a given [`FnMut`]. The closure can later be called "] 92 | #[doc = "by calling the object's `"] 93 | #[doc = $dm] 94 | #[doc = "` method. The closure can be freed without calling it by "] 95 | #[doc = "calling the object's `close()` method."] 96 | #[doc = "\n\n"] 97 | #[doc = "If the closure panics, the unwind will be caught and thrown "] 98 | #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] 99 | #[doc = "\n\n"] 100 | #[doc = "Unlike [`"] 101 | #[doc = $df] 102 | #[doc = "`] and [`"] 103 | #[doc = $dfo] 104 | #[doc = "`], it is not safe to call the resulting object's `"] 105 | #[doc = $dm] 106 | #[doc = "` method recursively. The [`FnMut`] is managed with an "] 107 | #[doc = "internal [`Mutex`], so calling `"] 108 | #[doc = $dm] 109 | #[doc = "` recursively will result in a deadlock."] 110 | pub fn $fm<'a: 'b, 'b>( 111 | env: &'b JNIEnv<'a>, 112 | f: impl for<'c, 'd> FnMut$args -> $ret + Send + 'static, 113 | ) -> Result> { 114 | $fmi(env, f, false) 115 | } 116 | 117 | #[doc = "Create an `"] 118 | #[doc = $dc] 119 | #[doc = "` from a given [`FnMut`] without checking if it is "] 120 | #[doc = "[`Send`]. Attempting to call `"] 121 | #[doc = $dm] 122 | #[doc = "` or `close()` on the resulting object from a thread other "] 123 | #[doc = "than its origin thread will result in an "] 124 | #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] 125 | #[doc = "being thrown."] 126 | pub fn $fml<'a: 'b, 'b>( 127 | env: &'b JNIEnv<'a>, 128 | f: impl for<'c, 'd> FnMut$args -> $ret + 'static, 129 | ) -> Result> { 130 | $fmi(env, f, true) 131 | } 132 | 133 | fn $fi<'a: 'b, 'b>( 134 | env: &'b JNIEnv<'a>, 135 | $closure_name: impl for<'c, 'd> Fn$args -> $ret + 'static, 136 | local: bool, 137 | ) -> Result> { 138 | let adapter = env.auto_local(fn_adapter(env, $closure, local)?); 139 | env.new_object( 140 | JClass::from(crate::classcache::get_class($ic).unwrap().as_obj()), 141 | "(Lio/github/gedgygedgy/rust/ops/FnAdapter;)V", 142 | &[(&adapter).into()], 143 | ) 144 | } 145 | 146 | #[doc = "Create an `"] 147 | #[doc = $dc] 148 | #[doc = "` from a given [`Fn`]. The closure can later be called by "] 149 | #[doc = "calling the object's `"] 150 | #[doc = $dm] 151 | #[doc = "` method. The closure can be freed without calling it by "] 152 | #[doc = "calling the object's `close()` method."] 153 | #[doc = "\n\n"] 154 | #[doc = "If the closure panics, the unwind will be caught and thrown "] 155 | #[doc = "as an `io.github.gedgygedgy.rust.panic.PanicException`."] 156 | #[doc = "\n\n"] 157 | #[doc = "It is safe to call the object's `"] 158 | #[doc = $dm] 159 | #[doc = "` method recursively."] 160 | pub fn $f<'a: 'b, 'b>( 161 | env: &'b JNIEnv<'a>, 162 | f: impl for<'c, 'd> Fn$args -> $ret + Send + Sync + 'static, 163 | ) -> Result> { 164 | $fi(env, f, false) 165 | } 166 | 167 | #[doc = "Create an `"] 168 | #[doc = $dc] 169 | #[doc = "` from a given [`Fn`] without checking if it is [`Send`]. "] 170 | #[doc = "Attempting to call `"] 171 | #[doc = $dm] 172 | #[doc = "` or `close()` on the resulting object from a thread other "] 173 | #[doc = "than its origin thread will result in an "] 174 | #[doc = "`io.github.gedgygedgy.rust.thread.LocalThreadException` "] 175 | #[doc = "being thrown."] 176 | pub fn $fl<'a: 'b, 'b>( 177 | env: &'b JNIEnv<'a>, 178 | f: impl for<'c, 'd> Fn$args -> $ret + 'static, 179 | ) -> Result> { 180 | $fi(env, f, true) 181 | } 182 | }; 183 | } 184 | 185 | define_fn_adapter! { 186 | fn_once: fn_once_runnable, 187 | fn_once_local: fn_once_runnable_local, 188 | fn_once_internal: fn_once_runnable_internal, 189 | fn_mut: fn_mut_runnable, 190 | fn_mut_local: fn_mut_runnable_local, 191 | fn_mut_internal: fn_mut_runnable_internal, 192 | fn: fn_runnable, 193 | fn_local: fn_runnable_local, 194 | fn_internal: fn_runnable_internal, 195 | impl_class: "io/github/gedgygedgy/rust/ops/FnRunnableImpl", 196 | doc_class: "io.github.gedgygedgy.rust.ops.FnRunnable", 197 | doc_method: "run()", 198 | doc_fn_once: "fn_once_runnable", 199 | doc_fn: "fn_runnable", 200 | doc_noop: "be a no-op", 201 | signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>) -> (), 202 | closure: move |env, _obj1, obj2, _arg1, _arg2| { 203 | f(env, obj2); 204 | JObject::null() 205 | }, 206 | } 207 | 208 | define_fn_adapter! { 209 | fn_once: fn_once_bi_function, 210 | fn_once_local: fn_once_bi_function_local, 211 | fn_once_internal: fn_once_bi_function_internal, 212 | fn_mut: fn_mut_bi_function, 213 | fn_mut_local: fn_mut_bi_function_local, 214 | fn_mut_internal: fn_mut_bi_function_internal, 215 | fn: fn_bi_function, 216 | fn_local: fn_bi_function_local, 217 | fn_internal: fn_bi_function_internal, 218 | impl_class: "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl", 219 | doc_class: "io.github.gedgygedgy.rust.ops.FnBiFunction", 220 | doc_method: "apply()", 221 | doc_fn_once: "fn_once_bi_function", 222 | doc_fn: "fn_bi_funciton", 223 | doc_noop: "return `null`", 224 | signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, 225 | closure: move |env, _obj1, obj2, arg1, arg2| { 226 | f(env, obj2, arg1, arg2) 227 | }, 228 | } 229 | 230 | define_fn_adapter! { 231 | fn_once: fn_once_function, 232 | fn_once_local: fn_once_function_local, 233 | fn_once_internal: fn_once_function_internal, 234 | fn_mut: fn_mut_function, 235 | fn_mut_local: fn_mut_function_local, 236 | fn_mut_internal: fn_mut_function_internal, 237 | fn: fn_function, 238 | fn_local: fn_function_local, 239 | fn_internal: fn_function_internal, 240 | impl_class: "io/github/gedgygedgy/rust/ops/FnFunctionImpl", 241 | doc_class: "io.github.gedgygedgy.rust.ops.FnFunction", 242 | doc_method: "apply()", 243 | doc_fn_once: "fn_once_function", 244 | doc_fn: "fn_function", 245 | doc_noop: "return `null`", 246 | signature: f: impl for<'c, 'd> Fn(&'d JNIEnv<'c>, JObject<'c>, JObject<'c>) -> JObject<'c>, 247 | closure: move |env, _obj1, obj2, arg1, _arg2| { 248 | f(env, obj2, arg1) 249 | }, 250 | } 251 | 252 | struct SendSyncWrapper(T); 253 | 254 | unsafe impl Send for SendSyncWrapper {} 255 | unsafe impl Sync for SendSyncWrapper {} 256 | 257 | type FnWrapper = SendSyncWrapper< 258 | Arc< 259 | dyn for<'a, 'b> Fn( 260 | &'b JNIEnv<'a>, 261 | JObject<'a>, 262 | JObject<'a>, 263 | JObject<'a>, 264 | JObject<'a>, 265 | ) -> JObject<'a> 266 | + 'static, 267 | >, 268 | >; 269 | 270 | fn fn_once_adapter<'a: 'b, 'b>( 271 | env: &'b JNIEnv<'a>, 272 | f: impl for<'c, 'd> FnOnce( 273 | &'d JNIEnv<'c>, 274 | JObject<'c>, 275 | JObject<'c>, 276 | JObject<'c>, 277 | JObject<'c>, 278 | ) -> JObject<'c> 279 | + 'static, 280 | local: bool, 281 | ) -> Result> { 282 | let mutex = Mutex::new(Some(f)); 283 | fn_adapter( 284 | env, 285 | move |env, obj1, obj2, arg1, arg2| { 286 | let f = { 287 | let mut guard = mutex.lock().unwrap(); 288 | if let Some(f) = guard.take() { 289 | f 290 | } else { 291 | return JObject::null(); 292 | } 293 | }; 294 | f(env, obj1, obj2, arg1, arg2) 295 | }, 296 | local, 297 | ) 298 | } 299 | 300 | fn fn_mut_adapter<'a: 'b, 'b>( 301 | env: &'b JNIEnv<'a>, 302 | f: impl for<'c, 'd> FnMut( 303 | &'d JNIEnv<'c>, 304 | JObject<'c>, 305 | JObject<'c>, 306 | JObject<'c>, 307 | JObject<'c>, 308 | ) -> JObject<'c> 309 | + 'static, 310 | local: bool, 311 | ) -> Result> { 312 | let mutex = Mutex::new(f); 313 | fn_adapter( 314 | env, 315 | move |env, obj1, obj2, arg1, arg2| { 316 | let mut guard = mutex.lock().unwrap(); 317 | guard(env, obj1, obj2, arg1, arg2) 318 | }, 319 | local, 320 | ) 321 | } 322 | 323 | fn fn_adapter<'a: 'b, 'b>( 324 | env: &'b JNIEnv<'a>, 325 | f: impl for<'c, 'd> Fn( 326 | &'d JNIEnv<'c>, 327 | JObject<'c>, 328 | JObject<'c>, 329 | JObject<'c>, 330 | JObject<'c>, 331 | ) -> JObject<'c> 332 | + 'static, 333 | local: bool, 334 | ) -> Result> { 335 | let arc: Arc< 336 | dyn for<'c, 'd> Fn( 337 | &'d JNIEnv<'c>, 338 | JObject<'c>, 339 | JObject<'c>, 340 | JObject<'c>, 341 | JObject<'c>, 342 | ) -> JObject<'c>, 343 | > = Arc::from(f); 344 | 345 | let obj = env.new_object( 346 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/ops/FnAdapter").unwrap().as_obj()), 347 | "(Z)V", 348 | &[local.into()] 349 | )?; 350 | env.set_rust_field::<_, _, FnWrapper>(obj, "data", SendSyncWrapper(arc))?; 351 | Ok(obj) 352 | } 353 | 354 | pub(crate) mod jni { 355 | use super::FnWrapper; 356 | use jni::{errors::Result, objects::JObject, JNIEnv, NativeMethod}; 357 | 358 | extern "C" fn fn_adapter_call_internal<'a>( 359 | env: JNIEnv<'a>, 360 | obj1: JObject<'a>, 361 | obj2: JObject<'a>, 362 | arg1: JObject<'a>, 363 | arg2: JObject<'a>, 364 | ) -> JObject<'a> { 365 | use std::panic::AssertUnwindSafe; 366 | 367 | let arc = if let Ok(f) = env.get_rust_field::<_, _, FnWrapper>(obj1, "data") { 368 | AssertUnwindSafe(f.0.clone()) 369 | } else { 370 | return JObject::null(); 371 | }; 372 | crate::exceptions::throw_unwind(&env, || arc(&env, obj1, obj2, arg1, arg2)) 373 | .unwrap_or_else(|_| JObject::null()) 374 | } 375 | 376 | extern "C" fn fn_adapter_close_internal(env: JNIEnv, obj: JObject) { 377 | let _ = crate::exceptions::throw_unwind(&env, || { 378 | let _ = env.take_rust_field::<_, _, FnWrapper>(obj, "data"); 379 | }); 380 | } 381 | 382 | pub fn init(env: &JNIEnv) -> Result<()> { 383 | use std::ffi::c_void; 384 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/Future").unwrap(); 385 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/future/FutureException").unwrap(); 386 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnAdapter").unwrap(); 387 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/Stream").unwrap(); 388 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/stream/StreamPoll").unwrap(); 389 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/Waker").unwrap(); 390 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/task/PollResult").unwrap(); 391 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnRunnableImpl").unwrap(); 392 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnBiFunctionImpl").unwrap(); 393 | crate::classcache::find_add_class(env, "io/github/gedgygedgy/rust/ops/FnFunctionImpl").unwrap(); 394 | let class = env.auto_local(env.find_class("io/github/gedgygedgy/rust/ops/FnAdapter")?); 395 | env.register_native_methods( 396 | &class, 397 | &[ 398 | NativeMethod { 399 | name: "callInternal".into(), 400 | sig: 401 | "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" 402 | .into(), 403 | fn_ptr: fn_adapter_call_internal as *mut c_void, 404 | }, 405 | NativeMethod { 406 | name: "closeInternal".into(), 407 | sig: "()V".into(), 408 | fn_ptr: fn_adapter_close_internal as *mut c_void, 409 | }, 410 | ], 411 | )?; 412 | 413 | Ok(()) 414 | } 415 | } 416 | 417 | #[cfg(test)] 418 | mod test { 419 | use crate::{exceptions::try_block, test_utils}; 420 | use jni::{objects::JObject, JNIEnv}; 421 | use std::{ 422 | cell::RefCell, 423 | rc::Rc, 424 | sync::{Arc, Mutex}, 425 | }; 426 | 427 | fn create_test_fn() -> ( 428 | Arc>, 429 | Box Fn(&'b JNIEnv<'a>, JObject<'a>) + Send + Sync + 'static>, 430 | ) { 431 | let arc = Arc::new(Mutex::new(0)); 432 | let arc2 = arc.clone(); 433 | ( 434 | arc, 435 | Box::new(move |_e, _o| { 436 | let mut guard = arc2.lock().unwrap(); 437 | *&mut *guard += 1; 438 | }), 439 | ) 440 | } 441 | 442 | fn create_test_fn_local() -> ( 443 | Rc>, 444 | Box Fn(&'b JNIEnv<'a>, JObject<'a>) + 'static>, 445 | ) { 446 | let rc = Rc::new(RefCell::new(0)); 447 | let rc2 = rc.clone(); 448 | ( 449 | rc, 450 | Box::new(move |_e, _o| { 451 | let mut guard = rc2.try_borrow_mut().unwrap(); 452 | *&mut *guard += 1; 453 | }), 454 | ) 455 | } 456 | 457 | fn test_data(data: &Arc>, expected: u32, expected_refcount: usize) { 458 | assert_eq!(Arc::strong_count(data), expected_refcount); 459 | let guard = data.lock().unwrap(); 460 | assert_eq!(*guard, expected); 461 | } 462 | 463 | fn test_data_local(data: &Rc>, expected: u32, expected_refcount: usize) { 464 | assert_eq!(Rc::strong_count(data), expected_refcount); 465 | let guard = data.try_borrow().unwrap(); 466 | assert_eq!(*guard, expected); 467 | } 468 | 469 | struct DropPanic; 470 | 471 | impl DropPanic { 472 | pub fn keep_alive(&self) {} 473 | } 474 | 475 | impl Drop for DropPanic { 476 | fn drop(&mut self) { 477 | panic!("DropPanic dropped"); 478 | } 479 | } 480 | 481 | fn create_drop_panic_fn( 482 | ) -> Box Fn(&'b JNIEnv<'a>, JObject<'a>) + Send + Sync + 'static> { 483 | let p = DropPanic; 484 | Box::new(move |_e, _o| { 485 | p.keep_alive(); 486 | }) 487 | } 488 | 489 | #[test] 490 | fn test_drop_panic() { 491 | test_utils::JVM_ENV.with(|env| { 492 | use std::{ 493 | mem::drop, 494 | panic::{catch_unwind, AssertUnwindSafe}, 495 | }; 496 | 497 | let dp = AssertUnwindSafe(create_drop_panic_fn()); 498 | dp(env, JObject::null()); 499 | catch_unwind(|| drop(dp)).unwrap_err(); 500 | }); 501 | } 502 | 503 | #[test] 504 | fn test_fn_once_runnable_run() { 505 | test_utils::JVM_ENV.with(|env| { 506 | let (data, f) = create_test_fn(); 507 | test_data(&data, 0, 2); 508 | 509 | let runnable = super::fn_once_runnable(env, f).unwrap(); 510 | test_data(&data, 0, 2); 511 | 512 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 513 | test_data(&data, 1, 1); 514 | 515 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 516 | test_data(&data, 1, 1); 517 | }); 518 | } 519 | 520 | #[test] 521 | fn test_fn_once_runnable_close() { 522 | test_utils::JVM_ENV.with(|env| { 523 | let (data, f) = create_test_fn(); 524 | test_data(&data, 0, 2); 525 | 526 | let runnable = super::fn_once_runnable(env, f).unwrap(); 527 | test_data(&data, 0, 2); 528 | 529 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 530 | test_data(&data, 0, 1); 531 | 532 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 533 | test_data(&data, 0, 1); 534 | }); 535 | } 536 | 537 | #[test] 538 | fn test_fn_once_runnable_run_close() { 539 | test_utils::JVM_ENV.with(|env| { 540 | let (data, f) = create_test_fn(); 541 | test_data(&data, 0, 2); 542 | 543 | let runnable = super::fn_once_runnable(env, f).unwrap(); 544 | test_data(&data, 0, 2); 545 | 546 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 547 | test_data(&data, 1, 1); 548 | 549 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 550 | test_data(&data, 1, 1); 551 | }); 552 | } 553 | 554 | #[test] 555 | fn test_fn_once_runnable_close_run() { 556 | test_utils::JVM_ENV.with(|env| { 557 | let (data, f) = create_test_fn(); 558 | test_data(&data, 0, 2); 559 | 560 | let runnable = super::fn_once_runnable(env, f).unwrap(); 561 | test_data(&data, 0, 2); 562 | 563 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 564 | test_data(&data, 0, 1); 565 | 566 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 567 | test_data(&data, 0, 1); 568 | }); 569 | } 570 | 571 | #[test] 572 | fn test_fn_once_runnable_thread() { 573 | test_utils::JVM_ENV.with(|env| { 574 | let (data, f) = create_test_fn(); 575 | test_data(&data, 0, 2); 576 | 577 | let runnable = super::fn_once_runnable(env, f).unwrap(); 578 | test_data(&data, 0, 2); 579 | 580 | let runnable = env.new_global_ref(runnable).unwrap(); 581 | let thread = std::thread::spawn(move || { 582 | test_utils::JVM_ENV.with(|env| { 583 | env.call_method(runnable.as_obj(), "run", "()V", &[]) 584 | .unwrap(); 585 | }); 586 | }); 587 | thread.join().unwrap(); 588 | test_data(&data, 1, 1); 589 | }); 590 | } 591 | 592 | #[test] 593 | fn test_fn_once_runnable_object() { 594 | test_utils::JVM_ENV.with(|env| { 595 | let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); 596 | let obj_ref_2 = obj_ref.clone(); 597 | let runnable = super::fn_once_runnable(env, move |e, o| { 598 | let guard = obj_ref_2.lock().unwrap(); 599 | assert!(e.is_same_object(guard.as_obj(), o).unwrap()); 600 | }) 601 | .unwrap(); 602 | 603 | { 604 | let mut guard = obj_ref.lock().unwrap(); 605 | *guard = env.new_global_ref(runnable).unwrap(); 606 | } 607 | 608 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 609 | }); 610 | } 611 | 612 | #[test] 613 | fn test_fn_once_runnable_recursive() { 614 | test_utils::JVM_ENV.with(|env| { 615 | let arc = Arc::new(Mutex::new(false)); 616 | let arc2 = arc.clone(); 617 | 618 | let runnable = super::fn_once_runnable(env, move |env, obj| { 619 | let value = { 620 | let mut guard = arc2.lock().unwrap(); 621 | let old = *guard; 622 | *guard = true; 623 | old 624 | }; 625 | if !value { 626 | env.call_method(obj, "run", "()V", &[]).unwrap(); 627 | } 628 | }) 629 | .unwrap(); 630 | 631 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 632 | 633 | let guard = arc.lock().unwrap(); 634 | assert!(*guard); 635 | }) 636 | } 637 | 638 | #[test] 639 | fn test_fn_once_runnable_panic() { 640 | test_utils::JVM_ENV.with(|env| { 641 | let runnable = 642 | super::fn_once_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); 643 | let result = try_block(env, || { 644 | env.call_method(runnable, "run", "()V", &[])?; 645 | Ok(false) 646 | }) 647 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 648 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 649 | let any = ex.take().unwrap(); 650 | let str = any.downcast::<&str>().unwrap(); 651 | assert_eq!(*str, "This is a panic"); 652 | Ok(true) 653 | }) 654 | .result() 655 | .unwrap(); 656 | assert!(result); 657 | }); 658 | } 659 | 660 | #[test] 661 | fn test_fn_once_runnable_close_self() { 662 | test_utils::JVM_ENV.with(|env| { 663 | let (data, f) = create_test_fn(); 664 | test_data(&data, 0, 2); 665 | 666 | let runnable = super::fn_once_runnable(env, move |env, obj| { 667 | env.call_method(obj, "close", "()V", &[]).unwrap(); 668 | f(env, obj); 669 | }) 670 | .unwrap(); 671 | test_data(&data, 0, 2); 672 | 673 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 674 | test_data(&data, 1, 1); 675 | 676 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 677 | test_data(&data, 1, 1); 678 | }); 679 | } 680 | 681 | #[test] 682 | fn test_fn_once_runnable_drop_panic() { 683 | test_utils::JVM_ENV.with(|env| { 684 | let dp = create_drop_panic_fn(); 685 | let runnable = super::fn_once_runnable(env, dp).unwrap(); 686 | 687 | let result = try_block(env, || { 688 | env.call_method(runnable, "close", "()V", &[])?; 689 | Ok(false) 690 | }) 691 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 692 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 693 | let any = ex.take().unwrap(); 694 | let msg = any.downcast::<&str>().unwrap(); 695 | assert_eq!(*msg, "DropPanic dropped"); 696 | Ok(true) 697 | }) 698 | .result() 699 | .unwrap(); 700 | assert!(result); 701 | }); 702 | } 703 | 704 | #[test] 705 | fn test_fn_once_runnable_local_run() { 706 | test_utils::JVM_ENV.with(|env| { 707 | let (data, f) = create_test_fn_local(); 708 | test_data_local(&data, 0, 2); 709 | 710 | let runnable = super::fn_once_runnable_local(env, f).unwrap(); 711 | test_data_local(&data, 0, 2); 712 | 713 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 714 | test_data_local(&data, 1, 1); 715 | 716 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 717 | test_data_local(&data, 1, 1); 718 | }); 719 | } 720 | 721 | #[test] 722 | fn test_fn_once_runnable_local_thread() { 723 | test_utils::JVM_ENV.with(|env| { 724 | let (data, f) = create_test_fn_local(); 725 | test_data_local(&data, 0, 2); 726 | 727 | let runnable = super::fn_once_runnable_local(env, f).unwrap(); 728 | let runnable = env.new_global_ref(runnable).unwrap(); 729 | test_data_local(&data, 0, 2); 730 | 731 | let thread = std::thread::spawn(move || { 732 | test_utils::JVM_ENV.with(|env| { 733 | let value = crate::exceptions::try_block(env, || { 734 | env.call_method(runnable.as_obj(), "run", "()V", &[])?; 735 | Ok(false) 736 | }) 737 | .catch( 738 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 739 | |_ex| Ok(true), 740 | ) 741 | .result() 742 | .unwrap(); 743 | assert!(value); 744 | 745 | let value = crate::exceptions::try_block(env, || { 746 | env.call_method(runnable.as_obj(), "close", "()V", &[])?; 747 | Ok(false) 748 | }) 749 | .catch( 750 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 751 | |_ex| Ok(true), 752 | ) 753 | .result() 754 | .unwrap(); 755 | assert!(value); 756 | }); 757 | }); 758 | thread.join().unwrap(); 759 | test_data_local(&data, 0, 2); 760 | }); 761 | } 762 | 763 | #[test] 764 | fn test_fn_mut_runnable_run() { 765 | test_utils::JVM_ENV.with(|env| { 766 | let (data, f) = create_test_fn(); 767 | test_data(&data, 0, 2); 768 | 769 | let runnable = super::fn_mut_runnable(env, f).unwrap(); 770 | test_data(&data, 0, 2); 771 | 772 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 773 | test_data(&data, 1, 2); 774 | 775 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 776 | test_data(&data, 2, 2); 777 | }); 778 | } 779 | 780 | #[test] 781 | fn test_fn_mut_runnable_close() { 782 | test_utils::JVM_ENV.with(|env| { 783 | let (data, f) = create_test_fn(); 784 | test_data(&data, 0, 2); 785 | 786 | let runnable = super::fn_mut_runnable(env, f).unwrap(); 787 | test_data(&data, 0, 2); 788 | 789 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 790 | test_data(&data, 0, 1); 791 | 792 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 793 | test_data(&data, 0, 1); 794 | }); 795 | } 796 | 797 | #[test] 798 | fn test_fn_mut_runnable_run_close() { 799 | test_utils::JVM_ENV.with(|env| { 800 | let (data, f) = create_test_fn(); 801 | test_data(&data, 0, 2); 802 | 803 | let runnable = super::fn_mut_runnable(env, f).unwrap(); 804 | test_data(&data, 0, 2); 805 | 806 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 807 | test_data(&data, 1, 2); 808 | 809 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 810 | test_data(&data, 1, 1); 811 | }); 812 | } 813 | 814 | #[test] 815 | fn test_fn_mut_runnable_close_run() { 816 | test_utils::JVM_ENV.with(|env| { 817 | let (data, f) = create_test_fn(); 818 | test_data(&data, 0, 2); 819 | 820 | let runnable = super::fn_mut_runnable(env, f).unwrap(); 821 | test_data(&data, 0, 2); 822 | 823 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 824 | test_data(&data, 0, 1); 825 | 826 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 827 | test_data(&data, 0, 1); 828 | }); 829 | } 830 | 831 | #[test] 832 | fn test_fn_mut_runnable_thread() { 833 | test_utils::JVM_ENV.with(|env| { 834 | let (data, f) = create_test_fn(); 835 | test_data(&data, 0, 2); 836 | 837 | let runnable = super::fn_mut_runnable(env, f).unwrap(); 838 | test_data(&data, 0, 2); 839 | 840 | let runnable = env.new_global_ref(runnable).unwrap(); 841 | let thread = std::thread::spawn(move || { 842 | test_utils::JVM_ENV.with(|env| { 843 | env.call_method(runnable.as_obj(), "run", "()V", &[]) 844 | .unwrap(); 845 | }); 846 | }); 847 | thread.join().unwrap(); 848 | test_data(&data, 1, 2); 849 | }); 850 | } 851 | 852 | #[test] 853 | fn test_fn_mut_runnable_object() { 854 | test_utils::JVM_ENV.with(|env| { 855 | let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); 856 | let obj_ref_2 = obj_ref.clone(); 857 | let runnable = super::fn_mut_runnable(env, move |e, o| { 858 | let guard = obj_ref_2.lock().unwrap(); 859 | assert!(e.is_same_object(guard.as_obj(), o).unwrap()); 860 | }) 861 | .unwrap(); 862 | 863 | { 864 | let mut guard = obj_ref.lock().unwrap(); 865 | *guard = env.new_global_ref(runnable).unwrap(); 866 | } 867 | 868 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 869 | }); 870 | } 871 | 872 | #[test] 873 | fn test_fn_mut_runnable_panic() { 874 | test_utils::JVM_ENV.with(|env| { 875 | let runnable = super::fn_mut_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); 876 | let result = try_block(env, || { 877 | env.call_method(runnable, "run", "()V", &[])?; 878 | Ok(false) 879 | }) 880 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 881 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 882 | let any = ex.take().unwrap(); 883 | let str = any.downcast::<&str>().unwrap(); 884 | assert_eq!(*str, "This is a panic"); 885 | Ok(true) 886 | }) 887 | .result() 888 | .unwrap(); 889 | assert!(result); 890 | }); 891 | } 892 | 893 | #[test] 894 | fn test_fn_mut_runnable_close_self() { 895 | test_utils::JVM_ENV.with(|env| { 896 | let (data, f) = create_test_fn(); 897 | test_data(&data, 0, 2); 898 | 899 | let runnable = super::fn_mut_runnable(env, move |env, obj| { 900 | env.call_method(obj, "close", "()V", &[]).unwrap(); 901 | f(env, obj); 902 | }) 903 | .unwrap(); 904 | test_data(&data, 0, 2); 905 | 906 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 907 | test_data(&data, 1, 1); 908 | 909 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 910 | test_data(&data, 1, 1); 911 | }); 912 | } 913 | 914 | #[test] 915 | fn test_fn_mut_runnable_drop_panic() { 916 | test_utils::JVM_ENV.with(|env| { 917 | let dp = create_drop_panic_fn(); 918 | let runnable = super::fn_mut_runnable(env, dp).unwrap(); 919 | 920 | let result = try_block(env, || { 921 | env.call_method(runnable, "close", "()V", &[])?; 922 | Ok(false) 923 | }) 924 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 925 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 926 | let any = ex.take().unwrap(); 927 | let msg = any.downcast::<&str>().unwrap(); 928 | assert_eq!(*msg, "DropPanic dropped"); 929 | Ok(true) 930 | }) 931 | .result() 932 | .unwrap(); 933 | assert!(result); 934 | }); 935 | } 936 | 937 | #[test] 938 | fn test_fn_mut_runnable_local_run() { 939 | test_utils::JVM_ENV.with(|env| { 940 | let (data, f) = create_test_fn_local(); 941 | test_data_local(&data, 0, 2); 942 | 943 | let runnable = super::fn_mut_runnable_local(env, f).unwrap(); 944 | test_data_local(&data, 0, 2); 945 | 946 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 947 | test_data_local(&data, 1, 2); 948 | 949 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 950 | test_data_local(&data, 2, 2); 951 | }); 952 | } 953 | 954 | #[test] 955 | fn test_fn_mut_runnable_local_thread() { 956 | test_utils::JVM_ENV.with(|env| { 957 | let (data, f) = create_test_fn_local(); 958 | test_data_local(&data, 0, 2); 959 | 960 | let runnable = super::fn_mut_runnable_local(env, f).unwrap(); 961 | let runnable = env.new_global_ref(runnable).unwrap(); 962 | test_data_local(&data, 0, 2); 963 | 964 | let thread = std::thread::spawn(move || { 965 | test_utils::JVM_ENV.with(|env| { 966 | let value = crate::exceptions::try_block(env, || { 967 | env.call_method(runnable.as_obj(), "run", "()V", &[])?; 968 | Ok(false) 969 | }) 970 | .catch( 971 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 972 | |_ex| Ok(true), 973 | ) 974 | .result() 975 | .unwrap(); 976 | assert!(value); 977 | 978 | let value = crate::exceptions::try_block(env, || { 979 | env.call_method(runnable.as_obj(), "close", "()V", &[])?; 980 | Ok(false) 981 | }) 982 | .catch( 983 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 984 | |_ex| Ok(true), 985 | ) 986 | .result() 987 | .unwrap(); 988 | assert!(value); 989 | }); 990 | }); 991 | thread.join().unwrap(); 992 | test_data_local(&data, 0, 2); 993 | }); 994 | } 995 | 996 | #[test] 997 | fn test_fn_runnable_run() { 998 | test_utils::JVM_ENV.with(|env| { 999 | let (data, f) = create_test_fn(); 1000 | test_data(&data, 0, 2); 1001 | 1002 | let runnable = super::fn_runnable(env, f).unwrap(); 1003 | test_data(&data, 0, 2); 1004 | 1005 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1006 | test_data(&data, 1, 2); 1007 | 1008 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1009 | test_data(&data, 2, 2); 1010 | }); 1011 | } 1012 | 1013 | #[test] 1014 | fn test_fn_runnable_close() { 1015 | test_utils::JVM_ENV.with(|env| { 1016 | let (data, f) = create_test_fn(); 1017 | test_data(&data, 0, 2); 1018 | 1019 | let runnable = super::fn_runnable(env, f).unwrap(); 1020 | test_data(&data, 0, 2); 1021 | 1022 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 1023 | test_data(&data, 0, 1); 1024 | 1025 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 1026 | test_data(&data, 0, 1); 1027 | }); 1028 | } 1029 | 1030 | #[test] 1031 | fn test_fn_runnable_run_close() { 1032 | test_utils::JVM_ENV.with(|env| { 1033 | let (data, f) = create_test_fn(); 1034 | test_data(&data, 0, 2); 1035 | 1036 | let runnable = super::fn_runnable(env, f).unwrap(); 1037 | test_data(&data, 0, 2); 1038 | 1039 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1040 | test_data(&data, 1, 2); 1041 | 1042 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 1043 | test_data(&data, 1, 1); 1044 | }); 1045 | } 1046 | 1047 | #[test] 1048 | fn test_fn_runnable_close_run() { 1049 | test_utils::JVM_ENV.with(|env| { 1050 | let (data, f) = create_test_fn(); 1051 | test_data(&data, 0, 2); 1052 | 1053 | let runnable = super::fn_runnable(env, f).unwrap(); 1054 | test_data(&data, 0, 2); 1055 | 1056 | env.call_method(runnable, "close", "()V", &[]).unwrap(); 1057 | test_data(&data, 0, 1); 1058 | 1059 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1060 | test_data(&data, 0, 1); 1061 | }); 1062 | } 1063 | 1064 | #[test] 1065 | fn test_fn_runnable_thread() { 1066 | test_utils::JVM_ENV.with(|env| { 1067 | let (data, f) = create_test_fn(); 1068 | test_data(&data, 0, 2); 1069 | 1070 | let runnable = super::fn_runnable(env, f).unwrap(); 1071 | test_data(&data, 0, 2); 1072 | 1073 | let runnable = env.new_global_ref(runnable).unwrap(); 1074 | let thread = std::thread::spawn(move || { 1075 | test_utils::JVM_ENV.with(|env| { 1076 | env.call_method(runnable.as_obj(), "run", "()V", &[]) 1077 | .unwrap(); 1078 | }); 1079 | }); 1080 | thread.join().unwrap(); 1081 | test_data(&data, 1, 2); 1082 | }); 1083 | } 1084 | 1085 | #[test] 1086 | fn test_fn_runnable_object() { 1087 | test_utils::JVM_ENV.with(|env| { 1088 | let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); 1089 | let obj_ref_2 = obj_ref.clone(); 1090 | let runnable = super::fn_runnable(env, move |e, o| { 1091 | let guard = obj_ref_2.lock().unwrap(); 1092 | assert!(e.is_same_object(guard.as_obj(), o).unwrap()); 1093 | }) 1094 | .unwrap(); 1095 | 1096 | { 1097 | let mut guard = obj_ref.lock().unwrap(); 1098 | *guard = env.new_global_ref(runnable).unwrap(); 1099 | } 1100 | 1101 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1102 | }); 1103 | } 1104 | 1105 | #[test] 1106 | fn test_fn_runnable_recursive() { 1107 | test_utils::JVM_ENV.with(|env| { 1108 | let arc = Arc::new(Mutex::new(false)); 1109 | let arc2 = arc.clone(); 1110 | 1111 | let calling = Mutex::new(false); 1112 | 1113 | let runnable = super::fn_runnable(env, move |env, obj| { 1114 | let calling_value = { 1115 | let mut guard = calling.lock().unwrap(); 1116 | let old = *guard; 1117 | *guard = true; 1118 | old 1119 | }; 1120 | if !calling_value { 1121 | env.call_method(obj, "run", "()V", &[]).unwrap(); 1122 | let mut guard = calling.lock().unwrap(); 1123 | *guard = false; 1124 | } else { 1125 | let mut guard = arc2.lock().unwrap(); 1126 | *guard = true; 1127 | } 1128 | }) 1129 | .unwrap(); 1130 | 1131 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1132 | 1133 | let guard = arc.lock().unwrap(); 1134 | assert!(*guard); 1135 | }) 1136 | } 1137 | 1138 | #[test] 1139 | fn test_fn_runnable_panic() { 1140 | test_utils::JVM_ENV.with(|env| { 1141 | let runnable = super::fn_runnable(env, |_e, _o| panic!("This is a panic")).unwrap(); 1142 | let result = try_block(env, || { 1143 | env.call_method(runnable, "run", "()V", &[])?; 1144 | Ok(false) 1145 | }) 1146 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 1147 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 1148 | let any = ex.take().unwrap(); 1149 | let str = any.downcast::<&str>().unwrap(); 1150 | assert_eq!(*str, "This is a panic"); 1151 | Ok(true) 1152 | }) 1153 | .result() 1154 | .unwrap(); 1155 | assert!(result); 1156 | }); 1157 | } 1158 | 1159 | #[test] 1160 | fn test_fn_runnable_close_self() { 1161 | test_utils::JVM_ENV.with(|env| { 1162 | let (data, f) = create_test_fn(); 1163 | test_data(&data, 0, 2); 1164 | 1165 | let runnable = super::fn_runnable(env, move |env, obj| { 1166 | env.call_method(obj, "close", "()V", &[]).unwrap(); 1167 | f(env, obj); 1168 | }) 1169 | .unwrap(); 1170 | test_data(&data, 0, 2); 1171 | 1172 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1173 | test_data(&data, 1, 1); 1174 | 1175 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1176 | test_data(&data, 1, 1); 1177 | }); 1178 | } 1179 | 1180 | #[test] 1181 | fn test_fn_runnable_drop_panic() { 1182 | test_utils::JVM_ENV.with(|env| { 1183 | let dp = create_drop_panic_fn(); 1184 | let runnable = super::fn_runnable(env, dp).unwrap(); 1185 | 1186 | let result = try_block(env, || { 1187 | env.call_method(runnable, "close", "()V", &[])?; 1188 | Ok(false) 1189 | }) 1190 | .catch("io/github/gedgygedgy/rust/panic/PanicException", |ex| { 1191 | let ex = crate::exceptions::JPanicException::from_env(env, ex).unwrap(); 1192 | let any = ex.take().unwrap(); 1193 | let msg = any.downcast::<&str>().unwrap(); 1194 | assert_eq!(*msg, "DropPanic dropped"); 1195 | Ok(true) 1196 | }) 1197 | .result() 1198 | .unwrap(); 1199 | assert!(result); 1200 | }); 1201 | } 1202 | 1203 | #[test] 1204 | fn test_fn_runnable_local_run() { 1205 | test_utils::JVM_ENV.with(|env| { 1206 | let (data, f) = create_test_fn_local(); 1207 | test_data_local(&data, 0, 2); 1208 | 1209 | let runnable = super::fn_runnable_local(env, f).unwrap(); 1210 | test_data_local(&data, 0, 2); 1211 | 1212 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1213 | test_data_local(&data, 1, 2); 1214 | 1215 | env.call_method(runnable, "run", "()V", &[]).unwrap(); 1216 | test_data_local(&data, 2, 2); 1217 | }); 1218 | } 1219 | 1220 | #[test] 1221 | fn test_fn_runnable_local_thread() { 1222 | test_utils::JVM_ENV.with(|env| { 1223 | let (data, f) = create_test_fn_local(); 1224 | test_data_local(&data, 0, 2); 1225 | 1226 | let runnable = super::fn_runnable_local(env, f).unwrap(); 1227 | let runnable = env.new_global_ref(runnable).unwrap(); 1228 | test_data_local(&data, 0, 2); 1229 | 1230 | let thread = std::thread::spawn(move || { 1231 | test_utils::JVM_ENV.with(|env| { 1232 | let value = crate::exceptions::try_block(env, || { 1233 | env.call_method(runnable.as_obj(), "run", "()V", &[])?; 1234 | Ok(false) 1235 | }) 1236 | .catch( 1237 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 1238 | |_ex| Ok(true), 1239 | ) 1240 | .result() 1241 | .unwrap(); 1242 | assert!(value); 1243 | 1244 | let value = crate::exceptions::try_block(env, || { 1245 | env.call_method(runnable.as_obj(), "close", "()V", &[])?; 1246 | Ok(false) 1247 | }) 1248 | .catch( 1249 | "io/github/gedgygedgy/rust/thread/LocalThreadException", 1250 | |_ex| Ok(true), 1251 | ) 1252 | .result() 1253 | .unwrap(); 1254 | assert!(value); 1255 | }); 1256 | }); 1257 | thread.join().unwrap(); 1258 | test_data_local(&data, 0, 2); 1259 | }); 1260 | } 1261 | 1262 | #[test] 1263 | fn test_fn_bi_function_object() { 1264 | test_utils::JVM_ENV.with(|env| { 1265 | let arg1 = env 1266 | .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) 1267 | .unwrap(); 1268 | let arg2 = env 1269 | .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) 1270 | .unwrap(); 1271 | let ret = env 1272 | .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) 1273 | .unwrap(); 1274 | let arg1_clone = arg1.clone(); 1275 | let arg2_clone = arg2.clone(); 1276 | let ret_clone = ret.clone(); 1277 | 1278 | let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); 1279 | let obj_ref_2 = obj_ref.clone(); 1280 | let bi_function = super::fn_bi_function(env, move |e, o, a1, a2| { 1281 | let guard = obj_ref_2.lock().unwrap(); 1282 | assert!(e.is_same_object(guard.as_obj(), o).unwrap()); 1283 | assert!(e.is_same_object(arg1_clone.as_obj(), a1).unwrap()); 1284 | assert!(e.is_same_object(arg2_clone.as_obj(), a2).unwrap()); 1285 | ret_clone.as_obj().into_inner().into() 1286 | }) 1287 | .unwrap(); 1288 | 1289 | { 1290 | let mut guard = obj_ref.lock().unwrap(); 1291 | *guard = env.new_global_ref(bi_function).unwrap(); 1292 | } 1293 | 1294 | let actual_ret = env 1295 | .call_method( 1296 | bi_function, 1297 | "apply", 1298 | "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", 1299 | &[arg1.as_obj().into(), arg2.as_obj().into()], 1300 | ) 1301 | .unwrap() 1302 | .l() 1303 | .unwrap(); 1304 | assert!(env.is_same_object(ret.as_obj(), actual_ret).unwrap()); 1305 | }); 1306 | } 1307 | 1308 | #[test] 1309 | fn test_fn_function_object() { 1310 | test_utils::JVM_ENV.with(|env| { 1311 | let arg = env 1312 | .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) 1313 | .unwrap(); 1314 | let ret = env 1315 | .new_global_ref(env.new_object("java/lang/Object", "()V", &[]).unwrap()) 1316 | .unwrap(); 1317 | let arg_clone = arg.clone(); 1318 | let ret_clone = ret.clone(); 1319 | 1320 | let obj_ref = Arc::new(Mutex::new(env.new_global_ref(JObject::null()).unwrap())); 1321 | let obj_ref_2 = obj_ref.clone(); 1322 | let function = super::fn_function(env, move |e, o, a| { 1323 | let guard = obj_ref_2.lock().unwrap(); 1324 | assert!(e.is_same_object(guard.as_obj(), o).unwrap()); 1325 | assert!(e.is_same_object(arg_clone.as_obj(), a).unwrap()); 1326 | ret_clone.as_obj().into_inner().into() 1327 | }) 1328 | .unwrap(); 1329 | 1330 | { 1331 | let mut guard = obj_ref.lock().unwrap(); 1332 | *guard = env.new_global_ref(function).unwrap(); 1333 | } 1334 | 1335 | let actual_ret = env 1336 | .call_method( 1337 | function, 1338 | "apply", 1339 | "(Ljava/lang/Object;)Ljava/lang/Object;", 1340 | &[arg.as_obj().into()], 1341 | ) 1342 | .unwrap() 1343 | .l() 1344 | .unwrap(); 1345 | assert!(env.is_same_object(ret.as_obj(), actual_ret).unwrap()); 1346 | }); 1347 | } 1348 | } 1349 | -------------------------------------------------------------------------------- /rust/stream.rs: -------------------------------------------------------------------------------- 1 | use crate::task::JPollResult; 2 | use ::jni::{ 3 | errors::{Error, Result}, 4 | objects::{GlobalRef, JMethodID, JObject, JClass}, 5 | signature::JavaType, 6 | JNIEnv, JavaVM, 7 | }; 8 | use futures::stream::Stream; 9 | use static_assertions::assert_impl_all; 10 | use std::{ 11 | convert::TryFrom, 12 | pin::Pin, 13 | task::{Context, Poll}, 14 | }; 15 | 16 | /// Wrapper for [`JObject`]s that implement 17 | /// `io.github.gedgygedgy.rust.stream.Stream`. Implements 18 | /// [`Stream`](futures::stream::Stream) to allow asynchronous Rust code to wait 19 | /// for items from Java code. 20 | /// 21 | /// Looks up the class and method IDs on creation rather than for every method 22 | /// call. 23 | /// 24 | /// For a [`Send`] version of this, use [`JSendStream`]. 25 | pub struct JStream<'a: 'b, 'b> { 26 | internal: JObject<'a>, 27 | poll_next: JMethodID<'a>, 28 | env: &'b JNIEnv<'a>, 29 | } 30 | 31 | impl<'a: 'b, 'b> JStream<'a, 'b> { 32 | /// Create a [`JStream`] from the environment and an object. This looks 33 | /// up the necessary class and method IDs to call all of the methods on it 34 | /// so that extra work doesn't need to be done on every method call. 35 | /// 36 | /// # Arguments 37 | /// 38 | /// * `env` - Java environment to use. 39 | /// * `obj` - Object to wrap. 40 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { 41 | let poll_next = env.get_method_id( 42 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/stream/Stream").unwrap().as_obj()), 43 | "pollNext", 44 | "(Lio/github/gedgygedgy/rust/task/Waker;)Lio/github/gedgygedgy/rust/task/PollResult;", 45 | )?; 46 | Ok(Self { 47 | internal: obj, 48 | poll_next, 49 | env, 50 | }) 51 | } 52 | 53 | fn j_poll_next(&self, waker: JObject<'a>) -> Result>>> { 54 | let result = self 55 | .env 56 | .call_method_unchecked( 57 | self.internal, 58 | self.poll_next, 59 | JavaType::Object("io/github/gedgygedgy/rust/task/PollResult".to_string()), 60 | &[waker.into()], 61 | )? 62 | .l()?; 63 | let _auto_local = self.env.auto_local(result); 64 | Ok(if self.env.is_same_object(result, JObject::null())? { 65 | Poll::Pending 66 | } else { 67 | Poll::Ready({ 68 | let poll = JPollResult::from_env(self.env, result)?; 69 | let stream_poll_obj = poll.get()?; 70 | if self.env.is_same_object(stream_poll_obj, JObject::null())? { 71 | None 72 | } else { 73 | let stream_poll = JStreamPoll::from_env(self.env, stream_poll_obj)?; 74 | Some(stream_poll.get()?) 75 | } 76 | }) 77 | }) 78 | } 79 | 80 | // Switch the Result and Poll return value to make this easier to implement using ?. 81 | fn poll_next_internal(&self, context: &mut Context) -> Result>>> { 82 | use crate::task::waker; 83 | self.j_poll_next(waker(self.env, context.waker().clone())?) 84 | } 85 | } 86 | 87 | impl<'a: 'b, 'b> ::std::ops::Deref for JStream<'a, 'b> { 88 | type Target = JObject<'a>; 89 | 90 | fn deref(&self) -> &Self::Target { 91 | &self.internal 92 | } 93 | } 94 | 95 | impl<'a: 'b, 'b> From> for JObject<'a> { 96 | fn from(other: JStream<'a, 'b>) -> JObject<'a> { 97 | other.internal 98 | } 99 | } 100 | 101 | impl<'a: 'b, 'b> Stream for JStream<'a, 'b> { 102 | type Item = Result>; 103 | 104 | fn poll_next(self: Pin<&mut Self>, context: &mut Context) -> Poll> { 105 | match self.poll_next_internal(context) { 106 | Ok(Poll::Ready(result)) => Poll::Ready(result.map(|o| Ok(o))), 107 | Ok(Poll::Pending) => Poll::Pending, 108 | Err(err) => Poll::Ready(Some(Err(err))), 109 | } 110 | } 111 | } 112 | 113 | /// [`Send`] version of [`JStream`]. Instead of storing a [`JNIEnv`], it stores 114 | /// a [`JavaVM`] and calls [`JavaVM::get_env`] when [`Stream::poll_next`] is 115 | /// called. 116 | pub struct JSendStream { 117 | internal: GlobalRef, 118 | vm: JavaVM, 119 | } 120 | 121 | impl<'a: 'b, 'b> TryFrom> for JSendStream { 122 | type Error = Error; 123 | 124 | fn try_from(stream: JStream<'a, 'b>) -> Result { 125 | Ok(Self { 126 | internal: stream.env.new_global_ref(stream.internal)?, 127 | vm: stream.env.get_java_vm()?, 128 | }) 129 | } 130 | } 131 | 132 | impl ::std::ops::Deref for JSendStream { 133 | type Target = GlobalRef; 134 | 135 | fn deref(&self) -> &Self::Target { 136 | &self.internal 137 | } 138 | } 139 | 140 | impl JSendStream { 141 | fn poll_next_internal( 142 | &self, 143 | context: &mut Context<'_>, 144 | ) -> Result>>> { 145 | let env = self.vm.get_env()?; 146 | let jstream = JStream::from_env(&env, self.internal.as_obj())?; 147 | jstream 148 | .poll_next_internal(context) 149 | .map(|result| result.map(|result| result.map(|obj| env.new_global_ref(obj)))) 150 | } 151 | } 152 | 153 | impl Stream for JSendStream { 154 | type Item = Result; 155 | 156 | fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll> { 157 | match self.poll_next_internal(context) { 158 | Ok(result) => result, 159 | Err(err) => Poll::Ready(Some(Err(err))), 160 | } 161 | } 162 | } 163 | 164 | assert_impl_all!(JSendStream: Send); 165 | 166 | struct JStreamPoll<'a: 'b, 'b> { 167 | internal: JObject<'a>, 168 | get: JMethodID<'a>, 169 | env: &'b JNIEnv<'a>, 170 | } 171 | 172 | impl<'a: 'b, 'b> JStreamPoll<'a, 'b> { 173 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { 174 | let get = env.get_method_id( 175 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/stream/StreamPoll").unwrap().as_obj()), 176 | "get", 177 | "()Ljava/lang/Object;")?; 178 | Ok(Self { 179 | internal: obj, 180 | get, 181 | env, 182 | }) 183 | } 184 | 185 | pub fn get(&self) -> Result> { 186 | self.env 187 | .call_method_unchecked( 188 | self.internal, 189 | self.get, 190 | JavaType::Object("java/lang/Object".into()), 191 | &[], 192 | )? 193 | .l() 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod test { 199 | use super::JStream; 200 | use crate::test_utils; 201 | use futures::stream::Stream; 202 | use std::{ 203 | pin::Pin, 204 | task::{Context, Poll}, 205 | }; 206 | 207 | #[test] 208 | fn test_jstream() { 209 | use std::sync::Arc; 210 | 211 | test_utils::JVM_ENV.with(|env| { 212 | let data = Arc::new(test_utils::TestWakerData::new()); 213 | assert_eq!(Arc::strong_count(&data), 1); 214 | assert_eq!(data.value(), false); 215 | 216 | let waker = test_utils::test_waker(&data); 217 | assert_eq!(Arc::strong_count(&data), 2); 218 | assert_eq!(data.value(), false); 219 | 220 | let stream_obj = env 221 | .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) 222 | .unwrap(); 223 | let mut stream = JStream::from_env(env, stream_obj).unwrap(); 224 | 225 | assert!(Pin::new(&mut stream) 226 | .poll_next(&mut Context::from_waker(&waker)) 227 | .is_pending()); 228 | assert_eq!(Arc::strong_count(&data), 3); 229 | assert_eq!(data.value(), false); 230 | 231 | let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 232 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) 233 | .unwrap(); 234 | assert_eq!(Arc::strong_count(&data), 2); 235 | assert_eq!(data.value(), true); 236 | data.set_value(false); 237 | 238 | let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 239 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) 240 | .unwrap(); 241 | assert_eq!(Arc::strong_count(&data), 2); 242 | assert_eq!(data.value(), false); 243 | data.set_value(false); 244 | 245 | let poll = Pin::new(&mut stream).poll_next(&mut Context::from_waker(&waker)); 246 | if let Poll::Ready(Some(Ok(actual_obj1))) = poll { 247 | assert!(env.is_same_object(actual_obj1, obj1).unwrap()); 248 | } else { 249 | panic!("Poll result should be ready"); 250 | } 251 | assert_eq!(Arc::strong_count(&data), 2); 252 | assert_eq!(data.value(), false); 253 | 254 | let poll = Pin::new(&mut stream).poll_next(&mut Context::from_waker(&waker)); 255 | if let Poll::Ready(Some(Ok(actual_obj2))) = poll { 256 | assert!(env.is_same_object(actual_obj2, obj2).unwrap()); 257 | } else { 258 | panic!("Poll result should be ready"); 259 | } 260 | assert_eq!(Arc::strong_count(&data), 2); 261 | assert_eq!(data.value(), false); 262 | 263 | assert!(Pin::new(&mut stream) 264 | .poll_next(&mut Context::from_waker(&waker)) 265 | .is_pending()); 266 | assert_eq!(Arc::strong_count(&data), 3); 267 | assert_eq!(data.value(), false); 268 | 269 | env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); 270 | assert_eq!(Arc::strong_count(&data), 2); 271 | assert_eq!(data.value(), true); 272 | data.set_value(false); 273 | 274 | let poll = Pin::new(&mut stream).poll_next(&mut Context::from_waker(&waker)); 275 | if let Poll::Ready(None) = poll { 276 | } else { 277 | panic!("Poll result should be ready"); 278 | } 279 | assert_eq!(Arc::strong_count(&data), 2); 280 | assert_eq!(data.value(), false); 281 | }); 282 | } 283 | 284 | #[test] 285 | fn test_jstream_await() { 286 | use futures::{executor::block_on, join}; 287 | 288 | test_utils::JVM_ENV.with(|env| { 289 | let stream_obj = env 290 | .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) 291 | .unwrap(); 292 | let mut stream = JStream::from_env(env, stream_obj).unwrap(); 293 | let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 294 | let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 295 | 296 | block_on(async { 297 | join!( 298 | async { 299 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) 300 | .unwrap(); 301 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) 302 | .unwrap(); 303 | env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); 304 | }, 305 | async { 306 | use futures::StreamExt; 307 | assert!(env 308 | .is_same_object(stream.next().await.unwrap().unwrap(), obj1) 309 | .unwrap()); 310 | assert!(env 311 | .is_same_object(stream.next().await.unwrap().unwrap(), obj2) 312 | .unwrap()); 313 | assert!(stream.next().await.is_none()); 314 | } 315 | ); 316 | }); 317 | }); 318 | } 319 | 320 | #[test] 321 | fn test_jsendstream_await() { 322 | use super::JSendStream; 323 | use futures::{executor::block_on, join}; 324 | use std::convert::TryInto; 325 | 326 | test_utils::JVM_ENV.with(|env| { 327 | let stream_obj = env 328 | .new_object("io/github/gedgygedgy/rust/stream/QueueStream", "()V", &[]) 329 | .unwrap(); 330 | let stream = JStream::from_env(env, stream_obj).unwrap(); 331 | let mut stream: JSendStream = stream.try_into().unwrap(); 332 | let obj1 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 333 | let obj2 = env.new_object("java/lang/Object", "()V", &[]).unwrap(); 334 | 335 | block_on(async { 336 | join!( 337 | async { 338 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj1.into()]) 339 | .unwrap(); 340 | env.call_method(stream_obj, "add", "(Ljava/lang/Object;)V", &[obj2.into()]) 341 | .unwrap(); 342 | env.call_method(stream_obj, "finish", "()V", &[]).unwrap(); 343 | }, 344 | async { 345 | use futures::StreamExt; 346 | assert!(env 347 | .is_same_object(stream.next().await.unwrap().unwrap().as_obj(), obj1) 348 | .unwrap()); 349 | assert!(env 350 | .is_same_object(stream.next().await.unwrap().unwrap().as_obj(), obj2) 351 | .unwrap()); 352 | assert!(stream.next().await.is_none()); 353 | } 354 | ); 355 | }); 356 | }); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /rust/task.rs: -------------------------------------------------------------------------------- 1 | use ::jni::{ 2 | errors::Result, 3 | objects::{JMethodID, JObject, JClass}, 4 | signature::JavaType, 5 | JNIEnv, 6 | }; 7 | use std::task::Waker; 8 | 9 | /// Wraps the given waker in a `io.github.gedgygedgy.rust.task.Waker` object. 10 | /// 11 | /// Calling this function is generally not necessary, since 12 | /// [`JFuture`](crate::future::JFuture) and [`JStream`](crate::stream::JStream) 13 | /// take care of it for you. 14 | /// 15 | /// # Arguments 16 | /// 17 | /// * `env` - Java environment in which to create the object. 18 | /// * `waker` - Waker to wrap in a Java object. 19 | pub fn waker<'a: 'b, 'b>(env: &'b JNIEnv<'a>, waker: Waker) -> Result> { 20 | let runnable = crate::ops::fn_once_runnable(env, |_e, _o| waker.wake())?; 21 | 22 | let obj = env.new_object( 23 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/task/Waker").unwrap().as_obj()), 24 | "(Lio/github/gedgygedgy/rust/ops/FnRunnable;)V", 25 | &[runnable.into()], 26 | )?; 27 | Ok(obj) 28 | } 29 | 30 | /// Wrapper for [`JObject`]s that implement 31 | /// `io.github.gedgygedgy.rust.task.PollResult`. Provides method to get the 32 | /// poll result. 33 | /// 34 | /// Looks up the class and method IDs on creation rather than for every method 35 | /// call. 36 | pub struct JPollResult<'a: 'b, 'b> { 37 | internal: JObject<'a>, 38 | get: JMethodID<'a>, 39 | env: &'b JNIEnv<'a>, 40 | } 41 | 42 | impl<'a: 'b, 'b> JPollResult<'a, 'b> { 43 | /// Create a [`JPollResult`] from the environment and an object. This looks 44 | /// up the necessary class and method IDs to call all of the methods on it 45 | /// so that extra work doesn't need to be done on every method call. 46 | /// 47 | /// # Arguments 48 | /// 49 | /// * `env` - Java environment to use. 50 | /// * `obj` - Object to wrap. 51 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { 52 | let get = env.get_method_id( 53 | JClass::from(crate::classcache::get_class("io/github/gedgygedgy/rust/task/PollResult").unwrap().as_obj()), 54 | "get", 55 | "()Ljava/lang/Object;")?; 56 | Ok(Self { 57 | internal: obj, 58 | get, 59 | env, 60 | }) 61 | } 62 | 63 | /// Gets the object associated with the [`JPollResult`] by calling 64 | /// `io.github.gedgygedgy.rust.task.PollResult.get()`. Can throw an 65 | /// exception. 66 | pub fn get(&self) -> Result> { 67 | self.env 68 | .call_method_unchecked( 69 | self.internal, 70 | self.get, 71 | JavaType::Object("java/lang/Object".into()), 72 | &[], 73 | )? 74 | .l() 75 | } 76 | } 77 | 78 | impl<'a: 'b, 'b> ::std::ops::Deref for JPollResult<'a, 'b> { 79 | type Target = JObject<'a>; 80 | 81 | fn deref(&self) -> &Self::Target { 82 | &self.internal 83 | } 84 | } 85 | 86 | impl<'a: 'b, 'b> From> for JObject<'a> { 87 | fn from(other: JPollResult<'a, 'b>) -> JObject<'a> { 88 | other.internal 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod test { 94 | use crate::test_utils; 95 | use std::sync::Arc; 96 | 97 | #[test] 98 | fn test_waker_wake() { 99 | test_utils::JVM_ENV.with(|env| { 100 | let data = Arc::new(test_utils::TestWakerData::new()); 101 | assert_eq!(Arc::strong_count(&data), 1); 102 | assert_eq!(data.value(), false); 103 | 104 | let waker = crate::test_utils::test_waker(&data); 105 | assert_eq!(Arc::strong_count(&data), 2); 106 | assert_eq!(data.value(), false); 107 | 108 | let jwaker = super::waker(env, waker).unwrap(); 109 | assert_eq!(Arc::strong_count(&data), 2); 110 | assert_eq!(data.value(), false); 111 | 112 | env.call_method(jwaker, "wake", "()V", &[]).unwrap(); 113 | assert_eq!(Arc::strong_count(&data), 1); 114 | assert_eq!(data.value(), true); 115 | data.set_value(false); 116 | 117 | env.call_method(jwaker, "wake", "()V", &[]).unwrap(); 118 | assert_eq!(Arc::strong_count(&data), 1); 119 | assert_eq!(data.value(), false); 120 | }); 121 | } 122 | 123 | #[test] 124 | fn test_waker_close_wake() { 125 | test_utils::JVM_ENV.with(|env| { 126 | let data = Arc::new(test_utils::TestWakerData::new()); 127 | assert_eq!(Arc::strong_count(&data), 1); 128 | assert_eq!(data.value(), false); 129 | 130 | let waker = crate::test_utils::test_waker(&data); 131 | assert_eq!(Arc::strong_count(&data), 2); 132 | assert_eq!(data.value(), false); 133 | 134 | let jwaker = super::waker(env, waker).unwrap(); 135 | assert_eq!(Arc::strong_count(&data), 2); 136 | assert_eq!(data.value(), false); 137 | 138 | env.call_method(jwaker, "close", "()V", &[]).unwrap(); 139 | assert_eq!(Arc::strong_count(&data), 1); 140 | assert_eq!(data.value(), false); 141 | 142 | env.call_method(jwaker, "wake", "()V", &[]).unwrap(); 143 | assert_eq!(Arc::strong_count(&data), 1); 144 | assert_eq!(data.value(), false); 145 | }); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /rust/uuid.rs: -------------------------------------------------------------------------------- 1 | use jni::{ 2 | errors::Result, 3 | objects::{AutoLocal, JMethodID, JObject}, 4 | signature::{JavaType, Primitive}, 5 | sys::jlong, 6 | JNIEnv, 7 | }; 8 | use uuid::Uuid; 9 | 10 | /// Wrapper for [`JObject`]s that contain `java.util.UUID`. Provides methods 11 | /// to convert to and from a [`Uuid`]. 12 | /// 13 | /// Looks up the class and method IDs on creation rather than for every method 14 | /// call. 15 | pub struct JUuid<'a: 'b, 'b> { 16 | internal: JObject<'a>, 17 | get_least_significant_bits: JMethodID<'a>, 18 | get_most_significant_bits: JMethodID<'a>, 19 | env: &'b JNIEnv<'a>, 20 | } 21 | 22 | impl<'a: 'b, 'b> JUuid<'a, 'b> { 23 | /// Create a [`JUuid`] from the environment and an object. This looks up 24 | /// the necessary class and method IDs to call all of the methods on it so 25 | /// that extra work doesn't need to be done on every method call. 26 | /// 27 | /// # Arguments 28 | /// 29 | /// * `env` - Java environment to use. 30 | /// * `obj` - Object to wrap. 31 | pub fn from_env(env: &'b JNIEnv<'a>, obj: JObject<'a>) -> Result { 32 | let class = env.auto_local(env.find_class("java/util/UUID")?); 33 | Self::from_env_impl(env, obj, class) 34 | } 35 | 36 | /// Create a [`JUuid`] which wraps a new `java.util.UUID` created from a 37 | /// given [`Uuid`]. 38 | /// 39 | /// # Arguments 40 | /// 41 | /// * `env` - Java environment to use. 42 | /// * `uuid` - [`Uuid`] to convert into a `java.util.UUID`. 43 | pub fn new(env: &'b JNIEnv<'a>, uuid: Uuid) -> Result { 44 | let val = uuid.as_u128(); 45 | let least = (val & 0xFFFFFFFFFFFFFFFF) as jlong; 46 | let most = ((val >> 64) & 0xFFFFFFFFFFFFFFFF) as jlong; 47 | 48 | let class = env.auto_local(env.find_class("java/util/UUID")?); 49 | let obj = env.new_object(&class, "(JJ)V", &[most.into(), least.into()])?; 50 | Self::from_env_impl(env, obj, class) 51 | } 52 | 53 | /// Convert the `java.util.UUID` into a [`Uuid`]. 54 | pub fn as_uuid(&self) -> Result { 55 | let least = self 56 | .env 57 | .call_method_unchecked( 58 | self.internal, 59 | self.get_least_significant_bits, 60 | JavaType::Primitive(Primitive::Long), 61 | &[], 62 | )? 63 | .j()? as u64; 64 | let most = self 65 | .env 66 | .call_method_unchecked( 67 | self.internal, 68 | self.get_most_significant_bits, 69 | JavaType::Primitive(Primitive::Long), 70 | &[], 71 | )? 72 | .j()? as u64; 73 | let val = ((most as u128) << 64) | (least as u128); 74 | Ok(Uuid::from_u128(val)) 75 | } 76 | 77 | fn from_env_impl( 78 | env: &'b JNIEnv<'a>, 79 | obj: JObject<'a>, 80 | class: AutoLocal<'a, 'b>, 81 | ) -> Result { 82 | let get_least_significant_bits = 83 | env.get_method_id(&class, "getLeastSignificantBits", "()J")?; 84 | let get_most_significant_bits = 85 | env.get_method_id(&class, "getMostSignificantBits", "()J")?; 86 | Ok(Self { 87 | internal: obj, 88 | get_least_significant_bits, 89 | get_most_significant_bits, 90 | env, 91 | }) 92 | } 93 | } 94 | 95 | impl<'a: 'b, 'b> ::std::ops::Deref for JUuid<'a, 'b> { 96 | type Target = JObject<'a>; 97 | 98 | fn deref(&self) -> &Self::Target { 99 | &self.internal 100 | } 101 | } 102 | 103 | impl<'a: 'b, 'b> From> for JObject<'a> { 104 | fn from(other: JUuid<'a, 'b>) -> JObject<'a> { 105 | other.internal 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod test { 111 | use super::JUuid; 112 | use crate::test_utils; 113 | use jni::{objects::JObject, sys::jlong}; 114 | use uuid::Uuid; 115 | 116 | struct UuidTest { 117 | uuid: u128, 118 | most: u64, 119 | least: u64, 120 | } 121 | 122 | const TESTS: &[UuidTest] = &[ 123 | UuidTest { 124 | uuid: 0x63f0f617_f589_40d0_98be_90747b1ea55a, 125 | most: 0x63f0f617_f589_40d0, 126 | least: 0x98be_90747b1ea55a, 127 | }, 128 | UuidTest { 129 | uuid: 0xdea61ec0_51a6_4d97_81e0_d7b77e9c03d4, 130 | most: 0xdea61ec0_51a6_4d97, 131 | least: 0x81e0_d7b77e9c03d4, 132 | }, 133 | ]; 134 | 135 | #[test] 136 | fn test_uuid_new() { 137 | test_utils::JVM_ENV.with(|env| { 138 | for test in TESTS { 139 | let most = test.most as jlong; 140 | let least = test.least as jlong; 141 | 142 | let uuid_obj = JUuid::new(env, Uuid::from_u128(test.uuid)).unwrap(); 143 | let obj: JObject = uuid_obj.into(); 144 | 145 | let actual_most = env 146 | .call_method(obj, "getMostSignificantBits", "()J", &[]) 147 | .unwrap() 148 | .j() 149 | .unwrap(); 150 | let actual_least = env 151 | .call_method(obj, "getLeastSignificantBits", "()J", &[]) 152 | .unwrap() 153 | .j() 154 | .unwrap(); 155 | assert_eq!(actual_most, most); 156 | assert_eq!(actual_least, least); 157 | } 158 | }); 159 | } 160 | 161 | #[test] 162 | fn test_uuid_as_uuid() { 163 | test_utils::JVM_ENV.with(|env| { 164 | for test in TESTS { 165 | let most = test.most as jlong; 166 | let least = test.least as jlong; 167 | 168 | let obj = env 169 | .new_object("java/util/UUID", "(JJ)V", &[most.into(), least.into()]) 170 | .unwrap(); 171 | let uuid_obj = JUuid::from_env(env, obj).unwrap(); 172 | 173 | assert_eq!(uuid_obj.as_uuid().unwrap(), Uuid::from_u128(test.uuid)); 174 | } 175 | }); 176 | } 177 | } 178 | --------------------------------------------------------------------------------