This is an automated email from the ASF dual-hosted git repository.
jsorel 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 f2347a9fbe feat(shapefile): add dbf read support
f2347a9fbe is described below
commit f2347a9fbe11d77cda16fcc95a86ddbb40680942
Author: jsorel <[email protected]>
AuthorDate: Tue Oct 31 15:45:09 2023 +0100
feat(shapefile): add dbf read support
---
.../main/module-info.java | 1 +
.../apache/sis/storage/shapefile/dbf/DBFField.java | 120 ++++++++++++
.../sis/storage/shapefile/dbf/DBFFieldEncoder.java | 206 +++++++++++++++++++++
.../sis/storage/shapefile/dbf/DBFHeader.java | 78 ++++++++
.../sis/storage/shapefile/dbf/DBFReader.java | 87 +++++++++
.../sis/storage/shapefile/dbf/DBFRecord.java} | 21 ++-
.../sis/storage/shapefile/dbf/DBFIOTest.java | 104 +++++++++++
7 files changed, 607 insertions(+), 10 deletions(-)
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
index 2aad9ecdde..d9cd721f2f 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
+++ b/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
@@ -29,4 +29,5 @@ module org.apache.sis.storage.shapefile {
exports org.apache.sis.storage.shapefile;
exports org.apache.sis.storage.shapefile.cpg;
exports org.apache.sis.storage.shapefile.shp;
+ exports org.apache.sis.storage.shapefile.dbf;
}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
new file mode 100644
index 0000000000..ca630ec6cb
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFField.java
@@ -0,0 +1,120 @@
+/*
+ * 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.sis.storage.shapefile.dbf;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Date;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.util.ArraysExt;
+
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class DBFField {
+
+ /**
+ * Binary, String : b or B
+ */
+ public static final int TYPE_BINARY = 'b';
+ /**
+ * Characters : c or C
+ */
+ public static final int TYPE_CHAR = 'c';
+ /**
+ * Date : d or D
+ */
+ public static final int TYPE_DATE = 'd';
+ /**
+ * Numeric : n or N
+ */
+ public static final int TYPE_NUMBER = 'n';
+ /**
+ * Logical : l or L
+ */
+ public static final int TYPE_LOGIC = 'l';
+ /**
+ * Memo, String : m or M
+ */
+ public static final int TYPE_MEMO = 'm';
+ /**
+ * TimeStamp : 8 bytes, two longs, first for date, second for time.
+ * The date is the number of days since 01/01/4713 BC.
+ * Time is hours * 3600000L + minutes * 60000L + Seconds * 1000L
+ */
+ public static final int TYPE_TIMESTAMP = '@';
+ /**
+ * Long : i or I on 4 bytes, first bit is the sign, 0 = negative
+ */
+ public static final int TYPE_LONG = 'i';
+ /**
+ * Autoincrement : same as Long
+ */
+ public static final int TYPE_INC = '+';
+ /**
+ * Floats : f or F
+ */
+ public static final int TYPE_FLOAT = 'f';
+ /**
+ * Double : o or O, real double on 8bytes, not string encoded
+ */
+ public static final int TYPE_DOUBLE = 'o';
+ /**
+ * OLE : g or G
+ */
+ public static final int TYPE_OLE = 'g';
+
+ public String fieldName;
+ public int fieldType;
+ public int fieldAddress;
+ public int fieldLength;
+ public int fieldLDecimals;
+
+ private DBFFieldEncoder encoder;
+
+ public void read(ChannelDataInput channel, Charset charset) throws
IOException {
+ byte[] n = channel.readBytes(11);
+ int nameSize = 0;
+ for (int i = 0; i < n.length && n[i] != 0; i++,nameSize++);
+
+ fieldName = new String(n, 0, nameSize);
+ fieldType =
Character.valueOf(((char)channel.readUnsignedByte())).toString().toLowerCase().charAt(0);
+ fieldAddress = channel.readInt();
+ fieldLength = channel.readUnsignedByte();
+ fieldLDecimals = channel.readUnsignedByte();
+ channel.skipBytes(14);
+
+ encoder = DBFFieldEncoder.getEncoder(fieldType, fieldLength,
fieldLDecimals, charset);
+ }
+
+ public DBFFieldEncoder getEncoder() {
+ return encoder;
+ }
+
+ @Override
+ public String toString() {
+ return "DBFField{" +
+ "fieldName='" + fieldName + '\'' +
+ ", fieldType=" + fieldType +
+ ", fieldAddress=" + fieldAddress +
+ ", fieldLength=" + fieldLength +
+ ", fieldLDecimals=" + fieldLDecimals +
+ '}';
+ }
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFFieldEncoder.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFFieldEncoder.java
new file mode 100644
index 0000000000..63d0b3237e
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFFieldEncoder.java
@@ -0,0 +1,206 @@
+/*
+ * 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.sis.storage.shapefile.dbf;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.time.LocalDate;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.io.stream.ChannelDataOutput;
+import static org.apache.sis.storage.shapefile.dbf.DBFField.*;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public abstract class DBFFieldEncoder {
+
+ public static DBFFieldEncoder getEncoder(int fieldType, int fieldLength,
int fieldDecimals, Charset charset) {
+ switch (fieldType) {
+ case TYPE_BINARY : return new Binary(fieldLength, fieldDecimals);
+ case TYPE_CHAR : return new Char(fieldLength, fieldDecimals,
charset);
+ case TYPE_DATE : return new Date(fieldLength, fieldDecimals);
+ case TYPE_NUMBER : {
+ if (fieldDecimals != 0) return new Decimal(fieldLength,
fieldDecimals);
+ return fieldLength > 9 ? new LongInt(fieldLength) : new
ShortInt(fieldLength);
+ }
+ case TYPE_LOGIC : return new Logic(fieldLength);
+ case TYPE_MEMO : throw new UnsupportedOperationException("todo");
+ case TYPE_TIMESTAMP : throw new
UnsupportedOperationException("todo");
+ case TYPE_LONG : throw new UnsupportedOperationException("todo");
+ case TYPE_INC : throw new UnsupportedOperationException("todo");
+ case TYPE_FLOAT : throw new UnsupportedOperationException("todo");
+ case TYPE_DOUBLE : throw new UnsupportedOperationException("todo");
+ case TYPE_OLE : throw new UnsupportedOperationException("todo");
+ default: throw new IllegalArgumentException("Unknown field type
"+fieldType);
+ }
+
+ }
+
+ protected final Class valueClass;
+ protected final int fieldLength;
+ protected final int fieldLDecimals;
+
+ public DBFFieldEncoder(Class valueClass, int fieldLength, int
fieldLDecimals) {
+ this.valueClass = valueClass;
+ this.fieldLength = fieldLength;
+ this.fieldLDecimals = fieldLDecimals;
+ }
+
+
+ public Class getValueClass() {
+ return valueClass;
+ }
+
+ public abstract Object read(ChannelDataInput channel) throws IOException;
+
+ public abstract void write(ChannelDataOutput channel, Object value) throws
IOException;
+
+
+ private static final class Binary extends DBFFieldEncoder {
+
+ public Binary(int fieldLength, int fieldDecimals) {
+ super(Long.class, fieldLength, fieldDecimals);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class Char extends DBFFieldEncoder {
+
+ private final Charset charset;
+
+ public Char(int fieldLength, int fieldDecimals, Charset charset) {
+ super(String.class, fieldLength, fieldDecimals);
+ this.charset = charset;
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ return new String(channel.readBytes(fieldLength), charset).trim();
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class Date extends DBFFieldEncoder {
+
+ public Date(int fieldLength, int fieldDecimals) {
+ super(LocalDate.class, fieldLength, fieldDecimals);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ final String str = new
String(channel.readBytes(fieldLength)).trim();
+ final int year = Integer.parseUnsignedInt(str,0,4,10);
+ final int month = Integer.parseUnsignedInt(str,4,6,10);
+ final int day = Integer.parseUnsignedInt(str,6,8,10);
+ return LocalDate.of(year, month, day);
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class ShortInt extends DBFFieldEncoder {
+
+ public ShortInt(int fieldLength) {
+ super(Integer.class, fieldLength, 0);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ final String str = new
String(channel.readBytes(fieldLength)).trim();
+ if (str.isEmpty()) return 0;
+ else return Integer.parseInt(str);
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class LongInt extends DBFFieldEncoder {
+
+ public LongInt(int fieldLength) {
+ super(Long.class, fieldLength, 0);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ final String str = new
String(channel.readBytes(fieldLength)).trim();
+ if (str.isEmpty()) return 0L;
+ else return Long.parseLong(str);
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class Decimal extends DBFFieldEncoder {
+
+ public Decimal(int fieldLength, int fieldDecimals) {
+ super(Double.class, fieldLength, fieldDecimals);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ final String str = new
String(channel.readBytes(fieldLength)).trim();
+ if (str.isEmpty()) return 0L;
+ else return Double.parseDouble(str);
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ private static final class Logic extends DBFFieldEncoder {
+
+ public Logic(int fieldLength) {
+ super(Boolean.class, fieldLength, 0);
+ }
+
+ @Override
+ public Object read(ChannelDataInput channel) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void write(ChannelDataOutput channel, Object value) throws
IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
new file mode 100644
index 0000000000..bbe3c8b475
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFHeader.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sis.storage.shapefile.dbf;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+import org.apache.sis.io.stream.ChannelDataInput;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class DBFHeader {
+
+ private static final int FIELD_SIZE = 32;
+
+ public int year;
+ public int month;
+ public int day;
+ public int nbRecord;
+ public int headerSize;
+ public int recordSize;
+ public DBFField[] fields;
+
+ public void read(ChannelDataInput channel, Charset charset) throws
IOException {
+ channel.buffer.order(ByteOrder.LITTLE_ENDIAN);
+ if (channel.readByte()!= 0x03) {
+ throw new IOException("Unvalid database III magic");
+ }
+ year = channel.readUnsignedByte();
+ month = channel.readUnsignedByte();
+ day = channel.readUnsignedByte();
+ nbRecord = channel.readInt();
+ headerSize = channel.readUnsignedShort();
+ recordSize = channel.readUnsignedShort();
+ channel.skipBytes(20);
+ fields = new DBFField[(headerSize - FIELD_SIZE - 1) / FIELD_SIZE];
+
+ for (int i = 0; i < fields.length; i++) {
+ fields[i] = new DBFField();
+ fields[i].read(channel, charset);
+ }
+ channel.skipBytes(1);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DBFHeader{");
+ sb.append("year=").append(year);
+ sb.append(",month=").append(month);
+ sb.append(",day=").append(day);
+ sb.append(",nbRecord=").append(nbRecord);
+ sb.append(",headerSize=").append(headerSize);
+ sb.append(",recordSize=").append(recordSize);
+ sb.append("}\n");
+ for (DBFField field : fields) {
+ sb.append("- ").append(field.toString());
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+}
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
new file mode 100644
index 0000000000..f7893632a8
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFReader.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sis.storage.shapefile.dbf;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.sis.io.stream.ChannelDataInput;
+
+/**
+ * Seekable dbf file reader.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class DBFReader implements AutoCloseable {
+
+ private static final int TAG_PRESENT = 0x20;
+ private static final int TAG_DELETED = 0x2a;
+
+ private final ChannelDataInput channel;
+ private final DBFHeader header;
+ private int nbRead = 0;
+
+ public DBFReader(ChannelDataInput channel, Charset charset) throws
IOException {
+ this.channel = channel;
+ this.header = new DBFHeader();
+ this.header.read(channel, charset);
+ }
+
+ public DBFHeader getHeader() {
+ return header;
+ }
+
+ public void moveToOffset(long position) throws IOException {
+ channel.seek(position);
+ }
+
+ /**
+ *
+ * @return record or DBFRecord.DELETED if this record has been deleted.
+ * @throws IOException if a decoding error occurs
+ */
+ public DBFRecord next() throws IOException {
+ if (nbRead >= header.nbRecord) {
+ //reached records end
+ return null;
+ }
+ nbRead++;
+
+ final int marker = channel.readUnsignedByte();
+ if (marker == TAG_DELETED) {
+ channel.skipBytes(header.recordSize);
+ return DBFRecord.DELETED;
+ } else if (marker != TAG_PRESENT) {
+ throw new IOException("Unexpected record marker " + marker);
+ }
+
+ final DBFRecord record = new DBFRecord();
+ record.fields = new Object[header.fields.length];
+ for (int i = 0; i < header.fields.length; i++) {
+ record.fields[i] = header.fields[i].getEncoder().read(channel);
+ }
+ return record;
+ }
+
+
+
+ @Override
+ public void close() throws IOException {
+ channel.channel.close();
+ }
+
+}
diff --git
a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
similarity index 69%
copy from incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
copy to
incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
index 2aad9ecdde..e21562d338 100644
--- a/incubator/src/org.apache.sis.storage.shapefile/main/module-info.java
+++
b/incubator/src/org.apache.sis.storage.shapefile/main/org/apache/sis/storage/shapefile/dbf/DBFRecord.java
@@ -14,19 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.apache.sis.storage.shapefile.dbf;
/**
- * Shapefile store.
+ * A DBF record is an array of field values.
*
- * @author Travis L. Pinney
- * @author Marc Le Bihan
+ * @author Johann Sorel (Geomatys)
*/
-module org.apache.sis.storage.shapefile {
- requires java.sql;
- requires esri.geometry.api;
- requires transitive org.apache.sis.storage;
+public final class DBFRecord {
+
+ public static final DBFRecord DELETED = new DBFRecord();
+
+ public Object[] fields;
+
+ public DBFRecord() {
+ }
- exports org.apache.sis.storage.shapefile;
- exports org.apache.sis.storage.shapefile.cpg;
- exports org.apache.sis.storage.shapefile.shp;
}
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
new file mode 100644
index 0000000000..46fa04af28
--- /dev/null
+++
b/incubator/src/org.apache.sis.storage.shapefile/test/org/apache/sis/storage/shapefile/dbf/DBFIOTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.sis.storage.shapefile.dbf;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import org.apache.sis.io.stream.ChannelDataInput;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.StorageConnector;
+import org.junit.Test;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public class DBFIOTest {
+
+ private ChannelDataInput openRead(String path) throws DataStoreException {
+ final URL url = DBFIOTest.class.getResource(path);
+ final StorageConnector cnx = new StorageConnector(url);
+ final ChannelDataInput cdi = cnx.getStorageAs(ChannelDataInput.class);
+ cnx.closeAllExcept(cdi);
+ return cdi;
+ }
+
+ @Test
+ public void readTest() throws DataStoreException, IOException {
+ final String path = "/org/apache/sis/storage/shapefile/point.dbf";
+ final ChannelDataInput cdi = openRead(path);
+
+ try (DBFReader reader = new DBFReader(cdi, StandardCharsets.UTF_8)) {
+ final DBFHeader header = reader.getHeader();
+ assertEquals(123, header.year);
+ assertEquals(10, header.month);
+ assertEquals(27, header.day);
+ assertEquals(2, header.nbRecord);
+ assertEquals(193, header.headerSize);
+ assertEquals(120, header.recordSize);
+ assertEquals(5, header.fields.length);
+ assertEquals("id", header.fields[0].fieldName);
+ assertEquals(110, header.fields[0].fieldType);
+ assertEquals(0, header.fields[0].fieldAddress);
+ assertEquals(10, header.fields[0].fieldLength);
+ assertEquals(0, header.fields[0].fieldLDecimals);
+ assertEquals("text", header.fields[1].fieldName);
+ assertEquals(99, header.fields[1].fieldType);
+ assertEquals(0, header.fields[1].fieldAddress);
+ assertEquals(80, header.fields[1].fieldLength);
+ assertEquals(0, header.fields[1].fieldLDecimals);
+ assertEquals("integer", header.fields[2].fieldName);
+ assertEquals(110, header.fields[2].fieldType);
+ assertEquals(0, header.fields[2].fieldAddress);
+ assertEquals(10, header.fields[2].fieldLength);
+ assertEquals(0, header.fields[2].fieldLDecimals);
+ assertEquals("float", header.fields[3].fieldName);
+ assertEquals(110, header.fields[3].fieldType);
+ assertEquals(0, header.fields[3].fieldAddress);
+ assertEquals(11, header.fields[3].fieldLength);
+ assertEquals(6, header.fields[3].fieldLDecimals);
+ assertEquals("date", header.fields[4].fieldName);
+ assertEquals(100, header.fields[4].fieldType);
+ assertEquals(0, header.fields[4].fieldAddress);
+ assertEquals(8, header.fields[4].fieldLength);
+ assertEquals(0, header.fields[4].fieldLDecimals);
+
+
+ final DBFRecord record1 = reader.next();
+ assertEquals(1L, record1.fields[0]);
+ assertEquals("text1", record1.fields[1]);
+ assertEquals(10L, record1.fields[2]);
+ assertEquals(20.0, record1.fields[3]);
+ assertEquals(LocalDate.of(2023, 10, 27), record1.fields[4]);
+
+ final DBFRecord record2 = reader.next();
+ assertEquals(2L, record2.fields[0]);
+ assertEquals("text2", record2.fields[1]);
+ assertEquals(40L, record2.fields[2]);
+ assertEquals(60.0, record2.fields[3]);
+ assertEquals(LocalDate.of(2023, 10, 28), record2.fields[4]);
+
+ //no more records
+ assertNull(reader.next());
+ }
+
+ }
+
+}