albfernandez / javadbf

Java library for reading and writing Xbase (dBase/DBF) files.
GNU Lesser General Public License v3.0
224 stars 98 forks source link

Get fields by name #38

Closed SirError closed 7 years ago

SirError commented 7 years ago

It'd be great if instead of an Array of Objects the nextRecord() could return an Object with more info about the collumns. So we could get the columns with something like "row.getString("CollumnName")".

SirError commented 7 years ago

Hi again. I was playing around with the code, and got some ideas. Maybe it might help you. Tell me what you think.

I made this scratch.

So we could have a method like this:

    public Map<String, Object> nextRecordField() {
        if (this.closed) {
            throw new IllegalArgumentException("this DBFReader is closed");
        }
        Map<String, Object> recordObjects = new LinkedHashMap<>();
        try {
            boolean isDeleted = false;

            do {
                if (isDeleted && !showDeletedRows) {
                    skip(this.header.recordLength - 1);
                }
                int t_byte = this.dataInputStream.readByte();
                if (t_byte == END_OF_DATA) {
                    return null;
                }
                isDeleted = t_byte == '*';
            } while (isDeleted && !showDeletedRows);

            if (showDeletedRows) {
                recordObjects.put("deleted", isDeleted);
            }

            for (int i = 0; i < this.header.fieldArray.length; i++) {
                DBFField field = this.header.fieldArray[i];
                Object o = getFieldValue(field);
                if (field.isSystem()) {
                    if (field.getType() == DBFDataType.NULL_FLAGS) {
                        if (o instanceof BitSet) {
                            BitSet nullFlags = (BitSet) o;
                            int currentIndex = -1;
                            for (int j = 0; j < this.header.fieldArray.length; j++) {
                                DBFField field1 = this.header.fieldArray[j];
                                if (field1.isNullable()) {
                                    currentIndex++;
                                    if (nullFlags.get(currentIndex)) {
                                        recordObjects.put(field1.getName(), null);
                                    }
                                }
                                if (field1.getType() == DBFDataType.VARBINARY || field1.getType() == DBFDataType.VARCHAR) {
                                    currentIndex++;
                                    if (recordObjects.get(i) instanceof byte[]) {
                                        byte[] data = (byte[]) recordObjects.get(j);
                                        int size = field1.getLength();
                                        if (!nullFlags.get(currentIndex)) {
                                            // Data is not full
                                            size = data[data.length - 1];
                                        }
                                        byte[] newData = new byte[size];
                                        System.arraycopy(data, 0, newData, 0, size);
                                        Object o1 = newData;
                                        if (field1.getType() == DBFDataType.VARCHAR) {
                                            o1 = new String(newData, getCharset());
                                        }
                                        recordObjects.put(field1.getName(), o1);
                                    }
                                }

                            }
                        }

                    }
                } else {
                    recordObjects.put(field.getName(), o);
                }
            }
        } catch (EOFException e) {
            return null;
        } catch (IOException e) {
            throw new DBFException(e.getMessage(), e);
        }

        return recordObjects;
    }

this way we return a map, and we can easily get the field using the key record.get("myField");

And to not break the actual api, the method nextRecord could call this one and then convert the return into an array, something like:

    public Object[] nextRecord() {
        return nextRecordField().entrySet().stream().map(Entry::getValue).toArray();
    }

so, what do you think?