Skip to content

Commit

Permalink
JNI bindings for FileStorage (ARK-Builders#61)
Browse files Browse the repository at this point in the history
* Remove unnecessary clone trait

* Add jni bindings for FileStorage

* Manually convert BTreeMap to java LinkedHashMap

* Add feature flag

* implemented java wrapper

Signed-off-by: pushkarm029 <[email protected]>

* fix

Signed-off-by: pushkarm029 <[email protected]>

* fix

Signed-off-by: pushkarm029 <[email protected]>

* Update folder structure and instructions

* added java wrapper tests

Signed-off-by: pushkarm029 <[email protected]>

* fix readme and makefile

Signed-off-by: pushkarm029 <[email protected]>

* ci rebase

Signed-off-by: pushkarm029 <[email protected]>

* fix

Signed-off-by: pushkarm029 <[email protected]>

* removed main

Signed-off-by: pushkarm029 <[email protected]>

* instead of throwing err, return err as string

Signed-off-by: pushkarm029 <[email protected]>

* Refactor module structure

* Fix fmt

* fix

Signed-off-by: pushkarm029 <[email protected]>

* add tests for exception

Signed-off-by: pushkarm029 <[email protected]>

* tests fix

Signed-off-by: pushkarm029 <[email protected]>

* fix

Signed-off-by: pushkarm029 <[email protected]>

* fix

Signed-off-by: pushkarm029 <[email protected]>

---------

Signed-off-by: pushkarm029 <[email protected]>
Co-authored-by: pushkarm029 <[email protected]>
  • Loading branch information
twitu and Pushkarm029 authored Jun 23, 2024
1 parent 6c5f786 commit 8d12782
Show file tree
Hide file tree
Showing 13 changed files with 556 additions and 2 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ jobs:
- name: Build Release
run: cargo build --verbose --release

- name: Install JDK
uses: actions/[email protected]
with:
distribution: 'temurin'
java-version: '22'

- name: Java tests
run: |
cd fs-storage
make test-linux
windows:
name: Test on Windows
runs-on: windows-latest
Expand All @@ -54,6 +65,17 @@ jobs:
- name: Run tests
run: cargo test --workspace --verbose

- name: Install JDK
uses: actions/[email protected]
with:
distribution: 'temurin'
java-version: '22'

- name: Java tests
run: |
cd fs-storage
make test-windows
mac-intel:
name: Test on macOS Intel
runs-on: macos-14
Expand All @@ -66,3 +88,14 @@ jobs:

- name: Run tests
run: cargo test --workspace --verbose

- name: Install JDK
uses: actions/[email protected]
with:
distribution: 'temurin'
java-version: '22'

- name: Java tests
run: |
cd fs-storage
make test-mac
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
target
Cargo.lock
**/app_id
*.class
9 changes: 7 additions & 2 deletions fs-storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"

[lib]
name = "fs_storage"
crate-type = ["rlib"]
crate-type = ["rlib", "cdylib"]
bench = false

[[example]]
Expand All @@ -15,11 +15,16 @@ name = "cli"
log = { version = "0.4.17", features = ["release_max_level_off"] }
serde_json = "1.0.82"
serde = { version = "1.0.138", features = ["derive"] }

jni = { version = "0.21.1", optional = true }
jnix = { version = "0.5.1", features = ["derive"] }

data-error = { path = "../data-error" }


[dev-dependencies]
anyhow = "1.0.81"
tempdir = "0.3.7"

[features]
default = ["jni-bindings"]
jni-bindings = ["jni"]
52 changes: 52 additions & 0 deletions fs-storage/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
CARGO_CMD = cargo
JAR_FILE := junit.jar
URL := https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.9.3/junit-platform-console-standalone-1.9.3.jar

.PHONY: test-linux
test-linux: build-rust download-test-lib java-test-linux

.PHONY: test-mac
test-mac: build-rust download-test-lib java-test-mac

.PHONY: test-windows
test-windows: build-rust download-test-lib java-test-windows

.PHONY: build-rust
build-rust:
$(CARGO_CMD) build

.PHONY: download-test-lib
download-test-lib:
cd tests && \
if [ ! -f "$(JAR_FILE)" ]; then \
curl -o "$(JAR_FILE)" "$(URL)"; \
else \
echo "$(JAR_FILE) already exists."; \
fi

.PHONY: java-test-linux
java-test-linux:
(cd ../target/debug && \
export LD_LIBRARY_PATH=$$PWD && \
cd ../../fs-storage/tests && \
javac -d out FileStorage.java && \
javac -d out -cp out:$(JAR_FILE) FileStorageTest.java && \
RUST_BACKTRACE=1 java -jar $(JAR_FILE) --class-path out --scan-class-path)

.PHONY: java-test-mac
java-test-mac:
(cd ../target/debug && \
export DYLD_LIBRARY_PATH=$$PWD && \
cd ../../fs-storage/tests && \
javac -d out FileStorage.java && \
javac -d out -cp out:$(JAR_FILE) FileStorageTest.java && \
RUST_BACKTRACE=1 java -jar $(JAR_FILE) --class-path out --scan-class-path)

.PHONY: java-test-windows
java-test-windows:
(cd ../target/debug && \
export LIBRARY_PATH="$$PWD" && \
cd ../../fs-storage/tests && \
javac -d out FileStorage.java && \
javac -d out -cp "out;$(JAR_FILE)" FileStorageTest.java && \
RUST_BACKTRACE=1 java -Djava.library.path=$$LIBRARY_PATH -jar $(JAR_FILE) --class-path out --scan-class-path)
12 changes: 12 additions & 0 deletions fs-storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,15 @@ key2: value2
```bash
cargo run --example cli read /tmp/z
```

## Java Wrapper
```java
javac -h . FileStorage.java
javac FileStorage.java
LD_LIBRARY_PATH=<project-root-path>/target/debug && java FileStorage.java
```

## Steps to test Java Wrapper
```bash
cd tests && make test
```
5 changes: 5 additions & 0 deletions fs-storage/src/base_storage.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use data_error::Result;
use std::collections::BTreeMap;

#[cfg(feature = "jni-bindings")]
use jnix::{FromJava, IntoJava};

#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
#[cfg_attr(feature = "jni-bindings", derive(FromJava, IntoJava))]
#[jnix(class_name = "SyncStatus")]
/// Represents the synchronization status of the storage.
pub enum SyncStatus {
/// No synchronization needed.
Expand Down
217 changes: 217 additions & 0 deletions fs-storage/src/jni/file_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
use crate::base_storage::SyncStatus;
use jni::signature::ReturnType;
use std::collections::BTreeMap;
use std::path::Path;
// This is the interface to the JVM that we'll call the majority of our
// methods on.
use jni::JNIEnv;

// These objects are what you should use as arguments to your native
// function. They carry extra lifetime information to prevent them escaping
// this context and getting used after being GC'd.
use jni::objects::{JClass, JObject, JString, JValue};

// This is just a pointer. We'll be returning it from our function. We
// can't return one of the objects with lifetime information because the
// lifetime checker won't let us.
use jni::sys::{jlong, jobject};
use jnix::{IntoJava, JnixEnv};

use crate::base_storage::BaseStorage;

use crate::file_storage::FileStorage;

impl FileStorage<String, String> {
fn from_jlong<'a>(value: jlong) -> &'a mut Self {
unsafe { &mut *(value as *mut FileStorage<String, String>) }
}
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_create<'local>(
mut env: JNIEnv<'local>,
_class: JClass,
label: JString<'local>,
path: JString<'local>,
) -> jlong {
let label: String = env
.get_string(&label)
.expect("Couldn't get label!")
.into();
let path: String = env
.get_string(&path)
.expect("Couldn't get path!")
.into();

let file_storage: FileStorage<String, String> =
FileStorage::new(label, Path::new(&path)).unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.expect("Failed to throw RuntimeException");
FileStorage::new("".to_string(), Path::new("")).unwrap()
});
Box::into_raw(Box::new(file_storage)) as jlong
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_set<'local>(
mut env: JNIEnv<'local>,
_class: JClass,
id: JString<'local>,
value: JString<'local>,
file_storage_ptr: jlong,
) {
let id: String = env.get_string(&id).expect("msg").into();
let value: String = env.get_string(&value).expect("msg").into();

FileStorage::from_jlong(file_storage_ptr).set(id, value);
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_remove<'local>(
mut env: JNIEnv<'local>,
_class: JClass,
id: JString<'local>,
file_storage_ptr: jlong,
) {
let id: String = env.get_string(&id).unwrap().into();
FileStorage::from_jlong(file_storage_ptr)
.remove(&id)
.unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.unwrap();
});
}

