diff --git a/bindings/java/src/async_operator.rs b/bindings/java/src/async_operator.rs index 15a260d5e5ca..b734d7181ad9 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) -> 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 { 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> { - let obs = op.list(&path).await?; +fn make_entries<'local>(entries: Result>) -> Result> { + 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 cbb32b9f8340..444dfbe5ca94 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 CompletableFuture removeAll(String path) { } public CompletableFuture> list(String path) { - final long requestId = list(nativeHandle, executorHandle, path); + return list(path, ListOptions.builder().build()); + } + + public CompletableFuture> list(String path, ListOptions options) { + final long requestId = list(nativeHandle, executorHandle, path, options); final CompletableFuture result = AsyncRegistry.take(requestId); return Objects.requireNonNull(result).thenApplyAsync(Arrays::asList); } @@ -310,5 +314,5 @@ private static native long write( 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 000000000000..556b1d7aff2c --- /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 9489509bad3a..f908eda3d5e7 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 void removeAll(String path) { } public List list(String path) { - return Arrays.asList(list(nativeHandle, path)); + return list(path, ListOptions.builder().build()); + } + + public List list(String path, ListOptions options) { + return Arrays.asList(list(nativeHandle, path, options)); } @Override @@ -152,5 +156,5 @@ public List list(String path) { 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 db0280129790..76b6447fcf50 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 { +fn intern_list( + env: &mut JNIEnv, + op: &mut BlockingOperator, + path: JString, + options: JObject, +) -> Result { 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 bf0fd90d9258..cb3533a49401 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.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 @@ public void testRemoveAll() { 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 entries = asyncOp() + .list(dir, ListOptions.builder().recursive(true).build()) + .join(); + assertThat(entries).hasSize(4); + + final List 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 8d43249137b0..e77af8ab6493 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.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 void testBlockingRemoveAll() { 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 entries = + op().list(dir, ListOptions.builder().recursive(true).build()); + assertThat(entries).hasSize(4); + + final List noRecursiveEntries = + op().list(dir, ListOptions.builder().recursive(false).build()); + assertThat(noRecursiveEntries).hasSize(3); + } }