/****************************************************************************** * Copyright (c) 1999, Frank Warmerdam * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ******************************************************************************/ package com.bbn.openmap.dataAccess.iso8211; import java.io.IOException; import java.util.Iterator; import java.util.Vector; import com.bbn.openmap.layer.vpf.MutableInt; import com.bbn.openmap.util.Debug; /** * Contains instance data from one data record (DR). The data is * contained as a list of DDFField instances partitioning the raw data * into fields. Class contains one DR record from a file. We read into * the same record object repeatedly to ensure that repeated leaders * can be easily preserved. */ public class DDFRecord implements DDFConstants { protected DDFModule poModule; protected boolean nReuseHeader; protected int nFieldOffset; // field data area, not dir entries. protected int nDataSize; // Whole record except leader with header protected byte[] pachData; protected int nFieldCount; protected Vector paoFields; // Vector of DDFFields protected boolean bIsClone = false; public DDFRecord(DDFModule poModuleIn) { poModule = poModuleIn; nReuseHeader = false; nFieldOffset = -1; nDataSize = 0; pachData = null; nFieldCount = 0; paoFields = null; bIsClone = false; } /** Get the number of DDFFields on this record. */ public int getFieldCount() { return nFieldCount; } /** Fetch size of records raw data (GetData()) in bytes. */ public int getDataSize() { return nDataSize; } /** * Fetch the raw data for this record. The returned pointer is * effectively to the data for the first field of the record, and * is of size GetDataSize(). */ public byte[] getData() { return pachData; } /** * Fetch the DDFModule with which this record is associated. */ public DDFModule getModule() { return poModule; } /** * Write out record contents to debugging file. * * A variety of information about this record, and all it's fields * and subfields is written to the given debugging file handle. * Note that field definition information (ala DDFFieldDefn) isn't * written. */ public String toString() { StringBuffer buf = new StringBuffer("DDFRecord:\n"); buf.append(" ReuseHeader = ").append(nReuseHeader).append("\n"); buf.append(" DataSize = ").append(nDataSize).append("\n"); if (paoFields != null) { for (Iterator it = paoFields.iterator(); it.hasNext();) { buf.append((DDFField) it.next()); } } return buf.toString(); } /** * Read a record of data from the file, and parse the header to * build a field list for the record (or reuse the existing one if * reusing headers). It is expected that the file pointer will be * positioned at the beginning of a data record. It is the * DDFModule's responsibility to do so. * * This method should only be called by the DDFModule class. */ protected boolean read() { /* -------------------------------------------------------------------- */ /* Redefine the record on the basis of the header if needed. */ /* * As a side effect this will read the data for the record as * well. */ /* -------------------------------------------------------------------- */ if (!nReuseHeader) { Debug.message("iso8211", "DDFRecord reusing header, calling readHeader()"); return readHeader(); } /* -------------------------------------------------------------------- */ /* Otherwise we read just the data and carefully overlay it on */ /* * the previous records data without disturbing the rest of * the */ /* record. */ /* -------------------------------------------------------------------- */ byte[] tempData = new byte[nDataSize - nFieldOffset]; int nReadBytes = poModule.read(tempData, 0, tempData.length); System.arraycopy(pachData, nFieldOffset, tempData, 0, tempData.length); if (nReadBytes != (int) (nDataSize - nFieldOffset) && nReadBytes == -1) { return false; } else if (nReadBytes != (int) (nDataSize - nFieldOffset)) { Debug.error("DDFRecord: Data record is short on DDF file."); return false; } // notdef: eventually we may have to do something at this // point to // notify the DDFField's that their data values have changed. return true; } /** * Clear any information associated with the last header in * preparation for reading a new header. */ public void clear() { if (paoFields != null) { paoFields = null; } paoFields = null; nFieldCount = 0; pachData = null; nDataSize = 0; nReuseHeader = false; } /** * This perform the header reading and parsing job for the read() * method. It reads the header, and builds a field list. */ protected boolean readHeader() { /* -------------------------------------------------------------------- */ /* Clear any existing information. */ /* -------------------------------------------------------------------- */ clear(); /* -------------------------------------------------------------------- */ /* Read the 24 byte leader. */ /* -------------------------------------------------------------------- */ byte[] achLeader = new byte[DDF_LEADER_SIZE]; int nReadBytes = poModule.read(achLeader, 0, DDF_LEADER_SIZE); if (nReadBytes == -1) { return false; } else if (nReadBytes != (int) DDF_LEADER_SIZE) { Debug.error("DDFRecord.readHeader(): Leader is short on DDF file."); return false; } /* -------------------------------------------------------------------- */ /* Extract information from leader. */ /* -------------------------------------------------------------------- */ int _recLength, _fieldAreaStart, _sizeFieldLength; int _sizeFieldPos, _sizeFieldTag; byte _leaderIden; try { String recLength = new String(achLeader, 0, 5); String fieldAreaStart = new String(achLeader, 12, 5); _recLength = controlValidFileLength(Integer.valueOf(recLength).intValue()); _fieldAreaStart = Integer.valueOf(fieldAreaStart).intValue(); } catch (NumberFormatException nfe) { // Turns out, this usually indicates the end of the header // information, // with "^^^^^^^" being in the file. This is filler. if (Debug.debugging("iso8211")) { Debug.output("Finished reading headers"); } if (Debug.debugging("iso8211detail")) { Debug.error("DDFRecord.readHeader(): " + nfe.getMessage()); nfe.printStackTrace(); } else { // Debug.output("Data record appears to be corrupt on // DDF file.\n -- ensure that the files were // uncompressed without modifying\n carriage // return/linefeeds (by default WINZIP does this)."); } return false; } _leaderIden = achLeader[6]; _sizeFieldLength = achLeader[20] - '0'; _sizeFieldPos = achLeader[21] - '0'; _sizeFieldTag = achLeader[23] - '0'; if (_leaderIden == 'R') { nReuseHeader = true; } nFieldOffset = _fieldAreaStart - DDF_LEADER_SIZE; if (Debug.debugging("iso8211")) { Debug.output("\trecord length [0,5] = " + _recLength); Debug.output("\tfield area start [12,5]= " + _fieldAreaStart); Debug.output("\tleader id [6] = " + (char) _leaderIden + ", reuse header = " + nReuseHeader); Debug.output("\tfield length [20] = " + _sizeFieldLength); Debug.output("\tfield position [21] = " + _sizeFieldPos); Debug.output("\tfield tag [23] = " + _sizeFieldTag); } boolean readSubfields = false; /* -------------------------------------------------------------------- */ /* Is there anything seemly screwy about this record? */ /* -------------------------------------------------------------------- */ if (_recLength == 0) { // Looks like for record lengths of zero, we really want // to consult the size of the fields before we try to read // in all of the data for this record. Most likely, we // don't, and want to access the data later only when we // need it. nDataSize = _fieldAreaStart - DDF_LEADER_SIZE; } else if (_recLength < 24 || _recLength > 100000000 || _fieldAreaStart < 24 || _fieldAreaStart > 100000) { Debug.error("DDFRecord: Data record appears to be corrupt on DDF file.\n -- ensure that the files were uncompressed without modifying\n carriage return/linefeeds (by default WINZIP does this)."); return false; } else { /* -------------------------------------------------------------------- */ /* Read the remainder of the record. */ /* -------------------------------------------------------------------- */ nDataSize = _recLength - DDF_LEADER_SIZE; readSubfields = true; } pachData = new byte[nDataSize]; if (poModule.read(pachData, 0, nDataSize) != nDataSize) { Debug.error("DDFRecord: Data record is short on DDF file."); return false; } /* -------------------------------------------------------------------- */ /* * Loop over the directory entries, making a pass counting * them. */ /* -------------------------------------------------------------------- */ int i; int nFieldEntryWidth; nFieldEntryWidth = _sizeFieldLength + _sizeFieldPos + _sizeFieldTag; nFieldCount = 0; for (i = 0; i < nDataSize; i += nFieldEntryWidth) { if (pachData[i] == DDF_FIELD_TERMINATOR) break; nFieldCount++; } /* ==================================================================== */ /* Allocate, and read field definitions. */ /* ==================================================================== */ paoFields = new Vector(nFieldCount); for (i = 0; i < nFieldCount; i++) { String szTag; int nEntryOffset = i * nFieldEntryWidth; int nFieldLength, nFieldPos; /* -------------------------------------------------------------------- */ /* Read the position information and tag. */ /* -------------------------------------------------------------------- */ szTag = new String(pachData, nEntryOffset, _sizeFieldTag); nEntryOffset += _sizeFieldTag; nFieldLength = Integer.valueOf(new String(pachData, nEntryOffset, _sizeFieldLength)) .intValue(); nEntryOffset += _sizeFieldLength; nFieldPos = Integer.valueOf(new String(pachData, nEntryOffset, _sizeFieldPos)) .intValue(); /* -------------------------------------------------------------------- */ /* Find the corresponding field in the module directory. */ /* -------------------------------------------------------------------- */ DDFFieldDefinition poFieldDefn = poModule.findFieldDefn(szTag); if (poFieldDefn == null) { Debug.error("DDFRecord: Undefined field " + szTag + " encountered in data record."); return false; } DDFField ddff = null; if (readSubfields) { /* -------------------------------------------------------------------- */ /* Assign info the DDFField. */ /* -------------------------------------------------------------------- */ byte[] tempData = new byte[nFieldLength]; System.arraycopy(pachData, _fieldAreaStart + nFieldPos - DDF_LEADER_SIZE, tempData, 0, tempData.length); ddff = new DDFField(poFieldDefn, tempData, readSubfields); } else { // Save the info for reading later directly out of the // field. ddff = new DDFField(poFieldDefn, nFieldPos, nFieldLength); ddff.setHeaderOffset(poModule._recLength + _fieldAreaStart); } paoFields.add(ddff); } return true; } private int controlValidFileLength(int fileLength) { return fileLength == 0 ? computeAwaitingFileLength(fileLength) : fileLength; } private int computeAwaitingFileLength(int fileLength) { int _awaiting_file_length = 0; try { int _src_file_length = (int)this.poModule.getFileLength(); int _current_record_length = this.poModule.getRecordLength(); _awaiting_file_length = (_src_file_length - (_current_record_length + DDFConstants.DDF_FOOTER_SIZE)); } catch (IOException e) { e.printStackTrace(); return fileLength; } return _awaiting_file_length; } /** * Find the named field within this record. * * @param pszName The name of the field to fetch. The comparison * is case insensitive. * @param iFieldIndex The instance of this field to fetch. Use * zero (the default) for the first instance. * * @return Pointer to the requested DDFField. This pointer is to * an internal object, and should not be freed. It remains * valid until the next record read. */ public DDFField findField(String pszName, int iFieldIndex) { for (Iterator it = paoFields.iterator(); it.hasNext();) { DDFField ddff = (DDFField) it.next(); if (pszName.equalsIgnoreCase(ddff.getFieldDefn().getName())) { if (iFieldIndex == 0) { return ddff; } else { iFieldIndex--; } } } return null; } /** * Fetch field object based on index. * * @param i The index of the field to fetch. Between 0 and * GetFieldCount()-1. * * @return A DDFField pointer, or null if the index is out of * range. */ public DDFField getField(int i) { try { return (DDFField) paoFields.elementAt(i); } catch (ArrayIndexOutOfBoundsException aioobe) { return null; } } /** * Get an iterator over the fields. */ public Iterator iterator() { if (paoFields != null) { return paoFields.iterator(); } return null; } /** * Fetch value of a subfield as an integer. This is a convenience * function for fetching a subfield of a field within this record. * * @param pszField The name of the field containing the subfield. * @param iFieldIndex The instance of this field within the * record. Use zero for the first instance of this field. * @param pszSubfield The name of the subfield within the selected * field. * @param iSubfieldIndex The instance of this subfield within the * record. Use zero for the first instance. * @return The value of the subfield, or zero if it failed for * some reason. */ public int getIntSubfield(String pszField, int iFieldIndex, String pszSubfield, int iSubfieldIndex) { DDFField poField; /* -------------------------------------------------------------------- */ /* Fetch the field. If this fails, return zero. */ /* -------------------------------------------------------------------- */ poField = findField(pszField, iFieldIndex); if (poField == null) { return 0; } /* -------------------------------------------------------------------- */ /* Get the subfield definition */ /* -------------------------------------------------------------------- */ DDFSubfieldDefinition poSFDefn = poField.getFieldDefn() .findSubfieldDefn(pszSubfield); if (poSFDefn == null) { return 0; } /* -------------------------------------------------------------------- */ /* Get a pointer to the data. */ /* -------------------------------------------------------------------- */ MutableInt nBytesRemaining = new MutableInt(); byte[] pachData = poField.getSubfieldData(poSFDefn, nBytesRemaining, iSubfieldIndex); /* -------------------------------------------------------------------- */ /* Return the extracted value. */ /* -------------------------------------------------------------------- */ return poSFDefn.extractIntData(pachData, nBytesRemaining.value, null); } /** * Fetch value of a subfield as a float (double). This is a * convenience function for fetching a subfield of a field within * this record. * * @param pszField The name of the field containing the subfield. * @param iFieldIndex The instance of this field within the * record. Use zero for the first instance of this field. * @param pszSubfield The name of the subfield within the selected * field. * @param iSubfieldIndex The instance of this subfield within the * record. Use zero for the first instance. * @return The value of the subfield, or zero if it failed for * some reason. */ public double getFloatSubfield(String pszField, int iFieldIndex, String pszSubfield, int iSubfieldIndex) { DDFField poField; /* -------------------------------------------------------------------- */ /* Fetch the field. If this fails, return zero. */ /* -------------------------------------------------------------------- */ poField = findField(pszField, iFieldIndex); if (poField == null) { return 0; } /* -------------------------------------------------------------------- */ /* Get the subfield definition */ /* -------------------------------------------------------------------- */ DDFSubfieldDefinition poSFDefn = poField.getFieldDefn() .findSubfieldDefn(pszSubfield); if (poSFDefn == null) { return 0; } /* -------------------------------------------------------------------- */ /* Get a pointer to the data. */ /* -------------------------------------------------------------------- */ MutableInt nBytesRemaining = new MutableInt(); byte[] pachData = poField.getSubfieldData(poSFDefn, nBytesRemaining, iSubfieldIndex); /* -------------------------------------------------------------------- */ /* Return the extracted value. */ /* -------------------------------------------------------------------- */ return poSFDefn.extractFloatData(pachData, nBytesRemaining.value, null); } /** * Fetch value of a subfield as a string. This is a convenience * function for fetching a subfield of a field within this record. * * @param pszField The name of the field containing the subfield. * @param iFieldIndex The instance of this field within the * record. Use zero for the first instance of this field. * @param pszSubfield The name of the subfield within the selected * field. * @param iSubfieldIndex The instance of this subfield within the * record. Use zero for the first instance. * @return The value of the subfield, or null if it failed for * some reason. The returned pointer is to internal data * and should not be modified or freed by the application. */ String getStringSubfield(String pszField, int iFieldIndex, String pszSubfield, int iSubfieldIndex) { DDFField poField; /* -------------------------------------------------------------------- */ /* Fetch the field. If this fails, return zero. */ /* -------------------------------------------------------------------- */ poField = findField(pszField, iFieldIndex); if (poField == null) { return null; } /* -------------------------------------------------------------------- */ /* Get the subfield definition */ /* -------------------------------------------------------------------- */ DDFSubfieldDefinition poSFDefn = poField.getFieldDefn() .findSubfieldDefn(pszSubfield); if (poSFDefn == null) { return null; } /* -------------------------------------------------------------------- */ /* Get a pointer to the data. */ /* -------------------------------------------------------------------- */ MutableInt nBytesRemaining = new MutableInt(); byte[] pachData = poField.getSubfieldData(poSFDefn, nBytesRemaining, iSubfieldIndex); /* -------------------------------------------------------------------- */ /* Return the extracted value. */ /* -------------------------------------------------------------------- */ return poSFDefn.extractStringData(pachData, nBytesRemaining.value, null); } }