Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JNI bindings for FileStorage #61

Merged
merged 22 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also git ignore *.h files

Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
target
Cargo.lock
**/app_id
*.class
7 changes: 6 additions & 1 deletion 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,6 +15,7 @@ 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 }


data-error = { path = "../data-error" }
Expand All @@ -23,3 +24,7 @@ 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
Comment on lines +6 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can detect the OS in the Makefile to have only one make test

For reference: https://gist.github.com/sighingnow/deee806603ec9274fd47
but there may be even an easier way


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

.PHONY: download-test-lib
download-test-lib:
cd tests && \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cd tests && \
@cd tests && \

So it doesn't print the command to the terminal each time

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
kirillt marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These commands should be run inside tests/

```java
javac -h . FileStorage.java
javac FileStorage.java
LD_LIBRARY_PATH=<project-root-path>/target/debug && java FileStorage.java
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project root path is always ../ if we run from fs-storage/

```

## Steps to test Java Wrapper
```bash
cd tests && make test
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no make test
it's os dependant

```
205 changes: 205 additions & 0 deletions fs-storage/src/jni_file_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#[cfg(feature = "jni-bindings")]
pub mod jni_bindings {
use std::collections::BTreeMap;
use std::path::Path;

use jni::signature::ReturnType;
// This is the interface to the JVM that we'll call the majority of our
// methods on.
use jni::JNIEnv;

// These objects are what you should use as arguments to your native
// function. They carry extra lifetime information to prevent them escaping
// this context and getting used after being GC'd.
use jni::objects::{JClass, JString, 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, jstring};

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));
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,
) -> jstring {
let id: String = env.get_string(&id).unwrap().into();
match FileStorage::from_jlong(file_storage_ptr).remove(&id) {
Ok(()) => env.new_string("").unwrap().into_raw(),
Err(err) => env
.new_string(err.to_string())
.unwrap()
.into_raw(),
}
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_needsSyncing(
env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) -> jstring {
match FileStorage::from_jlong(file_storage_ptr).needs_syncing() {
Ok(updated) => {
let result = if updated {
"true"
} else {
"false"
};
env.new_string(result).unwrap().into_raw()
}
Err(err) => env
.new_string(err.to_string())
.unwrap()
.into_raw(),
}
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_readFS(
kirillt marked this conversation as resolved.
Show resolved Hide resolved
mut env: JNIEnv<'_>,
_class: JClass,
file_storage_ptr: jlong,
) -> jobject {
let data: BTreeMap<String, String> =
FileStorage::from_jlong(file_storage_ptr)
.read_fs()
.expect("not able to read data");

// 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(
env: JNIEnv,
_class: JClass,
file_storage_ptr: jlong,
) -> jstring {
match FileStorage::from_jlong(file_storage_ptr).write_fs() {
Ok(()) => env.new_string("").unwrap().into_raw(),
Err(err) => env
.new_string(err.to_string())
.unwrap()
.into_raw(),
}
}

#[allow(clippy::suspicious_doc_comments)]
///! Safety: The FileStorage instance is dropped after this call
#[no_mangle]
pub extern "system" fn Java_FileStorage_erase(
env: JNIEnv,
_class: JClass,
file_storage_ptr: jlong,
) -> jstring {
let file_storage = unsafe {
Box::from_raw(file_storage_ptr as *mut FileStorage<String, String>)
};
match file_storage.erase() {
Ok(()) => env.new_string("").unwrap().into_raw(),
Err(err) => env
.new_string(err.to_string())
.unwrap()
.into_raw(),
}
}

#[no_mangle]
pub extern "system" fn Java_FileStorage_merge(
env: JNIEnv,
_class: JClass,
file_storage_ptr: jlong,
other_file_storage_ptr: jlong,
) -> jstring {
match FileStorage::from_jlong(file_storage_ptr)
.merge_from(FileStorage::from_jlong(other_file_storage_ptr))
{
Ok(()) => env.new_string("").unwrap().into_raw(),
Err(err) => env
.new_string(err.to_string())
.unwrap()
.into_raw(),
}
}
}
1 change: 1 addition & 0 deletions fs-storage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod base_storage;
pub mod file_storage;
pub mod jni_file_storage;
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
Loading