// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/vpf/DcwRecordFile.java,v $ // $Revision: 1.6 $ $Date: 2005/01/10 16:36:21 $ $Author: dietrick $ // ********************************************************************** package com.bbn.openmap.layer.vpf; import java.io.EOFException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.bbn.openmap.MoreMath; import com.bbn.openmap.io.BinaryBufferedFile; import com.bbn.openmap.io.BinaryFile; import com.bbn.openmap.io.FormatException; /** * Read and encapsulate VPF table files. */ public class DcwRecordFile { /** input is read from this file */ protected BinaryFile inputFile = null; /** the description of the table [read from the file] */ protected String tableDescription = null; /** the name of another table that describes what this one is for */ protected String documentationFileName = null; /** number of bytes consumed by the table header */ private int headerLength = 4; //for the 4 bytes of the // headerlength field /** * big-endian (<code>true</code>) or little-endian ( * <code>false</code>) */ protected boolean MSBFirst = false; /** ordered set of columns (read from table header) */ protected DcwColumnInfo[] columnInfo = null; /** * length of a record (<code>-1</code> indicates * variable-length record) */ protected int recordLength = 0; /** * for tables with variable-length records, the corresponding * variable-length index */ protected DcwVariableLengthIndexFile vli = null; /** the name of the file */ final protected String filename; /** the name of the table */ protected String tablename = null; /** * remember the byte order for later file openings, true for MSB * first */ protected boolean byteorder = true; /** the record number that a call to parseRow() will return */ int cursorRow = -1; /** the name of the row identifier column "id" */ public static final String ID_COLUMN_NAME = "id"; /** * Open a DcwRecordFile and completely initialize it * * @param name the name of the file to use for input * @exception FormatException some problem was encountered dealing * with the file */ public DcwRecordFile(String name) throws FormatException { this(name, false); } /** * Open a DcwRecordFile * * @param name the name of the file to use for input * @param deferInit if <code>true</code>, don't actually open * files and initialize the object. In this state, the only * method that should be called is finishInitialization. * @exception FormatException some problem was encountered dealing * with the file * @see #finishInitialization() */ public DcwRecordFile(String name, boolean deferInit) throws FormatException { this.filename = name; if (!deferInit) { finishInitialization(); } } /** * Strip the tablename out of the filename. Strips both path * information and the trailing '.', if it exists. */ private void internTableName() { int strlen = filename.length(); int firstchar = filename.lastIndexOf('/'); int lastchar = filename.endsWith(".") ? strlen - 1 : strlen; tablename = filename.substring(firstchar + 1, lastchar) .toLowerCase() .intern(); } /** * Returns the File this instance is using * * @return the File being read */ public String getTableFile() { return filename; } /** * return the name of the table */ public String getTableName() { return tablename; } /** * Complete initialization of this object. This function should * only be called once, and only if the object was constructed * with deferred initialization. * * @exception FormatException some problem was encountered dealing * with the file */ public synchronized void finishInitialization() throws FormatException { internTableName(); try { inputFile = new BinaryBufferedFile(filename); } catch (IOException e) { throw new FormatException(e.toString()); } try { byte preHeaderLen[] = inputFile.readBytes(4, false); char delim = inputFile.readChar(); switch (delim) { case 'L': case 'l': delim = inputFile.readChar(); //Intentional fall through to set byteorder case ';': //default is LSB first byteorder = false; inputFile.byteOrder(byteorder); break; case 'M': case 'm': //alternatively, it can be MSB first byteorder = true; inputFile.byteOrder(byteorder); delim = inputFile.readChar(); break; default: throw new FormatException("Invalid Byte Encoding Format"); } headerLength += MoreMath.BuildInteger(preHeaderLen, byteorder); if (delim != ';') {//Sanity check the input throw new FormatException("Unexpected character in header"); } tableDescription = inputFile.readToDelimiter(';'); documentationFileName = inputFile.readToDelimiter(';'); if ("-".equals(documentationFileName)) { documentationFileName = null; } ArrayList<Object> tmpcols = new ArrayList<Object>(); try { while (true) { DcwColumnInfo dci = new DcwColumnInfo(inputFile); int collen = dci.fieldLength(); if ((collen == -1) || (recordLength == -1)) { recordLength = -1; } else { recordLength += collen; } tmpcols.add(dci); } } catch (EOFException e) { } columnInfo = new DcwColumnInfo[tmpcols.size()]; tmpcols.toArray(columnInfo); cursorRow = 1; } catch (EOFException e) { throw new FormatException("Caught EOFException: " + e.getMessage()); } catch (NullPointerException npe) { } } /** * Returns a TilingAdapter for the selected column. * * @param primColumnName the name of the primitive column * @return an appropriate TilingAdapter instance or null */ public TilingAdapter getTilingAdapter(String primColumnName) { return getTilingAdapter(-1, whatColumn(primColumnName)); } /** * Returns a TilingAdapter for the selected column. * * @param primColumnName the name of the primitive column * @param tileColumnName the name of the tile_id column * @return an appropriate TilingAdapter instance or null */ public TilingAdapter getTilingAdapter(String tileColumnName, String primColumnName) { return getTilingAdapter(whatColumn(tileColumnName), whatColumn(primColumnName)); } /** * Returns a TilingAdapter for the selected column. * * @param primColumn the position of the primitive column * @param tileColumn the position of the tile_id column * @return an appropriate TilingAdapter instance or null */ public TilingAdapter getTilingAdapter(int tileColumn, int primColumn) { DcwColumnInfo tile = (tileColumn != -1) ? columnInfo[tileColumn] : null; if (primColumn == -1) { return null; } DcwColumnInfo prim = columnInfo[primColumn]; TilingAdapter retval = null; char primFieldType = prim.getFieldType(); if (tile == null) { if (primFieldType == 'K') { retval = new TilingAdapter.CrossTileAdapter(primColumn); } else if ((primFieldType == 'I') || (primFieldType == 'S')) { retval = new TilingAdapter.UntiledAdapter(primColumn); } } else { if (primFieldType == 'K') { //error??? duplicate tile data retval = new TilingAdapter.CrossTileAdapter(primColumn); } else if ((primFieldType == 'I') || (primFieldType == 'S')) { retval = new TilingAdapter.TiledAdapter(tileColumn, primColumn); } } return retval; } /** * Get the column number for a set of column names. * * @param names the names of the columns * @return an array of column numbers * @exception FormatException the table does not match the * specified schema */ public int[] lookupSchema(String[] names, boolean mustExist) throws FormatException { int retval[] = new int[names.length]; for (int i = 0; i < retval.length; i++) { retval[i] = whatColumn(names[i]); if ((retval[i] == -1) && mustExist) { throw new FormatException("Column " + names[i] + " doesn't exist"); } } return retval; } /** * Get the column number for a set of column names. * * @param names the names of the columns * @param type in same order as names * @param length in same order as names (-1 for a variable length * column) * @param strictlength false means that variable length columns * can be fixed-length instead * @param mustExist if true and a column doesn't exist, method * returns null * @return an array of column numbers * @exception FormatException the table does not match the * specified schema */ public int[] lookupSchema(String[] names, boolean mustExist, char type[], int length[], boolean strictlength) throws FormatException { int retval[] = lookupSchema(names, mustExist); if ((type.length == names.length) && (length.length == names.length)) { for (int i = 0; i < retval.length; i++) { if (retval[i] != -1) { columnInfo[retval[i]].assertSchema(type[i], length[i], strictlength); } } } return retval; } /** * Good for looking at the contents of a data file, this method * dumps a bunch of rows to System.out. It parses all the lines of * the file. * * @exception FormatException some kind of data format error was * encountered while parsing the file */ public void parseAllRowsAndPrintSome() throws FormatException { int row_id_column = whatColumn(ID_COLUMN_NAME); String vectorString = null; int rowcount = 0; for (List<Object> l = new ArrayList<Object>(getColumnCount()); parseRow(l);) { int cnt = ((Number) (l.get(row_id_column))).intValue(); if (cnt != ++rowcount) { System.out.println("Non-consecutive row number. Expected " + rowcount + " got " + cnt); } vectorString = VPFUtil.listToString(l); if ((rowcount < 20) || (rowcount % 100 == 0)) { System.out.println(vectorString); } } if (rowcount > 20) System.out.println(vectorString); } /** * Good for looking at the contents of a data file, this method * dumps a bunch of rows to System.out. (Using seekToRow to move * between records * * @exception FormatException some kind of data format error was * encountered while parsing the file */ public void parseSomeRowsAndPrint() throws FormatException { int row_id_column = whatColumn(ID_COLUMN_NAME); int rowcount = getRecordCount(); for (int i = 1; i <= rowcount; i++) { if ((i > 10) && ((i % 100) != 0) && (i != rowcount)) { continue; } seekToRow(i); List<Object> l = parseRow(); int cnt = ((Integer) (l.get(row_id_column))).intValue(); if (cnt != i) { System.out.println("Possible incorrect seek for row number " + i + " got " + cnt); } System.out.println(VPFUtil.listToString(l)); } } /** * Return a row from the table. repeatedly calling parseRow gets * consecutive rows. * * @return a List of fields read from the table * @exception FormatException an error was encountered reading the * row */ public List<Object> parseRow() throws FormatException { List<Object> retval = new ArrayList<Object>(getColumnCount()); return parseRow(retval) ? retval : null; } /** * Return a row from the table. repeatedly calling parseRow gets * consecutive rows. * * @param retval append the fields from a row in the table. * clear() is called before any real work is done. * @return true is we read a row, false if no more rows are * available * @exception FormatException an error was encountered reading the * row * @see java.util.List#clear() */ public synchronized boolean parseRow(List<Object> retval) throws FormatException { retval.clear(); try { for (int i = 0; i < columnInfo.length; i++) { Object newobj = columnInfo[i].parseField(inputFile); retval.add(newobj); } cursorRow++; return true; } catch (FormatException f) { throw new FormatException("DcwRecordFile: parserow on table " + filename + ": " + f.getMessage()); } catch (EOFException e) { if (!retval.isEmpty()) { throw new FormatException("DcwRecordFile: hit EOF when list = " + VPFUtil.listToString(retval)); } try { if (inputFile.available() > 0) { throw new FormatException("DcwRecordFile: hit EOF with available = " + inputFile.available() + " when list = " + VPFUtil.listToString(retval)); } } catch (IOException i) { throw new FormatException("IOException calling available()"); } return false; } } /** * Returns the documentation file associated with this table. * * @return the doc file - may be null */ public String getDocumentationFilename() { return documentationFileName; } /** * Returns the table description for this table. * * @return the table description - may be null */ public String getDescription() { return tableDescription; } /** * get the length of a single record * * @return -1 indicates a variably sized record */ public int getRecordLength() { return recordLength; } /** * Gets the number of records in the table. * * @return the number of records * @exception FormatException some problem was encountered dealing * with the file */ public int getRecordCount() throws FormatException { try { if (recordLength == -1) { return vli().getRecordCount(); } else { return (int) (inputFile.length() - headerLength) / recordLength; } } catch (IOException i) { System.out.println("RecordCount: io exception " + i.getMessage()); } catch (NullPointerException npe) { } return -1; } final private DcwVariableLengthIndexFile vli() throws FormatException, IOException { if (vli == null) { openVLI(); } return vli; } /** * Opens the associated variable length index for the file * * @exception FormatException an error. */ private void openVLI() throws FormatException, IOException { String realfname = filename; boolean endwithdot = realfname.endsWith("."); String fopen; if (endwithdot) { StringBuffer newf = new StringBuffer(realfname.substring(0, realfname.length() - 2)); fopen = newf.append("x.").toString(); } else { StringBuffer newf = new StringBuffer(realfname.substring(0, realfname.length() - 1)); fopen = newf.append("x").toString(); } vli = new DcwVariableLengthIndexFile(new BinaryBufferedFile(fopen), byteorder); } /** * Parses the row specified by rownumber * * @param rownumber the number of the row to return * [1..recordCount] * @return the values contained in the row * @exception FormatException data format errors */ public List<Object> getRow(int rownumber) throws FormatException { List<Object> l = new ArrayList<Object>(getColumnCount()); return getRow(l, rownumber) ? l : null; } /** * Parses the row specified by rownumber * * @param rownumber the number of the row to return * [1..recordCount] * @param retval values contained in the row * @exception FormatException data format errors * @see #parseRow() */ public synchronized boolean getRow(List<Object> retval, int rownumber) throws FormatException { if (inputFile == null) { reopen(rownumber); } else { seekToRow(rownumber); } return parseRow(retval); } /** * moves the input cursor to the specified row [affects subsequent * calls parseRow.] * * @param recordNumber the number of the row to seek to * @exception FormatException data format errors * @exception IllegalArgumentException recordNumber less than 1 */ public synchronized void seekToRow(int recordNumber) throws FormatException { if (recordNumber <= 0) { throw new IllegalArgumentException("DcwRecordFile: seekToRow(" + recordNumber + "," + getRecordCount() + "," + filename + ")"); } if (recordNumber == cursorRow) { return; } cursorRow = recordNumber; int offset = 0; try { if ((recordLength == -1) && (recordNumber != 1)) { offset = vli().recordOffset(recordNumber); } else { offset = (recordLength * (recordNumber - 1)) + headerLength; } inputFile.seek(offset); } catch (IOException io) { throw new FormatException("SeekToRow IOException " + io.getMessage() + " offset: " + offset + " " + tablename + " " + filename); } } /** * Returns the index into columnInfo of the column with the * specified name * * @param columnname the column name to match * @return an index into columnInfo (-1 indicates no such column) */ public int whatColumn(String columnname) { for (int i = 0; i < columnInfo.length; i++) { if (columnInfo[i].getColumnName().equals(columnname)) { return i; } } return -1; } /** * Returns the name of a column * * @param index the column to get the name for * @return the columnName */ public String getColumnName(int index) { return columnInfo[index].getColumnName(); } /** * Prints the table information to System.out. * * @exception FormatException some problem was encountered dealing * with the file */ public void printSchema() throws FormatException { System.out.println("File Name: " + filename + "\nTable name: " + tablename + "\nTable Description: " + tableDescription + "\nDocumentation File Name: " + documentationFileName + "\nRecord Length: " + recordLength + " Record Count: " + getRecordCount()); for (int i = 0; i < columnInfo.length; i++) { System.out.println("Column " + i + " " + columnInfo[i].toString()); } } /** Closes the associated input file. (may later get reopened) */ public synchronized void close() { cursorRow = -1; try { if (inputFile != null) { inputFile.close(); } inputFile = null; } catch (IOException i) { System.out.println("Caught ioexception " + i.getMessage()); } } /** * Reopen the associated input file. * * @param seekRow the row to seek to upon reopening the file. If * seekRow is invalid (less than 1), then the input stream * is in an undefined location, and seekToRow (or * getRow(int)) must be called before parseRow * @exception FormatException some error was encountered in * reopening file or seeking to the desired row. * @see #parseRow() * @see #getRow(int) * @see #close() */ public synchronized void reopen(int seekRow) throws FormatException { try { if (inputFile == null) { inputFile = new BinaryBufferedFile(filename); inputFile.byteOrder(byteorder); } if (seekRow > 0) { seekToRow(seekRow); } } catch (IOException i) { throw new FormatException(i.getClass() + ": " + i.getMessage()); } } /** * Returns the number of columns this table has */ final public int getColumnCount() { return columnInfo.length; } /** * Return the column info for this table. * <p> * NOTE: modifying this array is likely to cause problems... */ final public DcwColumnInfo[] getColumnInfo() { return columnInfo; } /** releases associated resources */ protected void finalize() { close(); } /** * An test main for parsing VPF table files. * * @param args file names to be read */ public static void main(String args[]) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); try { DcwRecordFile foo = new DcwRecordFile(args[i]); foo.printSchema(); foo.close(); foo.reopen(1); for (List<Object> l = new ArrayList<Object>(); foo.parseRow(l);) { System.out.println(VPFUtil.listToString(l)); } foo.close(); } catch (Exception e) { e.printStackTrace(); } } } }