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);
+ }
}