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, &params);
-    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);
     }
 }


Reply via email to