All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.formats.vpf.VPFTableReader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.formats.vpf;

import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.util.*;

import java.io.*;
import java.nio.*;
import java.util.ArrayList;

/**
 * DIGEST Part 2, Annex C.2.2.1.2 and C.2.3.1.1
 *
 * @author dcollins
 * @version $Id: VPFTableReader.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class VPFTableReader
{
    public VPFTableReader()
    {
    }

    public VPFBufferedRecordData read(File file)
    {
        if (file == null)
        {
            String message = Logging.getMessage("nullValue.FileIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        try
        {
            ByteBuffer buffer = this.readFileToBuffer(file);
            return this.doRead(file, buffer);
        }
        catch (Exception e)
        {
            String message = Logging.getMessage("VPF.ExceptionAttemptingToReadTable", file.getPath());
            Logging.logger().log(java.util.logging.Level.SEVERE, message, e);
            throw new WWRuntimeException(message, e);
        }
    }

    protected ByteBuffer readFileToBuffer(File file) throws IOException
    {
        ByteBuffer buffer = WWIO.readFileToBuffer(file, true); // Read VPF table to a direct ByteBuffer.
        buffer.order(ByteOrder.LITTLE_ENDIAN); // Default to least significant byte first order.
        return buffer;
    }

    protected VPFBufferedRecordData doRead(File file, ByteBuffer buffer)
    {
        // Read the table header.
        Header header = this.readHeader(buffer);
        // Set the byte ordering to the ordering specified by the table header.
        buffer.order(header.byteOrder);

        RecordIndex recordIndex = null;
        // Attempt to find a variable-length record index according to the file naming convention in
        // DIGEST Part 2 Annex C.2.3.1.2
        File recordIndexFile = new File(file.getParent(), getRecordIndexFilename(file.getName()));
        if (recordIndexFile.exists())
            recordIndex = this.readRecordIndex(recordIndexFile);
        // If the record index is null, then attempt to compute it from the header's column definitions.
        if (recordIndex == null)
            recordIndex = this.computeRecordIndex(buffer, header);
        // If the record index is still null, then we the column definitions are variable length, and there is no
        // variable-length record index associated with this table. In this case, we cannot read the table body.
        if (recordIndex == null)
        {
            String message = Logging.getMessage("VPF.VariableLengthIndexFileMissing");
            Logging.logger().severe(message);
            throw new WWRuntimeException(message);
        }

        // Read the table record data.
        return this.readRecordData(buffer, header.columns, recordIndex);
    }

    //**************************************************************//
    //********************  Header  ********************************//
    //**************************************************************//

    /** MIL-STD-2407, section 5.4.1.1 */
    protected static class Header
    {
        public int length;
        public ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; // Default to least significant byte first order.
        public String description;
        public String narrativeTableName;
        public Column[] columns;

        public Header()
        {
        }
    }

    /** MIL-STD-2407, section 5.4.1.1 */
    public class Column
    {
        public final String name;
        public String dataType;
        public int numElements;
        public String keyType;
        public String description;
        public String valueDescriptionTableName;
        public String thematicIndexName;
        public String narrativeTableName;
        public VPFDataBuffer dataBuffer;

        public Column(String name)
        {
            this.name = name;
        }

        public int getFieldLength()
        {
            if (this.isVariableLengthField())
                return -1;

            VPFDataType type = VPFDataType.fromTypeName(this.dataType);
            return this.numElements * type.getFieldLength();
        }

        public boolean isVariableLengthField()
        {
            VPFDataType type = VPFDataType.fromTypeName(this.dataType);
            return (this.numElements < 0) || type.isVariableLength();
        }

        public boolean isPrimaryKey()
        {
            return this.keyType.equals(VPFConstants.PRIMARY_KEY);
        }

        public boolean isUniqueKey()
        {
            return this.keyType.equals(VPFConstants.UNIQUE_KEY);
        }

        public boolean isNonUniqueKey()
        {
            return this.keyType.equals(VPFConstants.NON_UNIQUE_KEY);
        }
    }

    protected Header readHeader(ByteBuffer buffer)
    {
        int offset = buffer.position();
        int length = buffer.getInt();

        Header header = new Header();
        header.length = length;
        header.byteOrder = ByteOrder.LITTLE_ENDIAN; // Default to least significant byte first order.

        if (length == 0)
        {
            return header;
        }

        // Read the byte order character.
        String s = VPFUtils.readDelimitedText(buffer, ';');
        if (s != null && s.equalsIgnoreCase("M"))
            header.byteOrder = ByteOrder.BIG_ENDIAN;

        // Read the table description string.
        s = VPFUtils.readDelimitedText(buffer, ';');
        if (s != null)
            header.description = s.trim();

        // Read the narrative table name.
        s = VPFUtils.readDelimitedText(buffer, ';');
        if (s != null && s.charAt(0) != '-')
            header.narrativeTableName = s.trim();

        ArrayList columnList = new ArrayList();

        while (buffer.position() < (offset + length))
        {
            Column col = this.readColumnDescription(buffer);
            columnList.add(col);
        }

        header.columns = new Column[columnList.size()];
        columnList.toArray(header.columns);

        // Consume any remaining text, up to the column component delimiter ';'.
        VPFUtils.readDelimitedText(buffer, ';');

        return header;
    }

    protected Column readColumnDescription(ByteBuffer buffer)
    {
        String s = VPFUtils.readDelimitedText(buffer, '=');
        if (s == null)
        {
            String message = Logging.getMessage("VPF.MissingColumnName");
            Logging.logger().severe(message);
            throw new WWRuntimeException(message);
        }

        Column col = new Column(s);

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.dataType = s;

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.numElements = parseNumElements(s);

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.keyType = s;

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.description = s;

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.valueDescriptionTableName = s;

        s = VPFUtils.readDelimitedText(buffer, ',');
        if (s != null)
            col.thematicIndexName = s;

        // Consume any remaining text, up to the sub column delimiter ':'.
        s = VPFUtils.readDelimitedText(buffer, ':');
        if (s != null)
        {
            int pos = s.indexOf(",");
            if (pos >= 0)
            {
                s = s.substring(0, pos);
                col.narrativeTableName = s;
            }
        }

        return col;
    }

    protected static int parseNumElements(String numElements)
    {
        // "*" denotes a field with variable length.
        if (numElements == null || numElements.equals("*"))
            return -1;

        Integer i = WWUtil.convertStringToInteger(numElements);
        return (i != null) ? i : -1;
    }

    //**************************************************************//
    //********************  Record Data  ***************************//
    //**************************************************************//

    protected interface RecordDataReader
    {
        VPFDataBuffer getDataBuffer();

        void read(ByteBuffer byteBuffer);
    }

    protected abstract static class AbstractDataReader implements RecordDataReader
    {
        protected VPFDataBuffer dataBuffer;

        public AbstractDataReader(VPFDataBuffer dataBuffer)
        {
            this.dataBuffer = dataBuffer;
        }

        public VPFDataBuffer getDataBuffer()
        {
            return this.dataBuffer;
        }
    }

    protected static class FixedLengthDataReader extends AbstractDataReader
    {
        protected int numElements;

        public FixedLengthDataReader(VPFDataBuffer dataBuffer, int numElements)
        {
            super(dataBuffer);
            this.numElements = numElements;
        }

        public void read(ByteBuffer byteBuffer)
        {
            this.dataBuffer.read(byteBuffer, this.numElements);
        }
    }

    protected static class VariableLengthDataReader extends AbstractDataReader
    {
        public VariableLengthDataReader(VPFDataBuffer dataBuffer)
        {
            super(dataBuffer);
        }

        public void read(ByteBuffer byteBuffer)
        {
            this.dataBuffer.read(byteBuffer);
        }
    }

    protected VPFBufferedRecordData readRecordData(ByteBuffer byteBuffer, Column[] columns, RecordIndex recordIndex)
    {
        int numRows = recordIndex.numEntries;
        int numColumns = columns.length;

        // Create data readers for each column.
        RecordDataReader[] readers = new RecordDataReader[numColumns];
        for (int col = 0; col < numColumns; col++)
        {
            VPFDataType type = VPFDataType.fromTypeName(columns[col].dataType);
            VPFDataBuffer dataBuffer = type.createDataBuffer(numRows, columns[col].numElements);
            readers[col] = columns[col].isVariableLengthField() ?
                new VariableLengthDataReader(dataBuffer)
                : new FixedLengthDataReader(dataBuffer, columns[col].numElements);
        }

        // Read the column data associated with each row.
        for (int row = 0; row < numRows; row++)
        {
            byteBuffer.position(recordIndex.entries[row].offset);

            for (int col = 0; col < numColumns; col++)
            {
                readers[col].read(byteBuffer);
            }
        }

        VPFBufferedRecordData recordData = new VPFBufferedRecordData();
        recordData.setNumRecords(numRows);

        // Set the record data buffer associated with each column.
        for (int col = 0; col < numColumns; col++)
        {
            recordData.setRecordData(columns[col].name, readers[col].getDataBuffer());

            // Compute an index for any columns which are identified as primary keys or unique keys.
            if (!columns[col].name.equals(VPFConstants.ID) &&
                (columns[col].name.equals(VPFConstants.PRIMARY_KEY) ||
                    columns[col].name.equals(VPFConstants.UNIQUE_KEY)))
            {
                recordData.buildRecordIndex(columns[col].name);
            }
        }

        return recordData;
    }

    //**************************************************************//
    //********************  Record Index  **************************//
    //**************************************************************//

    public static class RecordIndex
    {
        public static class Entry
        {
            public int offset;
            public int length;

            public Entry(int offset, int length)
            {
                this.offset = offset;
                this.length = length;
            }
        }

        public int numEntries;
        public int headerLength;
        public Entry[] entries;

        public RecordIndex()
        {
        }
    }

    /**
     * Returns the name of the Variable-length Index File associated with a specified table name. Note that this does
     * not determine whether or not the index exists, it simply returns the abstract file name of the index. 
See * MIL-STD-2407, section 5.3.1.2 DIGEST Part 2, Annex C.2.3.1.2 * * @param tableName the table name to return an index name for. * * @return the name of a variable-length index file associated with the table name., */ protected static String getRecordIndexFilename(String tableName) { boolean isFcs = tableName.equalsIgnoreCase(VPFConstants.FEATURE_CLASS_SCHEMA_TABLE); StringBuilder sb = new StringBuilder(); int len = tableName.length(); sb.append(tableName, 0, (len > 0) ? (len - 1) : len); sb.append(isFcs ? "z" : "x"); return sb.toString(); } protected RecordIndex readRecordIndex(File file) { try { ByteBuffer buffer = this.readFileToBuffer(file); buffer.order(ByteOrder.LITTLE_ENDIAN); // Default to least significant byte first order. RecordIndex index = new RecordIndex(); index.numEntries = buffer.getInt(); index.headerLength = buffer.getInt(); index.entries = new RecordIndex.Entry[index.numEntries]; for (int i = 0; i < index.numEntries; i++) { int recordOffset = buffer.getInt(); int recordLength = buffer.getInt(); index.entries[i] = new RecordIndex.Entry(recordOffset, recordLength); } return index; } catch (Exception e) { String message = Logging.getMessage("VPF.ExceptionAttemptingToReadRecordIndex", file.getPath()); Logging.logger().log(java.util.logging.Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } } protected RecordIndex computeRecordIndex(ByteBuffer buffer, Header header) { // Compute a fixed length record size by summing the sizes of individual columns. Assume that the bytes of row // values are tightly packed. int recordLength = 0; for (Column col : header.columns) { // If any column contains a variable length field, then we cannot compute a record size for this table. if (col.isVariableLengthField()) { return null; } recordLength += col.getFieldLength(); } // Body offset is size of header length field (4 bytes) plus the length of the header content. // Body length is remaining bytes in file minus the body offset. // Number of records is the number of times a record with a fixed length can appear in the body. int bodyOffset = 4 + header.length; int bodyLength = buffer.limit() - bodyOffset; int numRecords = bodyLength / recordLength; RecordIndex index = new RecordIndex(); index.headerLength = header.length; index.numEntries = numRecords; index.entries = new RecordIndex.Entry[numRecords]; int offset = bodyOffset; for (int i = 0; i < numRecords; i++) { index.entries[i] = new RecordIndex.Entry(offset, recordLength); offset += index.entries[i].length; } return index; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy