This is an automated email from the ASF dual-hosted git repository.
tison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new edb48e8c refactor(bindings/java): split different classes (#2288)
edb48e8c is described below
commit edb48e8ced2c309cb806ec7b9c166151c36c73b8
Author: tison <[email protected]>
AuthorDate: Tue May 23 11:47:57 2023 +0800
refactor(bindings/java): split different classes (#2288)
Signed-off-by: tison <[email protected]>
---
Cargo.lock | 1 +
bindings/java/Cargo.toml | 1 +
bindings/java/src/blocking_operator.rs | 150 +++++++++++
bindings/java/src/error.rs | 116 +++++++++
bindings/java/src/lib.rs | 282 ++-------------------
.../{Operator.java => BlockingOperator.java} | 15 +-
.../src/main/java/org/apache/opendal/Operator.java | 34 +--
bindings/java/src/metadata.rs | 60 +++++
bindings/java/src/operator.rs | 130 ++++++++++
.../java/org/apache/opendal/AsyncStepsTest.java | 2 +-
.../java/org/apache/opendal/ExceptionTest.java | 8 +-
.../test/java/org/apache/opendal/StepsTest.java | 14 +-
12 files changed, 500 insertions(+), 313 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 734e88d3..cf2c3baa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2690,6 +2690,7 @@ dependencies = [
name = "opendal-java"
version = "0.1.0"
dependencies = [
+ "anyhow",
"jni",
"num_cpus",
"once_cell",
diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml
index 60728fd7..1730e5a4 100644
--- a/bindings/java/Cargo.toml
+++ b/bindings/java/Cargo.toml
@@ -34,6 +34,7 @@ doc = false
[dependencies]
opendal.workspace = true
+anyhow = "1.0.71"
jni = "0.21.1"
once_cell = "1.17.1"
tokio = { version = "1.28.1", features = ["full"] }
diff --git a/bindings/java/src/blocking_operator.rs
b/bindings/java/src/blocking_operator.rs
new file mode 100644
index 00000000..32a4899b
--- /dev/null
+++ b/bindings/java/src/blocking_operator.rs
@@ -0,0 +1,150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::str::FromStr;
+
+use jni::objects::{JClass, JObject, JString};
+use jni::sys::{jlong, jstring};
+use jni::JNIEnv;
+
+use opendal::{BlockingOperator, Operator, Scheme};
+
+use crate::jmap_to_hashmap;
+use crate::Result;
+
+#[no_mangle]
+pub extern "system" fn Java_org_apache_opendal_BlockingOperator_constructor(
+ mut env: JNIEnv,
+ _: JClass,
+ scheme: JString,
+ map: JObject,
+) -> jlong {
+ intern_constructor(&mut env, scheme, map).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ 0
+ })
+}
+
+fn intern_constructor(env: &mut JNIEnv, scheme: JString, map: JObject) ->
Result<jlong> {
+ let scheme = Scheme::from_str(env.get_string(&scheme)?.to_str()?)?;
+ let map = jmap_to_hashmap(env, &map)?;
+ let op = Operator::via_map(scheme, map)?;
+ Ok(Box::into_raw(Box::new(op.blocking())) as jlong)
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn
Java_org_apache_opendal_BlockingOperator_disposeInternal(
+ _: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+) {
+ drop(Box::from_raw(op));
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_read(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+ file: JString,
+) -> jstring {
+ intern_read(&mut env, &mut *op, file).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ JObject::null().into_raw()
+ })
+}
+
+fn intern_read(env: &mut JNIEnv, op: &mut BlockingOperator, file: JString) ->
Result<jstring> {
+ let file = env.get_string(&file)?;
+ let content = String::from_utf8(op.read(file.to_str()?)?)?;
+ Ok(env.new_string(content)?.into_raw())
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_write(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+ file: JString,
+ content: JString,
+) {
+ intern_write(&mut env, &mut *op, file, content).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ })
+}
+
+fn intern_write(
+ env: &mut JNIEnv,
+ op: &mut BlockingOperator,
+ file: JString,
+ content: JString,
+) -> Result<()> {
+ let file = env.get_string(&file)?;
+ let content = env.get_string(&content)?;
+ Ok(op.write(file.to_str()?, content.to_str()?.to_string())?)
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_stat(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+ file: JString,
+) -> jlong {
+ intern_stat(&mut env, &mut *op, file).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ 0
+ })
+}
+
+fn intern_stat(env: &mut JNIEnv, op: &mut BlockingOperator, file: JString) ->
Result<jlong> {
+ let file = env.get_string(&file)?;
+ let metadata = op.stat(file.to_str()?)?;
+ Ok(Box::into_raw(Box::new(metadata)) as jlong)
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_delete(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut BlockingOperator,
+ file: JString,
+) {
+ intern_delete(&mut env, &mut *op, file).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ })
+}
+
+fn intern_delete(env: &mut JNIEnv, op: &mut BlockingOperator, file: JString)
-> Result<()> {
+ let file = env.get_string(&file)?;
+ Ok(op.delete(file.to_str()?)?)
+}
diff --git a/bindings/java/src/error.rs b/bindings/java/src/error.rs
new file mode 100644
index 00000000..a8e4f146
--- /dev/null
+++ b/bindings/java/src/error.rs
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use jni::objects::{JThrowable, JValue};
+use jni::JNIEnv;
+use opendal::ErrorKind;
+use std::fmt::{Debug, Display, Formatter};
+
+pub(crate) struct Error {
+ inner: opendal::Error,
+}
+
+impl Error {
+ pub(crate) fn throw(&self, env: &mut JNIEnv) {
+ if let Err(err) = self.do_throw(env) {
+ env.fatal_error(err.to_string());
+ }
+ }
+
+ pub(crate) fn to_exception<'local>(
+ &self,
+ env: &mut JNIEnv<'local>,
+ ) -> jni::errors::Result<JThrowable<'local>> {
+ let class =
env.find_class("org/apache/opendal/exception/ODException")?;
+ let code = env.new_string(match self.inner.kind() {
+ ErrorKind::Unexpected => "Unexpected",
+ ErrorKind::Unsupported => "Unsupported",
+ ErrorKind::ConfigInvalid => "ConfigInvalid",
+ ErrorKind::NotFound => "NotFound",
+ ErrorKind::PermissionDenied => "PermissionDenied",
+ ErrorKind::IsADirectory => "IsADirectory",
+ ErrorKind::NotADirectory => "NotADirectory",
+ ErrorKind::AlreadyExists => "AlreadyExists",
+ ErrorKind::RateLimited => "RateLimited",
+ ErrorKind::IsSameFile => "IsSameFile",
+ ErrorKind::ConditionNotMatch => "ConditionNotMatch",
+ ErrorKind::ContentTruncated => "ContentTruncated",
+ ErrorKind::ContentIncomplete => "ContentIncomplete",
+ _ => "Unexpected",
+ })?;
+ let message = env.new_string(self.inner.to_string())?;
+ let exception = env.new_object(
+ class,
+ "(Ljava/lang/String;Ljava/lang/String;)V",
+ &[JValue::Object(&code), JValue::Object(&message)],
+ )?;
+ Ok(JThrowable::from(exception))
+ }
+
+ fn do_throw(&self, env: &mut JNIEnv) -> jni::errors::Result<()> {
+ let exception = self.to_exception(env)?;
+ env.throw(exception)
+ }
+}
+
+impl From<opendal::Error> for Error {
+ fn from(error: opendal::Error) -> Self {
+ Self { inner: error }
+ }
+}
+
+impl From<jni::errors::Error> for Error {
+ fn from(error: jni::errors::Error) -> Self {
+ Self {
+ inner: opendal::Error::new(ErrorKind::Unexpected,
&error.to_string()).set_source(error),
+ }
+ }
+}
+
+impl From<std::str::Utf8Error> for Error {
+ fn from(error: std::str::Utf8Error) -> Self {
+ Self {
+ inner: opendal::Error::new(ErrorKind::Unexpected,
&error.to_string()).set_source(error),
+ }
+ }
+}
+
+impl From<std::string::FromUtf8Error> for Error {
+ fn from(error: std::string::FromUtf8Error) -> Self {
+ Self {
+ inner: opendal::Error::new(ErrorKind::Unexpected,
&error.to_string()).set_source(error),
+ }
+ }
+}
+
+impl Debug for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ Debug::fmt(&self.inner, f)
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ Display::fmt(&self.inner, f)
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ self.inner.source()
+ }
+}
diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs
index 2da8a6af..05d8b72b 100644
--- a/bindings/java/src/lib.rs
+++ b/bindings/java/src/lib.rs
@@ -18,24 +18,21 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::c_void;
-use std::str::FromStr;
-use jni::objects::JClass;
-use jni::objects::JMap;
-use jni::objects::JObject;
-use jni::objects::JString;
-use jni::objects::JThrowable;
-use jni::objects::JValue;
-use jni::sys::{jboolean, jobject, JNI_VERSION_1_8};
-use jni::sys::{jint, jlong};
+use jni::objects::{JMap, JObject, JString};
+use jni::sys::jint;
+use jni::sys::JNI_VERSION_1_8;
use jni::{JNIEnv, JavaVM};
use once_cell::sync::OnceCell;
use tokio::runtime::Builder;
use tokio::runtime::Runtime;
-use opendal::Operator;
-use opendal::Scheme;
-use opendal::{BlockingOperator, ErrorKind};
+mod blocking_operator;
+mod error;
+mod metadata;
+mod operator;
+
+pub(crate) type Result<T> = std::result::Result<T, error::Error>;
static mut RUNTIME: OnceCell<Runtime> = OnceCell::new();
thread_local! {
@@ -75,263 +72,24 @@ pub unsafe extern "system" fn JNI_OnUnload(_: JavaVM, _:
*mut c_void) {
}
}
-#[no_mangle]
-pub extern "system" fn Java_org_apache_opendal_Operator_newOperator(
- mut env: JNIEnv,
- _class: JClass,
- input: JString,
- params: JObject,
-) -> jlong {
- let input: String = env
- .get_string(&input)
- .expect("cannot get java string")
- .into();
-
- let scheme = Scheme::from_str(&input).unwrap();
-
- let map = convert_jmap_to_hashmap(&mut env, ¶ms);
- if let Ok(operator) = Operator::via_map(scheme, map) {
- Box::into_raw(Box::new(operator)) as jlong
- } else {
- env.exception_clear().expect("cannot clear exception");
- env.throw_new(
- "java/lang/IllegalArgumentException",
- "Unsupported operator.",
- )
- .expect("cannot throw exception");
- 0 as jlong
- }
-}
-
-/// # Safety
-///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_writeAsync(
- mut env: JNIEnv,
- _class: JClass,
- ptr: *mut Operator,
- file: JString,
- content: JString,
-) -> jobject {
- let op = &mut *ptr;
-
- let file: String = env.get_string(&file).unwrap().into();
- let content: String = env.get_string(&content).unwrap().into();
-
- let class = "java/util/concurrent/CompletableFuture";
- let f = env.new_object(class, "()V", &[]).unwrap();
-
- // keep the future alive, so that we can complete it later
- // but this approach will be limited by global ref table size (65535)
- let future = env.new_global_ref(&f).unwrap();
-
- RUNTIME.get_unchecked().spawn(async move {
- let result = op.write(&file, content).await;
-
- let env = ENV.with(|cell| *cell.borrow_mut()).unwrap();
- let mut env = JNIEnv::from_raw(env).unwrap();
-
- match result {
- Ok(()) => env
- .call_method(
- future,
- "complete",
- "(Ljava/lang/Object;)Z",
- &[JValue::Object(&JObject::null())],
- )
- .unwrap(),
- Err(err) => {
- let exception = convert_error_to_exception(&mut env,
err).unwrap();
- env.call_method(
- future,
- "completeExceptionally",
- "(Ljava/lang/Throwable;)Z",
- &[JValue::Object(&exception)],
- )
- .unwrap()
- }
- }
- });
-
- f.as_raw()
-}
-
-/// # Safety
-///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_disposeInternal(
- mut _env: JNIEnv,
- _class: JClass,
- ptr: *mut Operator,
-) {
- drop(Box::from_raw(ptr));
-}
-
/// # Safety
///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_write(
- mut env: JNIEnv,
- _class: JClass,
- ptr: *mut BlockingOperator,
- file: JString,
- content: JString,
-) {
- let op = &mut *ptr;
- let file: String = env
- .get_string(&file)
- .expect("cannot get java string!")
- .into();
- let content: String = env
- .get_string(&content)
- .expect("cannot get java string!")
- .into();
- op.write(&file, content).unwrap();
+/// This function could be only when the lib is loaded.
+unsafe fn get_current_env<'local>() -> JNIEnv<'local> {
+ let env = ENV.with(|cell| *cell.borrow_mut()).unwrap();
+ JNIEnv::from_raw(env).unwrap()
}
-/// # Safety
-///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_read<'local>(
- mut env: JNIEnv<'local>,
- _class: JClass<'local>,
- ptr: *mut BlockingOperator,
- file: JString<'local>,
-) -> JString<'local> {
- let op = &mut *ptr;
- let file: String = env
- .get_string(&file)
- .expect("cannot get java string!")
- .into();
- let content = String::from_utf8(op.read(&file).unwrap()).expect("cannot
convert to string");
- env.new_string(content).expect("cannot create java string")
-}
-
-/// # Safety
-///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_stat(
- mut env: JNIEnv,
- _class: JClass,
- ptr: *mut BlockingOperator,
- file: JString,
-) -> jlong {
- let op = &mut *ptr;
- let file: String = env
- .get_string(&file)
- .expect("cannot get java string!")
- .into();
- let result = op.stat(&file);
- if let Err(error) = result {
- let exception = convert_error_to_exception(&mut env, error).unwrap();
- env.throw(exception).unwrap();
- return 0 as jlong;
- }
- Box::into_raw(Box::new(result.unwrap())) as jlong
-}
-
-/// # Safety
-///
-/// This function should not be called before the Stat are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_isFile(
- mut _env: JNIEnv,
- _class: JClass,
- ptr: *mut opendal::Metadata,
-) -> jboolean {
- let metadata = &mut *ptr;
- metadata.is_file() as jboolean
-}
-
-/// # Safety
-///
-/// This function should not be called before the Stat are ready.
-#[no_mangle]
-pub unsafe extern "system" fn
Java_org_apache_opendal_Metadata_getContentLength(
- mut _env: JNIEnv,
- _class: JClass,
- ptr: *mut opendal::Metadata,
-) -> jlong {
- let metadata = &mut *ptr;
- metadata.content_length() as jlong
-}
-
-/// # Safety
-///
-/// This function should not be called before the Stat are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_disposeInternal(
- mut _env: JNIEnv,
- _class: JClass,
- ptr: *mut opendal::Metadata,
-) {
- drop(Box::from_raw(ptr));
-}
-
-/// # Safety
-///
-/// This function should not be called before the Operator are ready.
-#[no_mangle]
-pub unsafe extern "system" fn Java_org_apache_opendal_Operator_delete<'local>(
- mut env: JNIEnv<'local>,
- _class: JClass<'local>,
- ptr: *mut BlockingOperator,
- file: JString<'local>,
-) {
- let op = &mut *ptr;
- let file: String = env
- .get_string(&file)
- .expect("cannot get java string!")
- .into();
- op.delete(&file).unwrap();
-}
-
-fn convert_error_to_exception<'local>(
- env: &mut JNIEnv<'local>,
- error: opendal::Error,
-) -> Result<JThrowable<'local>, jni::errors::Error> {
- let class = env.find_class("org/apache/opendal/exception/ODException")?;
-
- let code = env.new_string(match error.kind() {
- ErrorKind::Unexpected => "Unexpected",
- ErrorKind::Unsupported => "Unsupported",
- ErrorKind::ConfigInvalid => "ConfigInvalid",
- ErrorKind::NotFound => "NotFound",
- ErrorKind::PermissionDenied => "PermissionDenied",
- ErrorKind::IsADirectory => "IsADirectory",
- ErrorKind::NotADirectory => "NotADirectory",
- ErrorKind::AlreadyExists => "AlreadyExists",
- ErrorKind::RateLimited => "RateLimited",
- ErrorKind::IsSameFile => "IsSameFile",
- ErrorKind::ConditionNotMatch => "ConditionNotMatch",
- ErrorKind::ContentTruncated => "ContentTruncated",
- ErrorKind::ContentIncomplete => "ContentIncomplete",
- _ => "Unexpected",
- })?;
- let message = env.new_string(error.to_string())?;
-
- let sig = "(Ljava/lang/String;Ljava/lang/String;)V";
- let params = &[JValue::Object(&code), JValue::Object(&message)];
- env.new_object(class, sig, params).map(JThrowable::from)
-}
-
-fn convert_jmap_to_hashmap(env: &mut JNIEnv, params: &JObject) ->
HashMap<String, String> {
- let map = JMap::from_env(env, params).unwrap();
- let mut iter = map.iter(env).unwrap();
+fn jmap_to_hashmap(env: &mut JNIEnv, params: &JObject) ->
Result<HashMap<String, String>> {
+ let map = JMap::from_env(env, params)?;
+ let mut iter = map.iter(env)?;
let mut result: HashMap<String, String> = HashMap::new();
- while let Some(e) = iter.next(env).unwrap() {
+ while let Some(e) = iter.next(env)? {
let k = JString::from(e.0);
let v = JString::from(e.1);
- result.insert(
- env.get_string(&k).unwrap().into(),
- env.get_string(&v).unwrap().into(),
- );
+ result.insert(env.get_string(&k)?.into(), env.get_string(&v)?.into());
}
- result
+
+ Ok(result)
}
diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java
b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
similarity index 76%
copy from bindings/java/src/main/java/org/apache/opendal/Operator.java
copy to bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
index b3e0bb01..fbdf2575 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
@@ -17,25 +17,20 @@
* under the License.
*/
-
package org.apache.opendal;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
-public class Operator extends NativeObject {
- public Operator(String schema, Map<String, String> params) {
- super(newOperator(schema, params));
+public class BlockingOperator extends NativeObject {
+ public BlockingOperator(String schema, Map<String, String> map) {
+ super(constructor(schema, map));
}
public void write(String fileName, String content) {
write(nativeHandle, fileName, content);
}
- public CompletableFuture<Void> writeAsync(String fileName, String content)
{
- return writeAsync(nativeHandle, fileName, content);
- }
-
public String read(String s) {
return read(nativeHandle, s);
}
@@ -51,12 +46,10 @@ public class Operator extends NativeObject {
@Override
protected native void disposeInternal(long handle);
- private static native long newOperator(String type, Map<String, String>
params);
+ private static native long constructor(String schema, Map<String, String>
map);
private static native void write(long nativeHandle, String fileName,
String content);
- private static native CompletableFuture<Void> writeAsync(long
nativeHandle, String fileName, String content);
-
private static native String read(long nativeHandle, String fileName);
private static native void delete(long nativeHandle, String fileName);
diff --git a/bindings/java/src/main/java/org/apache/opendal/Operator.java
b/bindings/java/src/main/java/org/apache/opendal/Operator.java
index b3e0bb01..86b8066d 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -17,49 +17,27 @@
* under the License.
*/
-
package org.apache.opendal;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class Operator extends NativeObject {
- public Operator(String schema, Map<String, String> params) {
- super(newOperator(schema, params));
- }
- public void write(String fileName, String content) {
- write(nativeHandle, fileName, content);
- }
-
- public CompletableFuture<Void> writeAsync(String fileName, String content)
{
- return writeAsync(nativeHandle, fileName, content);
- }
- public String read(String s) {
- return read(nativeHandle, s);
+ public Operator(String schema, Map<String, String> map) {
+ super(constructor(schema, map));
}
- public void delete(String s) {
- delete(nativeHandle, s);
- }
- public Metadata stat(String fileName) {
- return new Metadata(stat(nativeHandle, fileName));
+ public CompletableFuture<Void> write(String fileName, String content) {
+ return write(nativeHandle, fileName, content);
}
@Override
protected native void disposeInternal(long handle);
- private static native long newOperator(String type, Map<String, String>
params);
-
- private static native void write(long nativeHandle, String fileName,
String content);
-
- private static native CompletableFuture<Void> writeAsync(long
nativeHandle, String fileName, String content);
-
- private static native String read(long nativeHandle, String fileName);
-
- private static native void delete(long nativeHandle, String fileName);
+ private static native long constructor(String schema, Map<String, String>
map);
- private static native long stat(long nativeHandle, String file);
+ private static native CompletableFuture<Void> write(long nativeHandle,
String fileName, String content);
}
diff --git a/bindings/java/src/metadata.rs b/bindings/java/src/metadata.rs
new file mode 100644
index 00000000..5a29466d
--- /dev/null
+++ b/bindings/java/src/metadata.rs
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use jni::objects::JClass;
+use jni::sys::{jboolean, jlong};
+use jni::JNIEnv;
+
+use opendal::Metadata;
+
+/// # Safety
+///
+/// This function should not be called before the Stat are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_isFile(
+ _: JNIEnv,
+ _: JClass,
+ ptr: *mut Metadata,
+) -> jboolean {
+ let metadata = &mut *ptr;
+ metadata.is_file() as jboolean
+}
+
+/// # Safety
+///
+/// This function should not be called before the Stat are ready.
+#[no_mangle]
+pub unsafe extern "system" fn
Java_org_apache_opendal_Metadata_getContentLength(
+ _: JNIEnv,
+ _: JClass,
+ ptr: *mut Metadata,
+) -> jlong {
+ let metadata = &mut *ptr;
+ metadata.content_length() as jlong
+}
+
+/// # Safety
+///
+/// This function should not be called before the Stat are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Metadata_disposeInternal(
+ _: JNIEnv,
+ _: JClass,
+ ptr: *mut Metadata,
+) {
+ drop(Box::from_raw(ptr));
+}
diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs
new file mode 100644
index 00000000..771d4e23
--- /dev/null
+++ b/bindings/java/src/operator.rs
@@ -0,0 +1,130 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use std::str::FromStr;
+
+use jni::objects::{JClass, JObject, JString, JValue};
+use jni::sys::{jlong, jobject};
+use jni::JNIEnv;
+
+use opendal::{Operator, Scheme};
+
+use crate::error::Error;
+use crate::{get_current_env, Result};
+use crate::{jmap_to_hashmap, RUNTIME};
+
+#[no_mangle]
+pub extern "system" fn Java_org_apache_opendal_Operator_constructor(
+ mut env: JNIEnv,
+ _: JClass,
+ scheme: JString,
+ map: JObject,
+) -> jlong {
+ intern_constructor(&mut env, scheme, map).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ 0
+ })
+}
+
+fn intern_constructor(env: &mut JNIEnv, scheme: JString, map: JObject) ->
Result<jlong> {
+ let scheme = Scheme::from_str(env.get_string(&scheme)?.to_str()?)?;
+ let map = jmap_to_hashmap(env, &map)?;
+ let op = Operator::via_map(scheme, map)?;
+ Ok(Box::into_raw(Box::new(op)) as jlong)
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_disposeInternal(
+ _: JNIEnv,
+ _: JClass,
+ op: *mut Operator,
+) {
+ drop(Box::from_raw(op));
+}
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_write(
+ mut env: JNIEnv,
+ _: JClass,
+ op: *mut Operator,
+ file: JString,
+ content: JString,
+) -> jobject {
+ intern_write(&mut env, op, file, content).unwrap_or_else(|e| {
+ e.throw(&mut env);
+ JObject::null().into_raw()
+ })
+}
+
+fn intern_write(
+ env: &mut JNIEnv,
+ op: *mut Operator,
+ file: JString,
+ content: JString,
+) -> Result<jobject> {
+ let op = unsafe { &mut *op };
+ let file: String = env.get_string(&file)?.into();
+ let content: String = env.get_string(&content)?.into();
+
+ let class = "java/util/concurrent/CompletableFuture";
+ let f = env.new_object(class, "()V", &[])?;
+
+ // keep the future alive, so that we can complete it later
+ // but this approach will be limited by global ref table size (65535)
+ let future = env.new_global_ref(&f)?;
+
+ let runtime = unsafe { RUNTIME.get_unchecked() };
+ runtime.spawn(async move {
+ let result = match op.write(&file, content).await {
+ Ok(()) => Ok(JObject::null()),
+ Err(err) => Err(Error::from(err)),
+ };
+ complete_future(future.as_ref(), result)
+ });
+
+ Ok(f.into_raw())
+}
+
+fn complete_future(future: &JObject, result: Result<JObject>) {
+ let mut env = unsafe { get_current_env() };
+ match result {
+ Ok(result) => env
+ .call_method(
+ future,
+ "complete",
+ "(Ljava/lang/Object;)Z",
+ &[JValue::Object(&result)],
+ )
+ .unwrap(),
+ Err(err) => {
+ let exception = err.to_exception(&mut env).unwrap();
+ env.call_method(
+ future,
+ "completeExceptionally",
+ "(Ljava/lang/Throwable;)Z",
+ &[JValue::Object(&exception)],
+ )
+ .unwrap()
+ }
+ };
+}
diff --git a/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
b/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
index 81e8538d..640f66c1 100644
--- a/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
+++ b/bindings/java/src/test/java/org/apache/opendal/AsyncStepsTest.java
@@ -41,7 +41,7 @@ public class AsyncStepsTest {
@When("Async write path {string} with content {string}")
public void async_write_path_test_with_content_hello_world(String
fileName, String content) {
- CompletableFuture<Void> f = operator.writeAsync(fileName, content);
+ CompletableFuture<Void> f = operator.write(fileName, content);
f.join();
diff --git a/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
index 2d384189..d91e6f91 100644
--- a/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
+++ b/bindings/java/src/test/java/org/apache/opendal/ExceptionTest.java
@@ -30,23 +30,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ExceptionTest {
- private Operator operator;
+ private BlockingOperator op;
@BeforeEach
public void init() {
Map<String, String> params = new HashMap<>();
params.put("root", "/tmp");
- this.operator = new Operator("Memory", params);
+ this.op = new BlockingOperator("Memory", params);
}
@AfterEach
public void clean() {
- this.operator.close();
+ this.op.close();
}
@Test
public void testStatNotExistFile() {
- final ODException exception = assertThrows(ODException.class, () ->
operator.stat("not_exist_file"));
+ final ODException exception = assertThrows(ODException.class, () ->
op.stat("not_exist_file"));
assertEquals(ODException.Code.NotFound, exception.getCode());
}
}
diff --git a/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
b/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
index 71a4a82c..ef26d128 100644
--- a/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
+++ b/bindings/java/src/test/java/org/apache/opendal/StepsTest.java
@@ -30,42 +30,42 @@ import static
org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class StepsTest {
- Operator operator;
+ BlockingOperator blockingOperator;
@Given("A new OpenDAL Blocking Operator")
public void a_new_open_dal_blocking_operator() {
Map<String, String> params = new HashMap<>();
params.put("root", "/tmp");
- this.operator = new Operator("Memory", params);
+ this.blockingOperator = new BlockingOperator("Memory", params);
}
@When("Blocking write path {string} with content {string}")
public void blocking_write_path_test_with_content_hello_world(String
fileName, String content) {
- this.operator.write(fileName, content);
+ this.blockingOperator.write(fileName, content);
}
@Then("The blocking file {string} should exist")
public void the_blocking_file_test_should_exist(String fileName) {
- Metadata metadata = this.operator.stat(fileName);
+ Metadata metadata = this.blockingOperator.stat(fileName);
assertNotNull(metadata);
}
@Then("The blocking file {string} entry mode must be file")
public void the_blocking_file_test_entry_mode_must_be_file(String
fileName) {
- Metadata metadata = this.operator.stat(fileName);
+ Metadata metadata = this.blockingOperator.stat(fileName);
assertTrue(metadata.isFile());
}
@Then("The blocking file {string} content length must be {int}")
public void the_blocking_file_test_content_length_must_be_13(String
fileName, int length) {
- Metadata metadata = this.operator.stat(fileName);
+ Metadata metadata = this.blockingOperator.stat(fileName);
assertEquals(metadata.getContentLength(), length);
}
@Then("The blocking file {string} must have content {string}")
public void the_blocking_file_test_must_have_content_hello_world(String
fileName, String content) {
- String readContent = this.operator.read(fileName);
+ String readContent = this.blockingOperator.read(fileName);
assertEquals(content, readContent);
}
}