org.kalypso.shape.dbf.DBFField.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypso.shape.dbf.DBFField.java

Source

/** This file is part of kalypso/deegree.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * history:
 * 
 * Files in this package are originally taken from deegree and modified here
 * to fit in kalypso. As goals of kalypso differ from that one in deegree
 * interface-compatibility to deegree is wanted but not retained always. 
 * 
 * If you intend to use this software in other ways than in kalypso 
 * (e.g. OGC-web services), you should consider the latest version of deegree,
 * see http://www.deegree.org .
 *
 * all modifications are licensed as deegree, 
 * original copyright:
 *
 * Copyright (C) 2001 by:
 * EXSE, Department of Geography, University of Bonn
 * http://www.giub.uni-bonn.de/exse/
 * lat/lon GmbH
 * http://www.lat-lon.de
 */

package org.kalypso.shape.dbf;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.apache.commons.lang3.StringUtils;
import org.kalypso.shape.tools.DataUtils;

/**
 * Class representing a field descriptor of a dBase III/IV file <br>
 * Original Author: Andreas Poth
 */
public class DBFField implements IDBFField {
    private static final int MAX_COLUMN_NAME_LENGTH = 11;

    private final short m_fieldLength;

    private final short m_decimalCount;

    private final FieldFormatter m_formatter;

    private final FieldType m_type;

    private final String m_name;

    /**
     * constructor recieves name and type of the field, the length of the field in bytes and the decimalcount. the
     * decimalcount is only considered if type id "N" or "F", it's maxvalue if fieldlength - 2!
     */
    public DBFField(final String name, final FieldType type, final short fieldLength, final short decimalCount)
            throws DBaseException {
        m_name = name;
        m_type = type;
        m_fieldLength = fieldLength;
        m_decimalCount = decimalCount;
        m_formatter = createFormatter(type, fieldLength, decimalCount);

        checkParameters();
    }

    private void checkParameters() throws DBaseException {
        checkName();
        checkFieldLength();
        checkDecimalCount();
        checkDependencies();
    }

    private void checkName() throws DBaseException {
        if (StringUtils.isBlank(m_name))
            throw new DBaseException("'Name' must not be blank.");

        if (StringUtils.length(m_name) > 11)
            throw new DBaseException("'Name' must not be longer than 11 characters.");
    }

    private void checkFieldLength() throws DBaseException {
        if (m_fieldLength < 0 || m_fieldLength > 255) {
            final String msg = String.format("Field length must not exceed 255 (is %d)", m_fieldLength);
            throw new DBaseException(msg);
        }
    }

    private void checkDecimalCount() throws DBaseException {
        // FIXME: somethings wrong here....
        if (m_decimalCount > 15)
            throw new DBaseException("Decimal count must be smaller than 16");
        if (m_decimalCount < 0 || m_decimalCount > 255) {
            final String msg = String.format("Decimal count  must not exceed 255 (is %d)", m_decimalCount);
            throw new DBaseException(msg);
        }
    }

    private void checkDependencies() throws DBaseException {
        final int fixedLength = m_type.getFixedLength();
        final char typeName = m_type.getName();
        final boolean supportsDecimal = m_type.isSupportDecimal();
        if (fixedLength != -1 && m_fieldLength != fixedLength) {
            final String msg = String.format("Datatype '%s' must have fieldLength = %d", typeName, fixedLength);
            throw new DBaseException(msg);
        }

        if (supportsDecimal) {
            if (m_fieldLength - m_decimalCount < 2)
                throw new DBaseException("Invalid fieldlength and/or decimalcount");
        } else if (m_decimalCount > 0) {
            final String msg = String.format("Datatype '%s' does not support decimals", typeName);
            throw new DBaseException(msg);
        }
    }

    private FieldFormatter createFormatter(final FieldType type, final short fieldLength,
            final short decimalCount) {
        switch (type) {
        case C:
        case M:
            return new FieldFormatterString();

        case N:
        case F:
            return new FieldFormatterNumber(fieldLength, decimalCount);

        case L:
            return new FieldFormatterBoolean();

        case D:
            return new FieldFormatterDate();
        }

        throw new IllegalArgumentException("Unknow field: " + type);
    }

    @Override
    public String getName() {
        return m_name;
    }

    /**
     * Creates a new {@link DBFField} with the given name and all other parameters taken from the current instance.
     */
    public DBFField withName(final String name) throws DBaseException {
        return new DBFField(name, m_type, m_fieldLength, m_decimalCount);
    }

