This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 2f404e4ea feat(binding/java): Add list with recursive support  (#5718)
2f404e4ea is described below

commit 2f404e4eae333245b0ca2c02298228d7f596e0cf
Author: Will Li <[email protected]>
AuthorDate: Thu Mar 20 15:02:21 2025 +0800

    feat(binding/java): Add list with recursive support  (#5718)
    
    * add list with recursive support with java binding
    
    * license and format
    
    * revert vscode change
    
    * fix error
    
    * remove complete layer
    
    * fix
    
    * tidy up
    
    Signed-off-by: tison <[email protected]>
    
    * fmt
    
    Signed-off-by: tison <[email protected]>
    
    ---------
    
    Signed-off-by: tison <[email protected]>
    Co-authored-by: tison <[email protected]>
---
 bindings/java/src/async_operator.rs                | 101 +++++++++++----------
 .../java/org/apache/opendal/AsyncOperator.java     |   8 +-
 .../main/java/org/apache/opendal/ListOptions.java  |  37 ++++++++
 .../src/main/java/org/apache/opendal/Operator.java |   8 +-
 bindings/java/src/operator.rs                      |  23 +++--
 .../opendal/test/behavior/AsyncListTest.java       |  27 ++++++
 .../opendal/test/behavior/BlockingListTest.java    |  25 +++++
 7 files changed, 171 insertions(+), 58 deletions(-)

diff --git a/bindings/java/src/async_operator.rs 
b/bindings/java/src/async_operator.rs
index 15a260d5e..b734d7181 100644
--- a/bindings/java/src/async_operator.rs
+++ b/bindings/java/src/async_operator.rs
@@ -30,6 +30,7 @@ use jni::sys::jsize;
 use jni::JNIEnv;
 use opendal::layers::BlockingLayer;
 use opendal::raw::PresignedRequest;
+use opendal::Entry;
 use opendal::Operator;
 use opendal::Scheme;
 
@@ -175,7 +176,7 @@ fn intern_write(
         let result = write_op
             .await
             .map(|_| JValueOwned::Void)
-            .map_err(|e| e.into());
+            .map_err(Into::into);
         complete_future(id, result)
     });
 
@@ -214,21 +215,18 @@ fn intern_append(
     let content = env.convert_byte_array(content)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_append(op, path, content).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .write_with(&path, content)
+            .append(true)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_append(op: &mut Operator, path: String, content: Vec<u8>) -> 
Result<()> {
-    Ok(op
-        .write_with(&path, content)
-        .append(true)
-        .await
-        .map(|_| ())?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -344,17 +342,17 @@ fn intern_delete(
     let path = jstring_to_string(env, &path)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_delete(op, path).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .delete(&path)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_delete(op: &mut Operator, path: String) -> Result<()> {
-    Ok(op.delete(&path).await?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -417,17 +415,17 @@ fn intern_create_dir(
     let path = jstring_to_string(env, &path)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_create_dir(op, path).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .create_dir(&path)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_create_dir(op: &mut Operator, path: String) -> Result<()> {
-    Ok(op.create_dir(&path).await?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -460,17 +458,17 @@ fn intern_copy(
     let target_path = jstring_to_string(env, &target_path)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_copy(op, source_path, target_path).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .copy(&source_path, &target_path)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_copy(op: &mut Operator, source_path: String, target_path: String) 
-> Result<()> {
-    Ok(op.copy(&source_path, &target_path).await?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -503,17 +501,17 @@ fn intern_rename(
     let target_path = jstring_to_string(env, &target_path)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_rename(op, source_path, target_path).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .rename(&source_path, &target_path)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_rename(op: &mut Operator, source_path: String, target_path: 
String) -> Result<()> {
-    Ok(op.rename(&source_path, &target_path).await?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -543,17 +541,17 @@ fn intern_remove_all(
     let path = jstring_to_string(env, &path)?;
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_remove_all(op, path).await;
-        complete_future(id, result.map(|_| JValueOwned::Void))
+        let result = op
+            .remove_all(&path)
+            .await
+            .map(|_| JValueOwned::Void)
+            .map_err(Into::into);
+        complete_future(id, result)
     });
 
     Ok(id)
 }
 
-async fn do_remove_all(op: &mut Operator, path: String) -> Result<()> {
-    Ok(op.remove_all(&path).await?)
-}
-
 /// # Safety
 ///
 /// This function should not be called before the Operator is ready.
@@ -564,8 +562,9 @@ pub unsafe extern "system" fn 
Java_org_apache_opendal_AsyncOperator_list(
     op: *mut Operator,
     executor: *const Executor,
     path: JString,
+    options: JObject,
 ) -> jlong {
-    intern_list(&mut env, op, executor, path).unwrap_or_else(|e| {
+    intern_list(&mut env, op, executor, path, options).unwrap_or_else(|e| {
         e.throw(&mut env);
         0
     })
@@ -576,32 +575,38 @@ fn intern_list(
     op: *mut Operator,
     executor: *const Executor,
     path: JString,
+    options: JObject,
 ) -> Result<jlong> {
     let op = unsafe { &mut *op };
     let id = request_id(env)?;
 
     let path = jstring_to_string(env, &path)?;
+    let recursive = env.call_method(&options, "isRecursive", "()Z", &[])?.z()?;
+
+    let mut list_op = op.list_with(&path);
+    list_op = list_op.recursive(recursive);
 
     executor_or_default(env, executor)?.spawn(async move {
-        let result = do_list(op, path).await;
+        let entries = list_op.await.map_err(Into::into);
+        let result = make_entries(entries);
         complete_future(id, result.map(JValueOwned::Object))
     });
 
     Ok(id)
 }
 
-async fn do_list<'local>(op: &mut Operator, path: String) -> 
Result<JObject<'local>> {
-    let obs = op.list(&path).await?;
+fn make_entries<'local>(entries: Result<Vec<Entry>>) -> 
Result<JObject<'local>> {
+    let entries = entries?;
 
     let mut env = unsafe { get_current_env() };
     let jarray = env.new_object_array(
-        obs.len() as jsize,
+        entries.len() as jsize,
         "org/apache/opendal/Entry",
         JObject::null(),
     )?;
 
-    for (idx, entry) in obs.iter().enumerate() {
-        let entry = make_entry(&mut env, entry.to_owned())?;
+    for (idx, entry) in entries.into_iter().enumerate() {
+        let entry = make_entry(&mut env, entry)?;
         env.set_object_array_element(&jarray, idx as jsize, entry)?;
     }
 
diff --git a/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java 
b/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
index cbb32b9f8..444dfbe5c 100644
--- a/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/AsyncOperator.java
@@ -269,7 +269,11 @@ public class AsyncOperator extends NativeObject {
     }
 
     public CompletableFuture<List<Entry>> list(String path) {
-        final long requestId = list(nativeHandle, executorHandle, path);
+        return list(path, ListOptions.builder().build());
+    }
+
+    public CompletableFuture<List<Entry>> list(String path, ListOptions 
options) {
+        final long requestId = list(nativeHandle, executorHandle, path, 
options);
         final CompletableFuture<Entry[]> result = 
AsyncRegistry.take(requestId);
         return Objects.requireNonNull(result).thenApplyAsync(Arrays::asList);
     }
@@ -310,5 +314,5 @@ public class AsyncOperator extends NativeObject {
 
     private static native long removeAll(long nativeHandle, long 
executorHandle, String path);
 
-    private static native long list(long nativeHandle, long executorHandle, 
String path);
+    private static native long list(long nativeHandle, long executorHandle, 
String path, ListOptions options);
 }
diff --git a/bindings/java/src/main/java/org/apache/opendal/ListOptions.java 
b/bindings/java/src/main/java/org/apache/opendal/ListOptions.java
new file mode 100644
index 000000000..556b1d7af
--- /dev/null
+++ b/bindings/java/src/main/java/org/apache/opendal/ListOptions.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.opendal;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ListOptions {
+
+    /**
+     * Return files in sub-directory as well.
+     */
+    private boolean recursive;
+}
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 9489509ba..f908eda3d 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -128,7 +128,11 @@ public class Operator extends NativeObject {
     }
 
     public List<Entry> list(String path) {
-        return Arrays.asList(list(nativeHandle, path));
+        return list(path, ListOptions.builder().build());
+    }
+
+    public List<Entry> list(String path, ListOptions options) {
+        return Arrays.asList(list(nativeHandle, path, options));
     }
 
     @Override
@@ -152,5 +156,5 @@ public class Operator extends NativeObject {
 
     private static native void removeAll(long op, String path);
 
-    private static native Entry[] list(long op, String path);
+    private static native Entry[] list(long op, String path, ListOptions 
options);
 }
diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs
index db0280129..76b6447fc 100644
--- a/bindings/java/src/operator.rs
+++ b/bindings/java/src/operator.rs
@@ -277,25 +277,36 @@ pub unsafe extern "system" fn 
Java_org_apache_opendal_Operator_list(
     _: JClass,
     op: *mut BlockingOperator,
     path: JString,
+    options: JObject,
 ) -> jobjectArray {
-    intern_list(&mut env, &mut *op, path).unwrap_or_else(|e| {
+    intern_list(&mut env, &mut *op, path, options).unwrap_or_else(|e| {
         e.throw(&mut env);
         JObject::default().into_raw()
     })
 }
 
-fn intern_list(env: &mut JNIEnv, op: &mut BlockingOperator, path: JString) -> 
Result<jobjectArray> {
+fn intern_list(
+    env: &mut JNIEnv,
+    op: &mut BlockingOperator,
+    path: JString,
+    options: JObject,
+) -> Result<jobjectArray> {
     let path = jstring_to_string(env, &path)?;
-    let obs = op.list(&path)?;
+    let recursive = env.call_method(&options, "isRecursive", "()Z", &[])?.z()?;
+
+    let mut list_op = op.list_with(&path);
+    list_op = list_op.recursive(recursive);
+
+    let entries = list_op.call()?;
 
     let jarray = env.new_object_array(
-        obs.len() as jsize,
+        entries.len() as jsize,
         "org/apache/opendal/Entry",
         JObject::null(),
     )?;
 
-    for (idx, entry) in obs.iter().enumerate() {
-        let entry = make_entry(env, entry.to_owned())?;
+    for (idx, entry) in entries.into_iter().enumerate() {
+        let entry = make_entry(env, entry)?;
         env.set_object_array_element(&jarray, idx as jsize, entry)?;
     }
 
diff --git 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncListTest.java
 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncListTest.java
index bf0fd90d9..cb3533a49 100644
--- 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncListTest.java
+++ 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AsyncListTest.java
@@ -30,6 +30,7 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 import org.apache.opendal.Capability;
 import org.apache.opendal.Entry;
+import org.apache.opendal.ListOptions;
 import org.apache.opendal.Metadata;
 import org.apache.opendal.OpenDALException;
 import org.apache.opendal.test.condition.OpenDALExceptionCondition;
@@ -224,4 +225,30 @@ class AsyncListTest extends BehaviorTestBase {
 
         asyncOp().removeAll(parent + "/").join();
     }
+
+    @Test
+    public void testListRecursive() {
+        final String dir = String.format("%s/%s/", UUID.randomUUID(), 
UUID.randomUUID());
+        final String fileName = UUID.randomUUID().toString();
+        final String filePath = String.format("%s%s", dir, fileName);
+        final String dirName = String.format("%s/", UUID.randomUUID());
+        final String dirPath = String.format("%s%s", dir, dirName);
+        final String content = "test_list_nested_dir";
+        final String nestedFile = String.format("%s%s", dirPath, 
UUID.randomUUID());
+
+        asyncOp().createDir(dir).join();
+        asyncOp().write(filePath, content).join();
+        asyncOp().createDir(dirPath).join();
+        asyncOp().write(nestedFile, content).join();
+
+        final List<Entry> entries = asyncOp()
+                .list(dir, ListOptions.builder().recursive(true).build())
+                .join();
+        assertThat(entries).hasSize(4);
+
+        final List<Entry> noRecursiveEntries = asyncOp()
+                .list(dir, ListOptions.builder().recursive(false).build())
+                .join();
+        assertThat(noRecursiveEntries).hasSize(3);
+    }
 }
diff --git 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingListTest.java
 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingListTest.java
index 8d4324913..e77af8ab6 100644
--- 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingListTest.java
+++ 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/BlockingListTest.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.UUID;
 import org.apache.opendal.Capability;
 import org.apache.opendal.Entry;
+import org.apache.opendal.ListOptions;
 import org.apache.opendal.Metadata;
 import org.apache.opendal.OpenDALException;
 import org.apache.opendal.test.condition.OpenDALExceptionCondition;
@@ -108,4 +109,28 @@ public class BlockingListTest extends BehaviorTestBase {
 
         op().removeAll(parent + "/");
     }
+
+    @Test
+    public void testListRecursive() {
+        final String dir = String.format("%s/%s/", UUID.randomUUID(), 
UUID.randomUUID());
+        final String fileName = UUID.randomUUID().toString();
+        final String filePath = String.format("%s%s", dir, fileName);
+        final String dirName = String.format("%s/", UUID.randomUUID());
+        final String dirPath = String.format("%s%s", dir, dirName);
+        final String content = "test_list_nested_dir";
+        final String nestedFile = String.format("%s%s", dirPath, 
UUID.randomUUID());
+
+        op().createDir(dir);
+        op().write(filePath, content);
+        op().createDir(dirPath);
+        op().write(nestedFile, content);
+
+        final List<Entry> entries =
+                op().list(dir, ListOptions.builder().recursive(true).build());
+        assertThat(entries).hasSize(4);
+
+        final List<Entry> noRecursiveEntries =
+                op().list(dir, ListOptions.builder().recursive(false).build());
+        assertThat(noRecursiveEntries).hasSize(3);
+    }
 }

Reply via email to