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.
*/