// ********************************************************************** // // <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/DcwColumnInfo.java,v $ // $Revision: 1.6 $ $Date: 2007/01/26 15:57:18 $ $Author: dietrick $ // ********************************************************************** package com.bbn.openmap.layer.vpf; import java.io.EOFException; import com.bbn.openmap.io.BinaryFile; import com.bbn.openmap.io.FormatException; /** * Encapsulate the information about a particular column in a vpf * table. This class can read both VPF V1 (MIL-STD-600006, dated 1992) * and VPF V2 (MIL-STD-2407, dated 1996, supercedes V1) */ public class DcwColumnInfo { /** the name of the column */ final private String columnName; /** the fieldtype of the contained data */ final private char fieldType; /** the number of values (-1 indicates variable) */ final private int numberOfElements; /** the keytype (primary key, non-key, foreign key) */ final private char keyType; /** optional text description of what the column is for */ final private String columnDescription; /** * optional table that provides descriptions of what the values in * this column are */ private String valueDescriptionTable = null; /** name of the optional thematic index created for this column */ private String thematicIndexName = null; /** name of the optional narrative table for this column */ private String narrativeTable = null; /** VPF Column Type Constants */ public static final char VPF_COLUMN_TEXT = 'T'; public static final char VPF_COLUMN_TEXTL1 = 'L'; public static final char VPF_COLUMN_TEXTL2 = 'M'; public static final char VPF_COLUMN_TEXTL3 = 'N'; public static final char VPF_COLUMN_FLOAT = 'F'; public static final char VPF_COLUMN_DOUBLE = 'R'; public static final char VPF_COLUMN_SHORT = 'S'; public static final char VPF_COLUMN_INT = 'I'; public static final char VPF_COLUMN_FLOAT_2COORD = 'C'; public static final char VPF_COLUMN_DOUBLE_2COORD = 'B'; public static final char VPF_COLUMN_FLOAT_3COORD = 'Z'; public static final char VPF_COLUMN_DOUBLE_3COORD = 'Y'; public static final char VPF_COLUMN_DATE = 'D'; public static final char VPF_COLUMN_NULL = 'X'; public static final char VPF_COLUMN_TRIPLET = 'K'; /** * VPF Column Type Constant for a column that can be either int or * short. This value will never be read from a VPF file, its a * special value that is accepted by lookupSchema */ public static final char VPF_COLUMN_INT_OR_SHORT = 'i'; /** VPF Column Key Type Constants */ public static final char VPF_COLUMN_PRIMARY_KEY = 'P'; public static final char VPF_COLUMN_FOREIGN_KEY = 'F'; public static final char VPF_COLUMN_NON_KEY = 'N'; /** * Construct a DcwColumnInfo from the specified input stream. * * @param inputFile the filestream to construct from * @exception EOFException when the first character read is a ';', * indicating that we've reached the end of the column * list; also thrown for an end of file * @exception FormatException some error was detected while * reading the info for the column. */ public DcwColumnInfo(BinaryFile inputFile) throws EOFException, FormatException { char delim = inputFile.readChar(); if (delim == ';') throw new EOFException(); StringBuffer buildstring = new StringBuffer(); do { buildstring.append(Character.toLowerCase(delim)); } while ((delim = inputFile.readChar()) != '='); columnName = buildstring.toString().trim().intern();// Collapse all blanks fieldType = inputFile.readChar(); delim = inputFile.readChar(); if (delim != ',') { //only legal delimiter if (delim != ' ') { //one DCW file uses this instead throw new com.bbn.openmap.io.InvalidCharException("Illegal delimiter character", delim); } } buildstring = new StringBuffer(); while ((delim = inputFile.readChar()) != ',') { // field length occasionally has trailing whitespace... if (!Character.isWhitespace(delim)) { buildstring.append(delim); //assumes not like "1 4" } } String nEls = buildstring.toString(); numberOfElements = (nEls.equals("*")) ? -1 : Integer.parseInt(nEls); // Sanity check the column schema... a few VPF primitives are // not // allowed to show up in arrays. complain about that now... if (numberOfElements != 1) { switch (fieldType) { case VPF_COLUMN_FLOAT: case VPF_COLUMN_DOUBLE: case VPF_COLUMN_SHORT: case VPF_COLUMN_INT: case VPF_COLUMN_DATE: case VPF_COLUMN_NULL: case VPF_COLUMN_TRIPLET: throw new FormatException("Illegal array type: " + fieldType + "for column " + columnName); default: //legal break; } } String tmpkeyType = readColumnText(inputFile); if (tmpkeyType == null) { throw new FormatException("keyType is required column info"); } tmpkeyType = tmpkeyType.trim(); if (tmpkeyType.length() == 1) { keyType = tmpkeyType.charAt(0); } else { throw new FormatException("keyType is supposed to be 1 character"); } columnDescription = readColumnText(inputFile); if (columnDescription == null) { return; } valueDescriptionTable = readColumnTextLowerCase(inputFile); if (valueDescriptionTable == null) { return; } if (valueDescriptionTable.equals("-")) { valueDescriptionTable = null; } else { valueDescriptionTable = valueDescriptionTable.intern(); } thematicIndexName = readColumnTextLowerCase(inputFile); if (thematicIndexName == null) { return; } if (thematicIndexName.equals("-")) { thematicIndexName = null; } else { thematicIndexName = thematicIndexName.intern(); } narrativeTable = readColumnTextLowerCase(inputFile); if (narrativeTable == null) { return; } if (narrativeTable.equals("-")) { narrativeTable = null; } else { narrativeTable = narrativeTable.intern(); } inputFile.assertChar(':'); } /** * Reads a string until the field separator is detected, the * column record separator is detected, or and end-of-file is hit. * * @return the string read from the file * @param inputFile the file to read the field from * @param toLower convert the string to lower-case * @exception FormatException ReadChar IOExceptions rethrown as * FormatExceptions */ private String readColumnText(BinaryFile inputFile) throws FormatException { StringBuffer buildretval = new StringBuffer(); boolean skipnext = false; char tmp; try { while ((tmp = inputFile.readChar()) != ',') { if ((tmp == ':') && !skipnext) { return null; } if (tmp == '\\') { skipnext = true; } else { skipnext = false; buildretval.append(tmp); } } } catch (EOFException e) { //allowable } return buildretval.toString(); } /** * Reads a string until the field separator is detected, the * column record separator is detected, or and end-of-file is hit, * and converts in to lowercase. * * @return the string read from the file, all in lowercase * @param inputFile the file to read the field from * @param toLower convert the string to lower-case * @exception FormatException ReadChar IOExceptions rethrown as * FormatExceptions */ private String readColumnTextLowerCase(BinaryFile inputFile) throws FormatException { StringBuffer buildretval = new StringBuffer(); boolean skipnext = false; char tmp; try { while ((tmp = inputFile.readChar()) != ',') { if ((tmp == ':') && !skipnext) { return null; } if (tmp == '\\') { skipnext = true; } else { skipnext = false; buildretval.append(Character.toLowerCase(tmp)); } } } catch (EOFException e) { //allowable } return buildretval.toString(); } /** * Claim that the column has a particular schema * * @param type the FieldType (datatype) this column is expected to * contain legal values are specified by the VPF standard. * the non-standard value 'i' is also accepted (equivalent * to 'I' or 'S'), indicating an integral type. * @param length the number of elements in this column * @param strictlength false means that variable length columns * can be fixed length instead * @exception FormatException the column is not of the particular * type/length */ public void assertSchema(char type, int length, boolean strictlength) throws FormatException { if ((type != fieldType) && !((type == 'i') && ((fieldType == VPF_COLUMN_INT) || (fieldType == VPF_COLUMN_SHORT)))) { throw new FormatException("AssertSchema failed on fieldType!"); } if ((strictlength && (length != numberOfElements)) || (!strictlength && (length != -1) && (length != numberOfElements))) { throw new FormatException("AssertSchema failed on length!"); } } /** * the number of bytes a field of this type takes in the input * file * * @return the number of bytes (-1 for a variable-length field) * @exception FormatException the FieldType of this Column is not * a valid VPF fieldtype */ public int fieldLength() throws FormatException { if (numberOfElements == -1) { return -1; } switch (fieldType) { case VPF_COLUMN_TEXT: case VPF_COLUMN_TEXTL1: case VPF_COLUMN_TEXTL3: case VPF_COLUMN_TEXTL2: //various text string types return numberOfElements; case VPF_COLUMN_FLOAT: //floats return 4; case VPF_COLUMN_DOUBLE: //doubles return 8; case VPF_COLUMN_SHORT: //shorts return 2; case VPF_COLUMN_INT: //ints return 4; case VPF_COLUMN_FLOAT_2COORD: //2-coord floats return numberOfElements * 8; case VPF_COLUMN_DOUBLE_2COORD: //2-coord doubles return numberOfElements * 16; case VPF_COLUMN_FLOAT_3COORD: //3-coord floats return numberOfElements * 12; case VPF_COLUMN_DOUBLE_3COORD: //3-coord doubles return numberOfElements * 24; case VPF_COLUMN_DATE: //dates return 20; case VPF_COLUMN_NULL: //nulls return 0; case VPF_COLUMN_TRIPLET: //cross-tile identifiers return -1; //variable length default: { throw new FormatException("Unknown field type: " + fieldType); } } //unreached } /** * get the name of the column * * @return the name of the column */ public String getColumnName() { return columnName; } /** * get the VPF datatype of the column * * @return the VPF datatype */ public char getFieldType() { return fieldType; } /** * get the number of elements * * @return the number of elements */ public int getNumberOfElements() { return numberOfElements; } /** * get the VPF key type (one of VPF_COLUMN_PRIMARY_KEY, * VPF_COLUMN_FOREIGN_KEY, or VPF_COLUMN_NON_KEY) * * @return the vpf key type */ public char getKeyType() { return keyType; } /** * Return <code>true</code> if this column is a primary key. For * any valid column, exactly one of isPrimaryKey, isForeignKey and * isNonKey will be <code>true</code>. * * @return true for a primary key, false otherwise. * @see #isForeignKey() * @see #isNonKey() */ public boolean isPrimaryKey() { return (keyType == VPF_COLUMN_PRIMARY_KEY); } /** * Return <code>true</code> if this column is a foreign key. For * any valid column, exactly one of isPrimaryKey, isForeignKey and * isNonKey will be <code>true</code>. * * @return true for a foreign key, false otherwise. * @see #isPrimaryKey() * @see #isNonKey() */ public boolean isForeignKey() { return (keyType == VPF_COLUMN_FOREIGN_KEY); } /** * Return <code>true</code> if this column is not a key column. * For any valid column, exactly one of isPrimaryKey, isForeignKey * and isNonKey will be <code>true</code>. * * @return false for a primary or foreign key, true otherwise. * @see #isForeignKey() * @see #isPrimaryKey() */ public boolean isNonKey() { return (keyType == VPF_COLUMN_NON_KEY); } /** * Get the column description * * @return the column description (possibly <code>null</code>) */ public String getColumnDescription() { return columnDescription; } /** * Get the name of the value description table * * @return the name of the value description table (possibly * <code>null</code>). The same as getVDT() * @see #getVDT() */ public String getValueDescriptionTable() { return valueDescriptionTable; } /** * Get the name of the value description table * * @return the name of the value description table (possibly * <code>null</code>). The same as * getValueDescriptionTable * @see #getValueDescriptionTable() */ public String getVDT() { return valueDescriptionTable; } /** * get the name of the thematic index * * @return the thematic index name (possibly <code>null</code>) */ public String getThematicIndexName() { return thematicIndexName; } /** * get the name of the narrative table * * @return the name of the narrative table (possibly * <code>null</code>) */ public String getNarrativeTable() { return narrativeTable; } /** * Read an element of the type specified by the column * * @return the value read from the input file * @exception EOFException an end-of-file was encountered before * reading any of the field * @exception FormatException some data-consistency check failed * while reading the data, or an end-of-file condition * popped up in the middle of reading a field (partial * read) */ public Object parseField(BinaryFile inputFile) throws EOFException, FormatException { // See table 56, p 79 of MIL-STD-600006 (1992 VPF Standard) // See table 10, p 51 of MIL-STD-2407 (1996 VPF Standard // supercedes 600006) boolean haveElements = (numberOfElements != -1); int numels = numberOfElements; switch (fieldType) { case VPF_COLUMN_TEXT: { if (!haveElements) {//Variable length string numels = inputFile.readInteger(); } if (numels == 0) { return ""; } String s = inputFile.readFixedLengthString(numels); if (haveElements) {//Fixed Length Strings loose trailing // whitespace s = s.trim(); } return s; } case VPF_COLUMN_TEXTL1: { if (!haveElements) {//Variable length string numels = inputFile.readInteger(); } if (numels == 0) { return ""; } byte[] str = inputFile.readBytes(numels, false); try { String s = new String(str, "ISO8859_1"); if (haveElements) {//Fixed Length Strings loose // trailing whitespace s = s.trim(); } return s; } catch (java.io.UnsupportedEncodingException uee) { return str; } } case VPF_COLUMN_TEXTL2: case VPF_COLUMN_TEXTL3: { if (!haveElements) {//Variable length string numels = inputFile.readInteger(); } if (numels == 0) { return new byte[0]; } return inputFile.readBytes(numels, false); } case VPF_COLUMN_FLOAT: { return new Float(inputFile.readFloat()); } case VPF_COLUMN_DOUBLE: { return new Double(inputFile.readDouble()); } case VPF_COLUMN_SHORT: { return new Short(inputFile.readShort()); } case VPF_COLUMN_INT: { return new Integer(inputFile.readInteger()); } case VPF_COLUMN_FLOAT_2COORD: { //2-coord floats if (!haveElements) { numels = inputFile.readInteger(); } return new CoordFloatString(numels, 2, inputFile); } case VPF_COLUMN_DOUBLE_2COORD: { //2-coord doubles if (!haveElements) { numels = inputFile.readInteger(); } return new CoordDoubleString(numels, 2, inputFile); } case VPF_COLUMN_FLOAT_3COORD: { //3-coord floats if (!haveElements) { numels = inputFile.readInteger(); } return new CoordFloatString(numels, 3, inputFile); } case VPF_COLUMN_DOUBLE_3COORD: { //3-coord doubles if (!haveElements) { numels = inputFile.readInteger(); } return new CoordDoubleString(numels, 3, inputFile); } case VPF_COLUMN_DATE: { inputFile.readBytes(20, false); return "[skipped date]"; } case VPF_COLUMN_NULL: { return "[Null Field Type]"; } case VPF_COLUMN_TRIPLET: { return new DcwCrossTileID(inputFile); } default: { throw new FormatException("Unknown field type: " + fieldType); } } //unreached } /** * produce a nice printed version of all our contained information * * @return a nice little string */ public String toString() { StringBuffer output = new StringBuffer(); output.append(columnName).append(" ").append(fieldType).append(" "); output.append(numberOfElements).append(" "); output.append(keyType).append(" "); output.append(columnDescription).append(" ").append(valueDescriptionTable).append(" "); output.append(thematicIndexName).append(" ").append(narrativeTable); return output.toString(); } }