// A JNI function called from Java that creates a `MyData` Rust type, converts it to a Java
// type and returns it.
#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_FileStorage_syncStatus<'env>(
env: jnix::jni::JNIEnv<'env>,
_this: jnix::jni::objects::JObject<'env>,
file_storage_ptr: jnix::jni::sys::jlong,
) -> jnix::jni::objects::JObject<'env> {
let env = JnixEnv::from(env);
let sync_status = FileStorage::from_jlong(file_storage_ptr)
.sync_status()
.unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", err.to_string())
.unwrap();
SyncStatus::InSync
});

sync_status.into_java(&env).forget()
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_sync(
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) {
FileStorage::from_jlong(file_storage_ptr)
.sync()
.unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.unwrap();
});
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_readFS(
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) -> jobject {
let data: BTreeMap<String, String> =
match FileStorage::from_jlong(file_storage_ptr).read_fs() {
Ok(data) => data.clone(),
Err(err) => {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.expect("Failed to throw RuntimeException");
return JObject::null().into_raw();
}
};

// Create a new LinkedHashMap object
let linked_hash_map_class =
env.find_class("java/util/LinkedHashMap").unwrap();
let linked_hash_map = env
.new_object(linked_hash_map_class, "()V", &[])
.unwrap();

// Get the put method ID
let put_method_id = env
.get_method_id(
"java/util/LinkedHashMap",
"put",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
)
.unwrap();

// Insert each key-value pair from the BTreeMap into the LinkedHashMap
for (key, value) in data {
let j_key = env.new_string(key).unwrap();
let j_value = env.new_string(value).unwrap();
let j_key = JValue::from(&j_key).as_jni();
let j_value = JValue::from(&j_value).as_jni();
unsafe {
env.call_method_unchecked(
&linked_hash_map,
put_method_id,
ReturnType::Object,
&[j_key, j_value],
)
.unwrap()
};
}

// Return the LinkedHashMap as a raw pointer
linked_hash_map.as_raw()
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_writeFS(
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) {
FileStorage::from_jlong(file_storage_ptr)
.write_fs()
.unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.unwrap();
});
}

#[allow(clippy::suspicious_doc_comments)]
///! Safety: The FileStorage instance is dropped after this call
#[no_mangle]
pub extern "system" fn Java_FileStorage_erase(
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) {
let file_storage = unsafe {
Box::from_raw(file_storage_ptr as *mut FileStorage<String, String>)
};
file_storage.erase().unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.unwrap();
});
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_merge(
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
other_file_storage_ptr: jlong,
) {
FileStorage::from_jlong(file_storage_ptr)
.merge_from(FileStorage::from_jlong(other_file_storage_ptr))
.unwrap_or_else(|err| {
env.throw_new("java/lang/RuntimeException", &err.to_string())
.unwrap();
});
}
1 change: 1 addition & 0 deletions fs-storage/src/jni/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod file_storage;
2 changes: 2 additions & 0 deletions fs-storage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub mod base_storage;
pub mod file_storage;
#[cfg(feature = "jni-bindings")]
pub mod jni;
pub mod monoid;
mod utils;
pub const ARK_FOLDER: &str = ".ark";
Expand Down
1 change: 1 addition & 0 deletions fs-storage/tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.jar
Loading

0 comments on commit 8d12782

Please sign in to comment.