/******************************************************************************
* 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.util.Iterator;
import java.util.Vector;
import com.bbn.openmap.layer.vpf.MutableInt;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
/**
* Information from the DDR defining one field. Note that just because
* a field is defined for a DDFModule doesn't mean that it actually
* occurs on any records in the module. DDFFieldDefns are normally
* just significant as containers of the DDFSubfieldDefinitions.
*/
public class DDFFieldDefinition implements DDFConstants {
protected DDFModule poModule;
protected String pszTag;
protected String _fieldName;
protected String _arrayDescr;
protected String _formatControls;
protected boolean bRepeatingSubfields;
protected int nFixedWidth; // zero if variable.
protected DataStructCode _data_struct_code;
protected DataTypeCode _data_type_code;
protected Vector paoSubfieldDefns;
/**
* Fetch a pointer to the field name (tag).
*
* @return this is an internal copy and shouldn't be freed.
*/
public String getName() {
return pszTag;
}
/**
* Fetch a longer descriptio of this field.
*
* @return this is an internal copy and shouldn't be freed.
*/
public String getDescription() {
return _fieldName;
}
/**
* Get the number of subfields.
*/
public int getSubfieldCount() {
if (paoSubfieldDefns != null) {
return paoSubfieldDefns.size();
}
return 0;
}
/**
* Get the width of this field. This function isn't normally used
* by applications.
*
* @return The width of the field in bytes, or zero if the field
* is not apparently of a fixed width.
*/
public int getFixedWidth() {
return nFixedWidth;
}
/**
* Fetch repeating flag.
*
* @return true if the field is marked as repeating.
*/
public boolean isRepeating() {
return bRepeatingSubfields;
}
/** this is just for an S-57 hack for swedish data */
public void setRepeating(boolean val) {
bRepeatingSubfields = val;
}
/** ********************************************************************* */
/* DDFFieldDefn() */
/** ********************************************************************* */
public DDFFieldDefinition() {
poModule = null;
pszTag = null;
_fieldName = null;
_arrayDescr = null;
_formatControls = null;
paoSubfieldDefns = null;
bRepeatingSubfields = false;
}
public DDFFieldDefinition(DDFModule poModuleIn, String pszTagIn,
byte[] pachFieldArea) {
initialize(poModuleIn, pszTagIn, pachFieldArea);
}
/**
* Initialize the field definition from the information in the DDR
* record. This is called by DDFModule.open().
*
* @param poModuleIn DDFModule representing file being read.
* @param pszTagIn the name of this field.
* @param pachFieldArea the data bytes in the file representing
* the field from the header.
*/
public boolean initialize(DDFModule poModuleIn, String pszTagIn,
byte[] pachFieldArea) {
/// pachFieldArea needs to be specified better. It's an
/// offset into a character array, and we need to know what
// it
/// is to scope it better in Java.
int iFDOffset = poModuleIn._fieldControlLength;
poModule = poModuleIn;
pszTag = pszTagIn;
/* -------------------------------------------------------------------- */
/* Set the data struct and type codes. */
/* -------------------------------------------------------------------- */
_data_struct_code = DataStructCode.get((char) pachFieldArea[0]);
_data_type_code = DataTypeCode.get((char) pachFieldArea[1]);
if (Debug.debugging("iso8211")) {
Debug.output("DDFFieldDefinition.initialize(" + pszTagIn
+ "):\n\t\t data_struct_code = " + _data_struct_code
+ "\n\t\t data_type_code = " + _data_type_code
+ "\n\t\t iFDOffset = " + iFDOffset);
}
/* -------------------------------------------------------------------- */
/* Capture the field name, description (sub field names), and */
/* format statements. */
/* -------------------------------------------------------------------- */
byte[] tempData = new byte[pachFieldArea.length - iFDOffset];
System.arraycopy(pachFieldArea,
iFDOffset,
tempData,
0,
pachFieldArea.length - iFDOffset);
MutableInt nCharsConsumed = new MutableInt();
_fieldName = DDFUtils.fetchVariable(tempData,
tempData.length,
DDF_UNIT_TERMINATOR,
DDF_FIELD_TERMINATOR,
nCharsConsumed);
if (Debug.debugging("iso8211")) {
Debug.output("DDFFieldDefinition.initialize(" + pszTagIn
+ "): created field name " + _fieldName);
}
iFDOffset += nCharsConsumed.value;
tempData = new byte[pachFieldArea.length - iFDOffset];
System.arraycopy(pachFieldArea,
iFDOffset,
tempData,
0,
pachFieldArea.length - iFDOffset);
_arrayDescr = DDFUtils.fetchVariable(tempData,
tempData.length,
DDF_UNIT_TERMINATOR,
DDF_FIELD_TERMINATOR,
nCharsConsumed);
iFDOffset += nCharsConsumed.value;
tempData = new byte[pachFieldArea.length - iFDOffset];
System.arraycopy(pachFieldArea,
iFDOffset,
tempData,
0,
pachFieldArea.length - iFDOffset);
_formatControls = DDFUtils.fetchVariable(tempData,
tempData.length,
DDF_UNIT_TERMINATOR,
DDF_FIELD_TERMINATOR,
nCharsConsumed);
/* -------------------------------------------------------------------- */
/* Parse the subfield info. */
/* -------------------------------------------------------------------- */
if (_data_struct_code != DataStructCode.ELEMENTARY) {
if (!buildSubfieldDefns(_arrayDescr)) {
return false;
}
if (!applyFormats(_formatControls)) {
return false;
}
}
return true;
}
/**
* Write out field definition info.
*
* A variety of information about this field definition, and all
* its subfields are written out too.
*/
public String toString() {
StringBuffer buf = new StringBuffer(" DDFFieldDefn:\n");
buf.append(" Tag = ").append(pszTag).append("\n");
buf.append(" _fieldName = ").append(_fieldName).append("\n");
buf.append(" _arrayDescr = ").append(_arrayDescr).append("\n");
buf.append(" _formatControls = ").append(_formatControls).append("\n");
buf.append(" _data_struct_code = ").append(_data_struct_code).append("\n");
buf.append(" _data_type_code = ").append(_data_type_code).append("\n");
if (paoSubfieldDefns != null) {
for (Iterator it = paoSubfieldDefns.iterator(); it.hasNext();) {
buf.append((DDFSubfieldDefinition) it.next());
}
}
return buf.toString();
}
/**
* Based on the list contained in the string, build a set of
* subfield definitions.
*/
protected boolean buildSubfieldDefns(String pszSublist) {
if (pszSublist.charAt(0) == '*') {
bRepeatingSubfields = true;
pszSublist = pszSublist.substring(1);
}
Vector papszSubfieldNames = PropUtils.parseMarkers(pszSublist, "!");
paoSubfieldDefns = new Vector();
for (Iterator it = papszSubfieldNames.iterator(); it.hasNext();) {
DDFSubfieldDefinition ddfsd = new DDFSubfieldDefinition();
ddfsd.setName((String) it.next());
paoSubfieldDefns.add(ddfsd);
}
return true;
}
/**
* Extract a substring terminated by a comma (or end of string).
* Commas in brackets are ignored as terminated with bracket
* nesting understood gracefully. If the returned string would
* being and end with a bracket then strip off the brackets.
* <P>
* Given a string like "(A,3(B,C),D),X,Y)" return "A,3(B,C),D".
* Give a string like "3A,2C" return "3A".
*/
protected String extractSubstring(String pszSrc) {
int nBracket = 0;
int i;
String pszReturn;
for (i = 0; i < pszSrc.length()
&& (nBracket > 0 || pszSrc.charAt(i) != ','); i++) {
if (pszSrc.charAt(i) == '(') {
nBracket++;
} else if (pszSrc.charAt(i) == ')') {
nBracket--;
}
}
if (pszSrc.charAt(0) == '(') {
pszReturn = pszSrc.substring(1, i - 2);
} else {
pszReturn = pszSrc.substring(0, i);
}
return pszReturn;
}
/**
* Given a string that contains a coded size symbol, expand it
* out.
*/
protected String expandFormat(String pszSrc) {
StringBuffer szDest = new StringBuffer();
int iSrc = 0;
int nRepeat = 0;
while (iSrc < pszSrc.length()) {
/*
* This is presumably an extra level of brackets around
* some binary stuff related to rescanning which we don't
* care to do (see 6.4.3.3 of the standard. We just strip
* off the extra layer of brackets
*/
if ((iSrc == 0 || pszSrc.charAt(iSrc - 1) == ',')
&& pszSrc.charAt(iSrc) == '(') {
String pszContents = extractSubstring(pszSrc + iSrc);
String pszExpandedContents = expandFormat(pszContents);
szDest.append(pszExpandedContents);
iSrc = iSrc + pszContents.length() + 2;
} else if ((iSrc == 0 || pszSrc.charAt(iSrc - 1) == ',') /*
* this
* is a
* repeated
* subclause
*/
&& Character.isDigit(pszSrc.charAt(iSrc))) {
int orig_iSrc = iSrc;
// skip over repeat count.
for (; Character.isDigit(pszSrc.charAt(iSrc)); iSrc++) {
}
String nRepeatString = pszSrc.substring(orig_iSrc, iSrc);
nRepeat = Integer.parseInt(nRepeatString);
String pszContents = extractSubstring(pszSrc.substring(iSrc));
String pszExpandedContents = expandFormat(pszContents);
for (int i = 0; i < nRepeat; i++) {
szDest.append(pszExpandedContents);
if (i < nRepeat - 1) {
szDest.append(",");
}
}
if (iSrc == '(') {
iSrc += pszContents.length() + 2;
} else {
iSrc += pszContents.length();
}
} else {
szDest.append(pszSrc.charAt(iSrc++));
}
}
return szDest.toString();
}
/**
* This method parses the format string partially, and then
* applies a subfield format string to each subfield object. It in
* turn does final parsing of the subfield formats.
*/
protected boolean applyFormats(String _formatControls) {
String pszFormatList;
Vector papszFormatItems;
/* -------------------------------------------------------------------- */
/* Verify that the format string is contained within brackets. */
/* -------------------------------------------------------------------- */
if (_formatControls.length() < 2 || !_formatControls.startsWith("(")
|| !_formatControls.endsWith(")")) {
Debug.error("DDFFieldDefinition: Format controls for " + pszTag
+ " field missing brackets {" + _formatControls
+ "} : length = " + _formatControls.length()
+ ", starts with {" + _formatControls.charAt(0)
+ "}, ends with {"
+ _formatControls.charAt(_formatControls.length() - 1)
+ "}");
return false;
}
/* -------------------------------------------------------------------- */
/* Duplicate the string, and strip off the brackets. */
/* -------------------------------------------------------------------- */
pszFormatList = expandFormat(_formatControls);
if (Debug.debugging("iso8211")) {
Debug.output("DDFFieldDefinition.applyFormats{" + _formatControls
+ "} expanded to {" + pszFormatList + "} ");
}
/* -------------------------------------------------------------------- */
/* Tokenize based on commas. */
/* -------------------------------------------------------------------- */
papszFormatItems = PropUtils.parseMarkers(pszFormatList, ",");
/* -------------------------------------------------------------------- */
/* Apply the format items to subfields. */
/* -------------------------------------------------------------------- */
int iFormatItem = 0;
for (Iterator it = papszFormatItems.iterator(); it.hasNext(); iFormatItem++) {
String pszPastPrefix = (String) it.next();
int pppIndex = 0;
// Skip over digits...
for (; Character.isDigit(pszPastPrefix.charAt(pppIndex)); pppIndex++) {
}
pszPastPrefix = pszPastPrefix.substring(pppIndex);
///////////////////////////////////////////////////////////////
// Did we get too many formats for the subfields created
// by names? This may be legal by the 8211 specification,
// but
// isn't encountered in any formats we care about so we
// just
// blow.
if (iFormatItem > paoSubfieldDefns.size()) {
Debug.error("DDFFieldDefinition: Got more formats than subfields for field "
+ pszTag);
break;
}
if (!((DDFSubfieldDefinition) paoSubfieldDefns.elementAt(iFormatItem)).setFormat(pszPastPrefix)) {
Debug.output("DDFFieldDefinition had problem setting format for "
+ pszPastPrefix);
return false;
}
}
/* -------------------------------------------------------------------- */
/* Verify that we got enough formats, cleanup and return. */
/* -------------------------------------------------------------------- */
if (iFormatItem < paoSubfieldDefns.size()) {
Debug.error("DDFFieldDefinition: Got fewer formats than subfields for field "
+ pszTag
+ " got ("
+ iFormatItem
+ ", should have "
+ paoSubfieldDefns.size() + ")");
return false;
}
/* -------------------------------------------------------------------- */
/* If all the fields are fixed width, then we are fixed width */
/* too. This is important for repeating fields. */
/* -------------------------------------------------------------------- */
nFixedWidth = 0;
for (int i = 0; i < paoSubfieldDefns.size(); i++) {
DDFSubfieldDefinition ddfsd = (DDFSubfieldDefinition) paoSubfieldDefns.elementAt(i);
if (ddfsd.getWidth() == 0) {
nFixedWidth = 0;
break;
} else {
nFixedWidth += ddfsd.getWidth();
}
}
return true;
}
/**
* Find a subfield definition by it's mnemonic tag.
*
* @param pszMnemonic The name of the field.
*
* @return The subfield pointer, or null if there isn't any such
* subfield.
*/
public DDFSubfieldDefinition findSubfieldDefn(String pszMnemonic) {
if (paoSubfieldDefns != null) {
for (Iterator it = paoSubfieldDefns.iterator(); pszMnemonic != null
&& it.hasNext();) {
DDFSubfieldDefinition ddfsd = (DDFSubfieldDefinition) it.next();
if (pszMnemonic.equalsIgnoreCase(ddfsd.getName())) {
return ddfsd;
}
}
}
return null;
}
/**
* Fetch a subfield by index.
*
* @param i The index subfield index. (Between 0 and
* GetSubfieldCount()-1)
* @return The subfield pointer, or null if the index is out of
* range.
*/
public DDFSubfieldDefinition getSubfieldDefn(int i) {
if (paoSubfieldDefns == null || i < 0 || i >= paoSubfieldDefns.size()) {
return null;
}
return (DDFSubfieldDefinition) paoSubfieldDefns.elementAt(i);
}
public static class DataStructCode {
public final static DataStructCode ELEMENTARY = new DataStructCode('0', "elementary");
public final static DataStructCode VECTOR = new DataStructCode('1', "vector");
public final static DataStructCode ARRAY = new DataStructCode('2', "array");
public final static DataStructCode CONCATENATED = new DataStructCode('3', "concatenated");
char code = '0';
String prettyName;
public DataStructCode(char structCode, String name) {
code = structCode;
prettyName = name;
}
public char getCode() {
return code;
}
public String toString() {
return prettyName;
}
public static DataStructCode get(char c) {
if (c == CONCATENATED.getCode())
return CONCATENATED;
if (c == VECTOR.getCode())
return VECTOR;
if (c == ARRAY.getCode())
return ARRAY;
if (c == ELEMENTARY.getCode())
return ELEMENTARY;
if (Debug.debugging("iso8211")) {
Debug.output("DDFFieldDefinition tested for unknown code: " + c);
}
return ELEMENTARY;
}
}
public static class DataTypeCode {
public final static DataTypeCode CHAR_STRING = new DataTypeCode('0', "character string");
public final static DataTypeCode IMPLICIT_POINT = new DataTypeCode('1', "implicit point");
public final static DataTypeCode EXPLICIT_POINT = new DataTypeCode('2', "explicit point");
public final static DataTypeCode EXPLICIT_POINT_SCALED = new DataTypeCode('3', "explicit point scaled");
public final static DataTypeCode CHAR_BIT_STRING = new DataTypeCode('4', "character bit string");
public final static DataTypeCode BIT_STRING = new DataTypeCode('5', "bit string");
public final static DataTypeCode MIXED_DATA_TYPE = new DataTypeCode('6', "mixed data type");
char code = '0';
String prettyName;
public DataTypeCode(char structCode, String desc) {
code = structCode;
prettyName = desc;
}
public char getCode() {
return code;
}
public String toString() {
return prettyName;
}
public static DataTypeCode get(char c) {
if (c == IMPLICIT_POINT.getCode())
return IMPLICIT_POINT;
if (c == EXPLICIT_POINT.getCode())
return EXPLICIT_POINT;
if (c == EXPLICIT_POINT_SCALED.getCode())
return EXPLICIT_POINT_SCALED;
if (c == CHAR_BIT_STRING.getCode())
return CHAR_BIT_STRING;
if (c == BIT_STRING.getCode())
return BIT_STRING;
if (c == MIXED_DATA_TYPE.getCode())
return MIXED_DATA_TYPE;
if (c == CHAR_STRING.getCode())
return CHAR_STRING;
if (Debug.debugging("iso8211")) {
Debug.output("DDFFieldDefinition tested for unknown data type code: "
+ c);
}
return CHAR_STRING;
}
}
}