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

asf-gitbox-commits pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 7eb41f2d3f feat(DBF): fix read and write support for deleted dbf 
records
7eb41f2d3f is described below

commit 7eb41f2d3f4311ce3ddab7023c5553cf377481fe
Author: jsorel <[email protected]>
AuthorDate: Wed Sep 9 11:00:44 2026 +0200

    feat(DBF): fix read and write support for deleted dbf records
---
 .../sis/storage/shapefile/dbf/DBFReader.java       |  7 +++-
 .../sis/storage/shapefile/dbf/DBFWriter.java       | 14 +++++++
 .../sis/storage/shapefile/dbf/DBFIOTest.java       | 48 ++++++++++++++++++++++
 3 files changed, 68 insertions(+), 1 deletion(-)

diff --git 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
index e5c128738f..acca67a986 100644
--- 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
@@ -69,12 +69,17 @@ public final class DBFReader implements AutoCloseable {
 
     /**
      * Move channel to given position.
+     * The number of read records is recomputed from the given position,
+     * this allows to continue reading sequentially after the jump.
      *
      * @param position new position
      * @throws IOException if the stream cannot be moved to the given position.
      */
     public void moveToOffset(long position) throws IOException {
         channel.seek(position);
+        if (header.recordSize > 0) {
+            nbRead = Math.toIntExact(Math.max(0, position - header.headerSize) 
/ header.recordSize);
+        }
     }
 
     /**
@@ -94,7 +99,7 @@ public final class DBFReader implements AutoCloseable {
 
         final int marker = channel.readUnsignedByte();
         if (marker == TAG_DELETED) {
-            channel.seek(channel.getStreamPosition() + header.recordSize);
+            channel.seek(channel.getStreamPosition() + header.recordSize - 1); 
//-1 for the delete tag
             return DELETED_RECORD;
         } else if (marker == TAG_EOF) {
             return null;
diff --git 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
index ea7acbd53e..9fdde1ff5e 100644
--- 
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFWriter.java
@@ -71,6 +71,20 @@ public final class DBFWriter implements AutoCloseable{
         writtenNbRecord++;
     }
 
+    /**
+     * Write a record marked as deleted.
+     *
+     * Field values are blanks, the deleted record is counted in the
+     * number of records written in the header.
+     *
+     * @throws IOException If an I/O error occurs
+     */
+    public void writeDeletedRecord() throws IOException {
+        channel.writeByte(DBFReader.TAG_DELETED);
+        channel.repeat(header.recordSize - 1, (byte) ' ');  //-1 for the 
delete tag
+        writtenNbRecord++;
+    }
+
     /**
      * Write end of file tag, update written number of record and release 
resources.
      *
diff --git 
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
 
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
index cf258ab64a..f8bd7d0e47 100644
--- 
a/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
+++ 
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
@@ -51,6 +51,13 @@ public class DBFIOTest {
         return cdi;
     }
 
+    private ChannelDataInput openRead(Path path) throws DataStoreException {
+        final StorageConnector cnx = new StorageConnector(path);
+        final ChannelDataInput cdi = cnx.getStorageAs(ChannelDataInput.class);
+        cnx.closeAllExcept(cdi);
+        return cdi;
+    }
+
     private ChannelDataOutput openWrite(Path path) throws DataStoreException, 
IOException {
         final StorageConnector cnx = new StorageConnector(path);
         cnx.setOption(OptionKey.OPEN_OPTIONS, new 
OpenOption[]{StandardOpenOption.WRITE, StandardOpenOption.CREATE, 
StandardOpenOption.TRUNCATE_EXISTING});
@@ -155,6 +162,47 @@ public class DBFIOTest {
         }
     }
 
+    /**
+     * Test writing and reading a record marked as deleted.
+     * Such record preserves the position of the following records.
+     */
+    @Test
+    public void deletedRecordTest() throws DataStoreException, IOException {
+        final String path = "/org/apache/sis/storage/shapefile/point.dbf";
+        final DBFHeader header;
+        final Object[] record1;
+        final Object[] record2;
+        try (DBFReader reader = new DBFReader(openRead(path), 
StandardCharsets.US_ASCII, null, null)) {
+            header  = reader.getHeader();
+            record1 = reader.next();
+            record2 = reader.next();
+        }
+
+        final Path tempFile = Files.createTempFile("tmp", ".dbf");
+        try {
+            //write a present, a deleted then a present record
+            try (DBFWriter writer = new DBFWriter(openWrite(tempFile))) {
+                writer.writeHeader(header);
+                writer.writeRecord(record1);
+                writer.writeDeletedRecord();
+                writer.writeRecord(record2);
+            }
+
+            try (DBFReader reader = new DBFReader(openRead(tempFile), 
StandardCharsets.US_ASCII, null, null)) {
+                //the deleted record is counted in the header
+                assertEquals(3, reader.getHeader().nbRecord);
+                assertEquals(header.recordSize, reader.getHeader().recordSize);
+                assertArrayEquals(record1, reader.next());
+                assertSame(DBFReader.DELETED_RECORD, reader.next());
+                //the record after the deleted one must still be properly 
aligned
+                assertArrayEquals(record2, reader.next());
+                assertNull(reader.next());
+            }
+        } finally {
+            Files.delete(tempFile);
+        }
+    }
+
     /**
      * Test reading only selected fields.
      */

Reply via email to