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/incubator-opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 0995d4014 feat(binding/java): add `rename` support (#3238)
0995d4014 is described below

commit 0995d4014c3f1fa016fce22f03126a86e781938c
Author: G-XD <[email protected]>
AuthorDate: Mon Oct 9 02:08:55 2023 +0800

    feat(binding/java): add `rename` support (#3238)
    
    * test(binding/java): add rename test
    
    * test(binding/java): remove copy capability dependency in rename test
---
 bindings/java/src/blocking_operator.rs             |  28 ++
 .../java/org/apache/opendal/BlockingOperator.java  |   6 +
 .../src/main/java/org/apache/opendal/Operator.java |   7 +
 bindings/java/src/operator.rs                      |  41 +++
 .../test/behavior/AbstractBehaviorTest.java        | 303 +++++++++++++++++++++
 5 files changed, 385 insertions(+)

diff --git a/bindings/java/src/blocking_operator.rs 
b/bindings/java/src/blocking_operator.rs
index 1f4e9240d..fd7fffa23 100644
--- a/bindings/java/src/blocking_operator.rs
+++ b/bindings/java/src/blocking_operator.rs
@@ -178,3 +178,31 @@ fn intern_copy(
 
     Ok(op.copy(&source_path, &target_path)?)
 }
+
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_BlockingOperator_rename(
+    mut env: JNIEnv,
+    _: JClass,
+    op: *mut BlockingOperator,
+    source_path: JString,
+    target_path: JString,
+) {
+    intern_rename(&mut env, &mut *op, source_path, 
target_path).unwrap_or_else(|e| {
+        e.throw(&mut env);
+    })
+}
+
+fn intern_rename(
+    env: &mut JNIEnv,
+    op: &mut BlockingOperator,
+    source_path: JString,
+    target_path: JString,
+) -> Result<()> {
+    let source_path = jstring_to_string(env, &source_path)?;
+    let target_path = jstring_to_string(env, &target_path)?;
+
+    Ok(op.rename(&source_path, &target_path)?)
+}
diff --git 
a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java 
b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
index 811f3d6b9..03e8ba8b7 100644
--- a/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/BlockingOperator.java
@@ -78,6 +78,10 @@ public class BlockingOperator extends NativeObject {
         copy(nativeHandle, sourcePath, targetPath);
     }
 
+    public void rename(String sourcePath, String targetPath) {
+        rename(nativeHandle, sourcePath, targetPath);
+    }
+
     @Override
     protected native void disposeInternal(long handle);
 
@@ -92,4 +96,6 @@ public class BlockingOperator extends NativeObject {
     private static native long createDir(long nativeHandle, String path);
 
     private static native long copy(long nativeHandle, String sourcePath, 
String targetPath);
+
+    private static native long rename(long nativeHandle, String sourcePath, 
String targetPath);
 }
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 8c791aedd..7ca7bccd2 100644
--- a/bindings/java/src/main/java/org/apache/opendal/Operator.java
+++ b/bindings/java/src/main/java/org/apache/opendal/Operator.java
@@ -187,6 +187,11 @@ public class Operator extends NativeObject {
         return AsyncRegistry.take(requestId);
     }
 
+    public CompletableFuture<Void> rename(String sourcePath, String 
targetPath) {
+        final long requestId = rename(nativeHandle, sourcePath, targetPath);
+        return AsyncRegistry.take(requestId);
+    }
+
     @Override
     protected native void disposeInternal(long handle);
 
@@ -215,4 +220,6 @@ public class Operator extends NativeObject {
     private static native long createDir(long nativeHandle, String path);
 
     private static native long copy(long nativeHandle, String sourcePath, 
String targetPath);
+
+    private static native long rename(long nativeHandle, String sourcePath, 
String targetPath);
 }
diff --git a/bindings/java/src/operator.rs b/bindings/java/src/operator.rs
index 74e0b9c30..97fbaaa9a 100644
--- a/bindings/java/src/operator.rs
+++ b/bindings/java/src/operator.rs
@@ -372,6 +372,47 @@ async fn do_copy(op: &mut Operator, source_path: String, 
target_path: String) ->
     Ok(op.copy(&source_path, &target_path).await?)
 }
 
+/// # Safety
+///
+/// This function should not be called before the Operator are ready.
+#[no_mangle]
+pub unsafe extern "system" fn Java_org_apache_opendal_Operator_rename(
+    mut env: JNIEnv,
+    _: JClass,
+    op: *mut Operator,
+    source_path: JString,
+    target_path: JString,
+) -> jlong {
+    intern_rename(&mut env, op, source_path, target_path).unwrap_or_else(|e| {
+        e.throw(&mut env);
+        0
+    })
+}
+
+fn intern_rename(
+    env: &mut JNIEnv,
+    op: *mut Operator,
+    source_path: JString,
+    target_path: JString,
+) -> Result<jlong> {
+    let op = unsafe { &mut *op };
+    let id = request_id(env)?;
+
+    let source_path = jstring_to_string(env, &source_path)?;
+    let target_path = jstring_to_string(env, &target_path)?;
+
+    unsafe { get_global_runtime() }.spawn(async move {
+        let result = do_rename(op, source_path, target_path).await;
+        complete_future(id, result.map(|_| JValueOwned::Void))
+    });
+
+    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 are ready.
diff --git 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
index ee7c5721b..b88290f65 100644
--- 
a/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
+++ 
b/bindings/java/src/test/java/org/apache/opendal/test/behavior/AbstractBehaviorTest.java
@@ -375,6 +375,158 @@ public abstract class AbstractBehaviorTest {
         }
     }
 
+    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+    @Nested
+    class AsyncRenameTest {
+        @BeforeAll
+        public void precondition() {
+            final Capability capability = operator.info.fullCapability;
+            assumeTrue(capability.read && capability.write && 
capability.rename);
+        }
+
+        /**
+         * Rename a file and test with stat.
+         */
+        @Test
+        public void testRenameFile() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] content = generateBytes();
+
+            operator.write(sourcePath, content).join();
+
+            final String targetPath = UUID.randomUUID().toString();
+
+            operator.rename(sourcePath, targetPath).join();
+
+            assertThatThrownBy(() -> operator.stat(sourcePath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            assertThat(operator.read(targetPath).join()).isEqualTo(content);
+
+            operator.delete(sourcePath).join();
+            operator.delete(targetPath).join();
+        }
+
+        /**
+         * Rename a nonexistent source should return an error.
+         */
+        @Test
+        public void testRenameNonExistingSource() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final String targetPath = UUID.randomUUID().toString();
+
+            assertThatThrownBy(() -> operator.rename(sourcePath, 
targetPath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+        }
+
+        /**
+         * Rename a dir as source should return an error.
+         */
+        @Test
+        public void testRenameSourceDir() {
+            final String sourcePath = String.format("%s/", 
UUID.randomUUID().toString());
+            final String targetPath = UUID.randomUUID().toString();
+
+            operator.createDir(sourcePath).join();
+
+            assertThatThrownBy(() -> operator.rename(sourcePath, 
targetPath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+            operator.delete(sourcePath).join();
+        }
+
+        /**
+         * Rename to a dir should return an error.
+         */
+        @Test
+        public void testRenameTargetDir() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] content = generateBytes();
+
+            operator.write(sourcePath, content).join();
+
+            final String targetPath = String.format("%s/", 
UUID.randomUUID().toString());
+
+            operator.createDir(targetPath).join();
+
+            assertThatThrownBy(() -> operator.rename(sourcePath, 
targetPath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+            operator.delete(sourcePath).join();
+            operator.delete(targetPath).join();
+        }
+
+        /**
+         * Rename a file to self should return an error.
+         */
+        @Test
+        public void testRenameSelf() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] content = generateBytes();
+
+            operator.write(sourcePath, content).join();
+
+            assertThatThrownBy(() -> operator.rename(sourcePath, 
sourcePath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsSameFile));
+
+            operator.delete(sourcePath).join();
+        }
+
+        /**
+         * Rename to a nested path, parent path should be created successfully.
+         */
+        @Test
+        public void testRenameNested() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] content = generateBytes();
+
+            operator.write(sourcePath, content).join();
+
+            final String targetPath = String.format(
+                    "%s/%s/%s",
+                    UUID.randomUUID().toString(),
+                    UUID.randomUUID().toString(),
+                    UUID.randomUUID().toString());
+
+            operator.rename(sourcePath, targetPath).join();
+
+            assertThatThrownBy(() -> operator.stat(sourcePath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            assertThat(operator.read(targetPath).join()).isEqualTo(content);
+
+            operator.delete(sourcePath).join();
+            operator.delete(targetPath).join();
+        }
+
+        /**
+         * Rename to a exist path should overwrite successfully.
+         */
+        @Test
+        public void testRenameOverwrite() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            operator.write(sourcePath, sourceContent).join();
+
+            final String targetPath = UUID.randomUUID().toString();
+            final byte[] targetContent = generateBytes();
+            assertNotEquals(sourceContent, targetContent);
+
+            operator.write(targetPath, targetContent).join();
+
+            operator.rename(sourcePath, targetPath).join();
+
+            assertThatThrownBy(() -> operator.stat(sourcePath).join())
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            
assertThat(operator.read(targetPath).join()).isEqualTo(sourceContent);
+
+            operator.delete(sourcePath).join();
+            operator.delete(targetPath).join();
+        }
+    }
+
     @TestInstance(TestInstance.Lifecycle.PER_CLASS)
     @Nested
     class BlockingWriteTest {
@@ -605,6 +757,157 @@ public abstract class AbstractBehaviorTest {
         }
     }
 
+    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+    @Nested
+    class BlockingRenameTest {
+        @BeforeAll
+        public void precondition() {
+            final Capability capability = blockingOperator.info.fullCapability;
+            assumeTrue(capability.read && capability.write && 
capability.blocking && capability.rename);
+        }
+
+        /**
+         * Rename a file and test with stat.
+         */
+        @Test
+        public void testBlockingRenameFile() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            blockingOperator.write(sourcePath, sourceContent);
+
+            final String targetPath = UUID.randomUUID().toString();
+
+            blockingOperator.rename(sourcePath, targetPath);
+
+            assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            
assertThat(blockingOperator.stat(targetPath).getContentLength()).isEqualTo(sourceContent.length);
+
+            blockingOperator.delete(sourcePath);
+            blockingOperator.delete(targetPath);
+        }
+
+        /**
+         * Rename a nonexistent source should return an error.
+         */
+        @Test
+        public void testBlockingRenameNonExistingSource() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final String targetPath = UUID.randomUUID().toString();
+
+            assertThatThrownBy(() -> blockingOperator.rename(sourcePath, 
targetPath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+        }
+
+        /**
+         * Rename a dir as source should return an error.
+         */
+        @Test
+        public void testBlockingRenameSourceDir() {
+            final String sourcePath = String.format("%s/", 
UUID.randomUUID().toString());
+            final String targetPath = UUID.randomUUID().toString();
+
+            blockingOperator.createDir(sourcePath);
+
+            assertThatThrownBy(() -> blockingOperator.rename(sourcePath, 
targetPath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+        }
+
+        /**
+         * Rename to a dir should return an error.
+         */
+        @Test
+        public void testBlockingRenameTargetDir() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            blockingOperator.write(sourcePath, sourceContent);
+
+            final String targetPath = String.format("%s/", 
UUID.randomUUID().toString());
+
+            blockingOperator.createDir(targetPath);
+
+            assertThatThrownBy(() -> blockingOperator.rename(sourcePath, 
targetPath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsADirectory));
+
+            blockingOperator.delete(sourcePath);
+            blockingOperator.delete(targetPath);
+        }
+
+        /**
+         * Rename a file to self should return an error.
+         */
+        @Test
+        public void testBlockingRenameSelf() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            blockingOperator.write(sourcePath, sourceContent);
+
+            assertThatThrownBy(() -> blockingOperator.rename(sourcePath, 
sourcePath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.IsSameFile));
+
+            blockingOperator.delete(sourcePath);
+        }
+
+        /**
+         * Rename to a nested path, parent path should be created successfully.
+         */
+        @Test
+        public void testBlockingRenameNested() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            blockingOperator.write(sourcePath, sourceContent);
+
+            final String targetPath = String.format(
+                    "%s/%s/%s",
+                    UUID.randomUUID().toString(),
+                    UUID.randomUUID().toString(),
+                    UUID.randomUUID().toString());
+
+            blockingOperator.rename(sourcePath, targetPath);
+
+            assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            
assertThat(blockingOperator.read(targetPath)).isEqualTo(sourceContent);
+
+            blockingOperator.delete(sourcePath);
+            blockingOperator.delete(targetPath);
+        }
+
+        /**
+         * Rename to a exist path should overwrite successfully.
+         */
+        @Test
+        public void testBlockingRenameOverwrite() {
+            final String sourcePath = UUID.randomUUID().toString();
+            final byte[] sourceContent = generateBytes();
+
+            blockingOperator.write(sourcePath, sourceContent);
+
+            final String targetPath = UUID.randomUUID().toString();
+            final byte[] targetContent = generateBytes();
+
+            assertNotEquals(sourceContent, targetContent);
+
+            blockingOperator.write(targetPath, targetContent);
+
+            blockingOperator.rename(sourcePath, targetPath);
+
+            assertThatThrownBy(() -> blockingOperator.stat(sourcePath))
+                    
.is(OpenDALExceptionCondition.ofAsync(OpenDALException.Code.NotFound));
+
+            
assertThat(blockingOperator.read(targetPath)).isEqualTo(sourceContent);
+
+            blockingOperator.delete(sourcePath);
+            blockingOperator.delete(targetPath);
+        }
+    }
+
     /**
      * Generates a byte array of random content.
      */

Reply via email to