    @Override
    public short getLength() {
        return m_fieldLength;
    }

    /**
     * Creates a new {@link DBFField} with the given length and all other parameters taken from the current instance.
     */
    public DBFField withLength(final short length) throws DBaseException {
        return new DBFField(m_name, m_type, length, m_decimalCount);
    }

    @Override
    public FieldType getType() {
        return m_type;
    }

    /**
     * Creates a new {@link DBFField} with the given type and all other parameters taken from the current instance.<br/>
     * Automatically changes length and decimal count if necessary.
     */
    public DBFField withType(final FieldType type) throws DBaseException {
        switch (type) {
        case C:
            return new DBFField(m_name, type, m_fieldLength, (short) 0);

        case D:
            return new DBFField(m_name, type, (short) 8, (short) 0);

        case F:
            return new DBFField(m_name, type, m_fieldLength, m_decimalCount);

        case L:
            return new DBFField(m_name, type, (short) 1, (short) 0);

        case M:
            return new DBFField(m_name, type, (short) 10, (short) 0);

        case N:
            return new DBFField(m_name, type, m_fieldLength, m_decimalCount);
        }

        // never reached
        throw new IllegalArgumentException();
    }

    @Override
    public short getDecimalCount() {
        return m_decimalCount;
    }

    /**
     * Creates a new {@link DBFField} with the given decimal count and all other parameters taken from the current
     * instance.
     */
    public DBFField withDecimalCount(final short count) throws DBaseException {
        return new DBFField(m_name, m_type, m_fieldLength, count);
    }

    @Override
    public byte[] writeValue(final DataOutput output, final Object value, final Charset charset)
            throws DBaseException, IOException {
        final byte[] bytes = m_formatter.toBytes(value, charset);

        final int bytesToWrite = Math.min(bytes.length, m_fieldLength);

        output.write(bytes, 0, bytesToWrite);

        for (int j = bytesToWrite; j < m_fieldLength; j++)
            output.writeByte((byte) 0x20);

        return bytes;
    }

    @Override
    public Object readValue(final DataInput input, final Charset charset) throws IOException, DBaseException {
        final byte[] value = new byte[m_fieldLength];
        input.readFully(value);

        final String asString = new String(value, charset).trim();
        return m_formatter.fromString(asString);
    }

    public static IDBFField read(final DataInput input, final Charset charset) throws IOException, DBaseException {
        final byte[] columnNameBytes = new byte[MAX_COLUMN_NAME_LENGTH];
        input.readFully(columnNameBytes);

        final int columNameLength = findColumnNameLength(columnNameBytes);

        final String columnName = new String(columnNameBytes, 0, columNameLength, charset);

        final char columnType = (char) input.readByte();

        input.skipBytes(4);

        // get field length and precision
        final short fieldLength = DataUtils.fixByte(input.readByte());
        final short decimalCount = DataUtils.fixByte(input.readByte());

        input.skipBytes(14);

        final FieldType fieldType = FieldType.valueOf("" + columnType);
        return new DBFField(columnName, fieldType, fieldLength, decimalCount);
    }

    // HACK: we truncate names at '0' bytes. It is however unclear, if this is according to the specification.
    private static int findColumnNameLength(final byte[] bytes) {
        for (int i = 0; i < MAX_COLUMN_NAME_LENGTH; i++) {
            if (bytes[i] == 0)
                return i;
        }

        return MAX_COLUMN_NAME_LENGTH;
    }

    @Override
    public void write(final DataOutput output, final Charset charset) throws IOException {
        final byte[] bytes = new byte[32];
        Arrays.fill(bytes, 0, bytes.length, (byte) 0);

        // copy name into the first 11 bytes
        final String name = getName();
        final byte[] nameBytes = name.getBytes(charset);
        final int nameLength = Math.min(nameBytes.length, 11);
        System.arraycopy(nameBytes, 0, bytes, 0, nameLength);
        Arrays.fill(bytes, nameLength, 11, (byte) 0);

        final FieldType type = getType();
        bytes[11] = (byte) type.getName();
        bytes[16] = (byte) getLength();
        bytes[17] = (byte) getDecimalCount();

        bytes[20] = 1; // work area id (don't know if it should be 1)

        bytes[31] = 0x00; // has no index tag in a MDX file

        output.write(bytes);
    }
}