/****************************************************************************** * 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.io.BinaryBufferedFile; import com.bbn.openmap.io.BinaryFile; import com.bbn.openmap.util.Debug; /** * The class that represents a ISO 8211 file. */ public class DDFModule implements DDFConstants { protected BinaryFile fpDDF; protected String fileName; protected long nFirstRecordOffset; protected byte _interchangeLevel; protected byte _inlineCodeExtensionIndicator; protected byte _versionNumber; protected byte _appIndicator; protected int _fieldControlLength; protected String _extendedCharSet; // 4 characters protected int _recLength; protected byte _leaderIden; protected int _fieldAreaStart; protected int _sizeFieldLength; protected int _sizeFieldPos; protected int _sizeFieldTag; protected Vector paoFieldDefns; //DDFFieldDefinitions protected DDFRecord poRecord; /** * The constructor. Need to call open() if this constructor is * used. */ public DDFModule() { paoFieldDefns = null; poRecord = null; fpDDF = null; } public DDFModule(String ddfName) throws IOException { open(ddfName); } /** * Close an ISO 8211 file. Just close the file pointer to the * file. */ public void close() { if (fpDDF != null) { try { fpDDF.close(); } catch (IOException ioe) { Debug.error("DDFModule IOException when closing DDFModule file"); } fpDDF = null; } } /** * Clean up, get rid of data and close file pointer. */ public void destroy() { close(); // Cleanup the working record. poRecord = null; // Cleanup the field definitions. paoFieldDefns = null; } /** * Open a ISO 8211 (DDF) file for reading, and read the DDR record * to build the field definitions. * * If the open succeeds the data descriptive record (DDR) will * have been read, and all the field and subfield definitions will * be available. * * @param pszFilename The name of the file to open. */ public BinaryFile open(String pszFilename) throws IOException { fileName = pszFilename; fpDDF = new BinaryBufferedFile(pszFilename); // Read the 24 byte leader. byte[] achLeader = new byte[DDF_LEADER_SIZE]; if (fpDDF.read(achLeader) != DDF_LEADER_SIZE) { destroy(); if (Debug.debugging("iso8211")) { Debug.output("DDFModule: Leader is short on DDF file " + pszFilename); } return null; } // Verify that this appears to be a valid DDF file. int i; boolean bValid = true; for (i = 0; i < (int) DDF_LEADER_SIZE; i++) { if (achLeader[i] < 32 || achLeader[i] > 126) { bValid = false; } } if (achLeader[5] != '1' && achLeader[5] != '2' && achLeader[5] != '3') { bValid = false; } if (achLeader[6] != 'L') { bValid = false; } if (achLeader[8] != '1' && achLeader[8] != ' ') { bValid = false; } // Extract information from leader. if (bValid) { _recLength = Integer.parseInt(new String(achLeader, 0, 5)); _interchangeLevel = achLeader[5]; _leaderIden = achLeader[6]; _inlineCodeExtensionIndicator = achLeader[7]; _versionNumber = achLeader[8]; _appIndicator = achLeader[9]; _fieldControlLength = Integer.parseInt(new String(achLeader, 10, 2)); _fieldAreaStart = Integer.parseInt(new String(achLeader, 12, 5)); _extendedCharSet = new String((char) achLeader[17] + "" + (char) achLeader[18] + "" + (char) achLeader[19]); _sizeFieldLength = Integer.parseInt(new String(achLeader, 20, 1)); _sizeFieldPos = Integer.parseInt(new String(achLeader, 21, 1)); _sizeFieldTag = Integer.parseInt(new String(achLeader, 23, 1)); if (_recLength < 12 || _fieldControlLength == 0 || _fieldAreaStart < 24 || _sizeFieldLength == 0 || _sizeFieldPos == 0 || _sizeFieldTag == 0) { bValid = false; } if (Debug.debugging("iso8211")) { Debug.output("bValid = " + bValid + ", from " + new String(achLeader)); Debug.output(toString()); } } // If the header is invalid, then clean up, report the error // and return. if (!bValid) { destroy(); if (Debug.debugging("iso8211")) { Debug.error("DDFModule: File " + pszFilename + " does not appear to have a valid ISO 8211 header."); } return null; } if (Debug.debugging("iso8211")) { Debug.output("DDFModule: header parsed successfully"); } /* -------------------------------------------------------------------- */ /* Read the whole record into memory. */ /* -------------------------------------------------------------------- */ byte[] pachRecord = new byte[_recLength]; System.arraycopy(achLeader, 0, pachRecord, 0, achLeader.length); int numNewRead = pachRecord.length - achLeader.length; if (fpDDF.read(pachRecord, achLeader.length, numNewRead) != numNewRead) { if (Debug.debugging("iso8211")) { Debug.error("DDFModule: Header record is short on DDF file " + pszFilename); } return null; } /* First make a pass counting the directory entries. */ int nFieldEntryWidth = _sizeFieldLength + _sizeFieldPos + _sizeFieldTag; int nFieldDefnCount = 0; for (i = DDF_LEADER_SIZE; i < _recLength; i += nFieldEntryWidth) { if (pachRecord[i] == DDF_FIELD_TERMINATOR) break; nFieldDefnCount++; } /* Allocate, and read field definitions. */ paoFieldDefns = new Vector(); for (i = 0; i < nFieldDefnCount; i++) { if (Debug.debugging("iso8211")) { Debug.output("DDFModule.open: Reading field " + i); } byte[] szTag = new byte[128]; int nEntryOffset = DDF_LEADER_SIZE + i * nFieldEntryWidth; int nFieldLength, nFieldPos; System.arraycopy(pachRecord, nEntryOffset, szTag, 0, _sizeFieldTag); nEntryOffset += _sizeFieldTag; nFieldLength = Integer.parseInt(new String(pachRecord, nEntryOffset, _sizeFieldLength)); nEntryOffset += _sizeFieldLength; nFieldPos = Integer.parseInt(new String(pachRecord, nEntryOffset, _sizeFieldPos)); byte[] subPachRecord = new byte[nFieldLength]; System.arraycopy(pachRecord, _fieldAreaStart + nFieldPos, subPachRecord, 0, nFieldLength); paoFieldDefns.add(new DDFFieldDefinition(this, new String(szTag, 0, _sizeFieldTag), subPachRecord)); } // Free the memory... achLeader = null; pachRecord = null; // Record the current file offset, the beginning of the first // data record. nFirstRecordOffset = fpDDF.getFilePointer(); return fpDDF; } public long getFileLength() throws IOException { return fpDDF.length(); } public int getRecordLength() throws IOException { return _recLength; } /** * Write out module info to debugging file. * * A variety of information about the module is written to the * debugging file. This includes all the field and subfield * definitions read from the header. */ public String toString() { StringBuffer buf = new StringBuffer("DDFModule:\n"); buf.append(" _recLength = ").append(_recLength).append("\n"); buf.append(" _interchangeLevel = ").append(_interchangeLevel).append("\n"); buf.append(" _leaderIden = ").append((char) _leaderIden).append("\n"); buf.append(" _inlineCodeExtensionIndicator = ") .append(_inlineCodeExtensionIndicator).append("\n"); buf.append(" _versionNumber = ").append(_versionNumber).append("\n"); buf.append(" _appIndicator = ").append(_appIndicator).append("\n"); buf.append(" _extendedCharSet = ").append(_extendedCharSet).append("\n"); buf.append(" _fieldControlLength = ").append(_fieldControlLength).append("\n"); buf.append(" _fieldAreaStart = ").append(_fieldAreaStart).append("\n"); buf.append(" _sizeFieldLength = ").append(_sizeFieldLength).append("\n"); buf.append(" _sizeFieldPos = ").append(_sizeFieldPos).append("\n"); buf.append(" _sizeFieldTag = ").append(_sizeFieldTag).append("\n"); return buf.toString(); } public String dump() { StringBuffer buf = new StringBuffer(toString()); DDFRecord poRecord; int iRecord = 0; while ((poRecord = readRecord()) != null) { buf.append(" Record ").append((iRecord++)).append("(") .append(poRecord.getDataSize()).append(" bytes)\n"); for (Iterator it = poRecord.iterator(); it.hasNext(); buf.append(((DDFField) it.next()).toString())) { } } return buf.toString(); } /** * Fetch the definition of the named field. * * This function will scan the DDFFieldDefn's on this module, to * find one with the indicated field name. * * @param pszFieldName The name of the field to search for. The * comparison is case insensitive. * * @return A pointer to the request DDFFieldDefn object is * returned, or null if none matching the name are found. * The return object remains owned by the DDFModule, and * should not be deleted by application code. */ public DDFFieldDefinition findFieldDefn(String pszFieldName) { for (Iterator it = paoFieldDefns.iterator(); it.hasNext();) { DDFFieldDefinition ddffd = (DDFFieldDefinition) it.next(); String pszThisName = ddffd.getName(); if (Debug.debugging("iso8211detail")) { Debug.output("DDFModule.findFieldDefn(" + pszFieldName + ":" + pszFieldName.length() + ") checking against [" + pszThisName + ":" + pszThisName.length() + "]"); } if (pszFieldName.equalsIgnoreCase(pszThisName)) { return ddffd; } } return null; } /** * Read one record from the file, and return to the application. * The returned record is owned by the module, and is reused from * call to call in order to preserve headers when they aren't * being re-read from record to record. * * @return A pointer to a DDFRecord object is returned, or null if * a read error, or end of file occurs. The returned * record is owned by the module, and should not be * deleted by the application. The record is only valid * until the next ReadRecord() at which point it is * overwritten. */ public DDFRecord readRecord() { if (poRecord == null) { poRecord = new DDFRecord(this); } if (poRecord.read()) { return poRecord; } else { return null; } } /** * Method for other components to call to get the DDFModule to * read bytes into the provided array. * * @param toData the bytes to put data into. * @param offset the byte offset to start reading from, whereever * the pointer currently is. * @param length the number of bytes to read. * @return the number of bytes read. */ public int read(byte[] toData, int offset, int length) { if (fpDDF == null) { reopen(); } if (fpDDF != null) { try { return fpDDF.read(toData, offset, length); } catch (IOException ioe) { Debug.error("DDFModule.read(): IOException caught"); } catch (ArrayIndexOutOfBoundsException aioobe) { Debug.error("DDFModule.read(): " + aioobe.getMessage() + " reading from " + offset + " to " + length + " into " + (toData == null ? "null byte[]" : "byte[" + toData.length + "]")); aioobe.printStackTrace(); } } return 0; } /** * Convenience method to read a byte from the data file. Assumes * that you know what you are doing based on the parameters read * in the data file. For DDFFields that haven't loaded their * subfields. */ public int read() { if (fpDDF == null) { reopen(); } if (fpDDF != null) { try { return fpDDF.read(); } catch (IOException ioe) { Debug.error("DDFModule.read(): IOException caught"); } } return 0; } /** * Convenience method to seek to a location in the data file. * Assumes that you know what you are doing based on the * parameters read in the data file. For DDFFields that haven't * loaded their subfields. * * @param pos the byte position to reposition the file pointer to. */ public void seek(long pos) throws IOException { if (fpDDF == null) { reopen(); } if (fpDDF != null) { fpDDF.seek(pos); } else { throw new IOException("DDFModule doesn't have a pointer to a file"); } } /** * Fetch a field definition by index. * * @param i (from 0 to GetFieldCount() - 1. * @return the returned field pointer or null if the index is out * of range. */ public DDFFieldDefinition getField(int i) { if (i >= 0 || i < paoFieldDefns.size()) { return (DDFFieldDefinition) paoFieldDefns.elementAt(i); } return null; } /** * Return to first record. * * The next call to ReadRecord() will read the first data record * in the file. * * @param nOffset the offset in the file to return to. By default * this is -1, a special value indicating that reading * should return to the first data record. Otherwise it is * an absolute byte offset in the file. */ public void rewind(long nOffset) throws IOException { if (nOffset == -1) { nOffset = nFirstRecordOffset; } if (fpDDF != null) { fpDDF.seek(nOffset); // Don't know what this has to do with anything... if (nOffset == nFirstRecordOffset && poRecord != null) { poRecord.clear(); } } } public void reopen() { try { if (fpDDF == null) { fpDDF = new BinaryBufferedFile(fileName); } } catch (IOException ioe) { } } }