/******************************************************************************
* 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 com.bbn.openmap.MoreMath;
import com.bbn.openmap.layer.vpf.MutableInt;
import com.bbn.openmap.util.Debug;
/**
* Information from the DDR record describing one subfield of a DDFFieldDefn.
* All subfields of a field will occur in each occurrence of that field (as a
* DDFField) in a DDFRecord. Subfield's actually contain formatted data (as
* instances within a record).
*
* @author Guillaume Pelletier provided fix for Big Endian support (important
* for S-57)
*/
public class DDFSubfieldDefinition implements DDFConstants {
protected String pszName; // a.k.a. subfield mnemonic
protected String pszFormatString;
protected DDFDataType eType;
protected int eBinaryFormat;
/**
* bIsVariable determines whether we using the chFormatDelimeter (true), or
* the fixed width (false).
*/
protected boolean bIsVariable;
protected char chFormatDelimeter;
protected int nFormatWidth;
public interface DDFBinaryFormat {
public final static int NotBinary = 0;
public final static int UInt = 1;
public final static int SInt = 2;
public final static int FPReal = 3;
public final static int FloatReal = 4;
public final static int FloatComplex = 5;
}
public int getWidth() {
return nFormatWidth;
}
/** Get pointer to subfield name. */
public String getName() {
return pszName;
}
/** Get pointer to subfield format string */
public String getFormat() {
return pszFormatString;
}
/**
* Get the general type of the subfield. This can be used to determine which
* of ExtractFloatData(), ExtractIntData() or ExtractStringData() should be
* used.
*
* @return The subfield type. One of DDFInt, DDFFloat, DDFString or
* DDFBinaryString.
*/
public DDFDataType getType() {
return eType;
}
public DDFSubfieldDefinition() {
pszName = null;
bIsVariable = true;
nFormatWidth = 0;
chFormatDelimeter = DDF_UNIT_TERMINATOR;
eBinaryFormat = DDFBinaryFormat.NotBinary;
eType = DDFDataType.DDFString;
pszFormatString = new String("");
}
/**
* Set the name of the subfield.
*/
public void setName(String pszNewName) {
pszName = pszNewName.trim();
}
/**
* While interpreting the format string we don't support:
* <UL>
* <LI>Passing an explicit terminator for variable length field.
* <LI>'X' for unused data ... this should really be filtered
* <LI>out by DDFFieldDefinition.applyFormats(), but isn't.
* <LI>'B' bitstrings that aren't a multiple of eight.
* </UL>
*/
public boolean setFormat(String pszFormat) {
pszFormatString = pszFormat;
if (Debug.debugging("iso8211")) {
Debug.output("DDFSubfieldDefinition.setFormat(" + pszFormat + ")");
}
/* -------------------------------------------------------------------- */
/* These values will likely be used. */
/* -------------------------------------------------------------------- */
if (pszFormatString.length() > 1 && pszFormatString.charAt(1) == '(') {
// Need to loop through characters to grab digits, and
// then get integer version. If we look a the atoi code,
// it checks for non-digit characters and then stops.
int i = 3;
for (; i < pszFormat.length()
&& Character.isDigit(pszFormat.charAt(i)); i++) {
}
nFormatWidth = Integer.parseInt(pszFormat.substring(2, i));
bIsVariable = (nFormatWidth == 0);
} else {
bIsVariable = true;
}
/* -------------------------------------------------------------------- */
/* Interpret the format string. */
/* -------------------------------------------------------------------- */
switch (pszFormatString.charAt(0)) {
case 'A':
case 'C': // It isn't clear to me how this is different than
// 'A'
eType = DDFDataType.DDFString;
break;
case 'R':
eType = DDFDataType.DDFFloat;
break;
case 'I':
case 'S':
eType = DDFDataType.DDFInt;
break;
case 'B':
case 'b':
// Is the width expressed in bits? (is it a bitstring)
bIsVariable = false;
if (pszFormatString.charAt(1) == '(') {
int numEndIndex = 2;
for (; numEndIndex < pszFormatString.length()
&& Character.isDigit(pszFormatString
.charAt(numEndIndex)); numEndIndex++) {
}
String numberString = pszFormatString.substring(2, numEndIndex);
nFormatWidth = Integer.valueOf(numberString).intValue();
if (nFormatWidth % 8 != 0) {
Debug
.error("DDFSubfieldDefinition.setFormat() problem with "
+ pszFormatString.charAt(0)
+ " not being modded with 8 evenly");
return false;
}
nFormatWidth = Integer.parseInt(numberString) / 8;
eBinaryFormat = DDFBinaryFormat.SInt; // good
// default,
// works for
// SDTS.
if (nFormatWidth < 5) {
eType = DDFDataType.DDFInt;
} else {
eType = DDFDataType.DDFBinaryString;
}
} else { // or do we have a binary type indicator? (is it binary)
eBinaryFormat = (int) (pszFormatString.charAt(1) - '0');
int numEndIndex = 2;
for (; numEndIndex < pszFormatString.length()
&& Character.isDigit(pszFormatString
.charAt(numEndIndex)); numEndIndex++) {
}
nFormatWidth = Integer.valueOf(
pszFormatString
.substring(2,
numEndIndex))
.intValue();
if (eBinaryFormat == DDFBinaryFormat.SInt
|| eBinaryFormat == DDFBinaryFormat.UInt) {
eType = DDFDataType.DDFInt;
} else {
eType = DDFDataType.DDFFloat;
}
}
break;
case 'X':
// 'X' is extra space, and shouldn't be directly assigned
// to a
// subfield ... I haven't encountered it in use yet
// though.
Debug.error("DDFSubfieldDefinition: Format type of "
+ pszFormatString.charAt(0) + " not supported.");
return false;
default:
Debug.error("DDFSubfieldDefinition: Format type of "
+ pszFormatString.charAt(0) + " not recognised.");
return false;
}
return true;
}
/**
* Write out subfield definition info. A variety of information about this
* field definition is written to the give debugging file handle.
*/
public String toString() {
StringBuffer buf = new StringBuffer(" DDFSubfieldDefn:\n");
buf.append(" Label = ").append(pszName).append("\n");
buf.append(" FormatString = ").append(pszFormatString).append("\n");
return buf.toString();
}
/**
* Scan for the end of variable length data. Given a pointer to the data for
* this subfield (from within a DDFRecord) this method will return the
* number of bytes which are data for this subfield. The number of bytes
* consumed as part of this field can also be fetched. This number may be
* one longer than the length if there is a terminator character used.
* <p>
*
* This method is mainly for internal use, or for applications which want
* the raw binary data to interpret themselves. Otherwise use one of
* ExtractStringData(), ExtractIntData() or ExtractFloatData().
*
* @param pachSourceData
* The pointer to the raw data for this field. This may have come
* from DDFRecord::GetData(), taking into account skip factors
* over previous subfields data.
* @param nMaxBytes
* The maximum number of bytes that are accessible after
* pachSourceData.
* @param pnConsumedBytes
* the number of bytes used.
*
* @return The number of bytes at pachSourceData which are actual data for
* this record (not including unit, or field terminator).
*/
public int getDataLength(byte[] pachSourceData, int nMaxBytes,
MutableInt pnConsumedBytes) {
if (!bIsVariable) {
if (nFormatWidth > nMaxBytes) {
Debug.error("DDFSubfieldDefinition: Only " + nMaxBytes
+ " bytes available for subfield " + pszName
+ " with format string " + pszFormatString
+ " ... returning shortened data.");
if (pnConsumedBytes != null) {
pnConsumedBytes.value = nMaxBytes;
}
return nMaxBytes;
} else {
if (pnConsumedBytes != null) {
pnConsumedBytes.value = nFormatWidth;
}
return nFormatWidth;
}
} else {
int nLength = 0;
boolean bCheckFieldTerminator = true;
/*
* We only check for the field terminator because of some buggy
* datasets with missing format terminators. However, we have found
* the field terminator is a legal character within the fields of
* some extended datasets (such as JP34NC94.000). So we don't check
* for the field terminator if the field appears to be multi-byte
* which we established by the first character being out of the
* ASCII printable range (32-127).
*/
if (pachSourceData[0] < 32 || pachSourceData[0] >= 127) {
bCheckFieldTerminator = false;
}
while (nLength < nMaxBytes
&& pachSourceData[nLength] != chFormatDelimeter) {
if (bCheckFieldTerminator
&& pachSourceData[nLength] == DDF_FIELD_TERMINATOR)
break;
nLength++;
}
if (pnConsumedBytes != null) {
if (nMaxBytes == 0) {
pnConsumedBytes.value = nLength;
} else {
pnConsumedBytes.value = nLength + 1;
}
}
return nLength;
}
}
/**
* Extract a zero terminated string containing the data for this subfield.
* Given a pointer to the data for this subfield (from within a DDFRecord)
* this method will return the data for this subfield. The number of bytes
* consumed as part of this field can also be fetched. This number may be
* one longer than the string length if there is a terminator character
* used.
* <p>
*
* This function will return the raw binary data of a subfield for types
* other than DDFString, including data past zero chars. This is the
* standard way of extracting DDFBinaryString subfields for instance.
* <p>
*
* @param pachSourceData
* The pointer to the raw data for this field. This may have come
* from DDFRecord::GetData(), taking into account skip factors
* over previous subfields data.
* @param nMaxBytes
* The maximum number of bytes that are accessible after
* pachSourceData.
* @param pnConsumedBytes
* Pointer to an integer into which the number of bytes consumed
* by this field should be written. May be null to ignore. This
* is used as a skip factor to increment pachSourceData to point
* to the next subfields data.
*
* @return A pointer to a buffer containing the data for this field. The
* returned pointer is to an internal buffer which is invalidated on
* the next ExtractStringData() call on this DDFSubfieldDefn(). It
* should not be freed by the application.
*/
String extractStringData(byte[] pachSourceData, int nMaxBytes,
MutableInt pnConsumedBytes) {
int oldConsumed = 0;
if (pnConsumedBytes != null) {
oldConsumed = pnConsumedBytes.value;
}
int nLength = getDataLength(pachSourceData, nMaxBytes, pnConsumedBytes);
String ns = new String(pachSourceData, 0, nLength);
if (Debug.debugging("iso8211detail") && pnConsumedBytes != null) {
Debug.output(" extracting string data from " + nLength
+ " bytes of " + pachSourceData.length + ": " + ns
+ ": consumed " + pnConsumedBytes.value + " vs. "
+ oldConsumed + ", max = " + nMaxBytes);
}
return ns;
}
/**
* Extract a subfield value as a float. Given a pointer to the data for this
* subfield (from within a DDFRecord) this method will return the floating
* point data for this subfield. The number of bytes consumed as part of
* this field can also be fetched. This method may be called for any type of
* subfield, and will return zero if the subfield is not numeric.
*
* @param pachSourceData
* The pointer to the raw data for this field. This may have come
* from DDFRecord::GetData(), taking into account skip factors
* over previous subfields data.
* @param nMaxBytes
* The maximum number of bytes that are accessible after
* pachSourceData.
* @param pnConsumedBytes
* Pointer to an integer into which the number of bytes consumed
* by this field should be written. May be null to ignore. This
* is used as a skip factor to increment pachSourceData to point
* to the next subfields data.
*
* @return The subfield's numeric value (or zero if it isn't numeric).
*/
public double extractFloatData(byte[] pachSourceData, int nMaxBytes,
MutableInt pnConsumedBytes) {
switch (pszFormatString.charAt(0)) {
case 'A':
case 'I':
case 'R':
case 'S':
case 'C':
String dataString = extractStringData(pachSourceData, nMaxBytes,
pnConsumedBytes);
if (dataString.length() == 0) {
return 0;
}
try {
return Double.parseDouble(dataString);
} catch (NumberFormatException nfe) {
if (Debug.debugging("iso8211")) {
Debug
.output("DDFSubfieldDefinition.extractFloatData: number format problem: "
+ dataString);
}
return 0;
}
case 'B':
case 'b':
byte[] abyData = new byte[8];
if (pnConsumedBytes != null) {
pnConsumedBytes.value = nFormatWidth;
}
if (nFormatWidth > nMaxBytes) {
Debug
.error("DDFSubfieldDefinition: format width is greater than max bytes for float");
return 0.0;
}
// Byte swap the data if it isn't in machine native
// format. In any event we copy it into our buffer to
// ensure it is word aligned.
//
// DFD - don't think this applies to Java, since it's
// always big endian
// if (pszFormatString.charAt(0) == 'B') ||
// (pszFormatString.charAt(0) == 'b') {
// for (int i = 0; i < nFormatWidth; i++) {
// abyData[nFormatWidth-i-1] = pachSourceData[i];
// }
// } else {
// System.arraycopy(pachSourceData, 0, abyData, 8-nFormatWidth,
// nFormatWidth);
System.arraycopy(pachSourceData, 0, abyData, 0, nFormatWidth);
// }
// Interpret the bytes of data.
switch (eBinaryFormat) {
case DDFBinaryFormat.UInt:
case DDFBinaryFormat.SInt:
case DDFBinaryFormat.FloatReal:
return (int) pszFormatString.charAt(0) == 'B' ? MoreMath
.BuildIntegerBE(abyData) : MoreMath
.BuildIntegerLE(abyData);
// if (nFormatWidth == 1)
// return(abyData[0]);
// else if (nFormatWidth == 2)
// return(*((GUInt16 *) abyData));
// else if (nFormatWidth == 4)
// return(*((GUInt32 *) abyData));
// else {
// return 0.0;
// }
// case DDFBinaryFormat.SInt:
// if (nFormatWidth == 1)
// return(*((signed char *) abyData));
// else if (nFormatWidth == 2)
// return(*((GInt16 *) abyData));
// else if (nFormatWidth == 4)
// return(*((GInt32 *) abyData));
// else {
// return 0.0;
// }
// case DDFBinaryFormat.FloatReal:
// if (nFormatWidth == 4)
// return(*((float *) abyData));
// else if (nFormatWidth == 8)
// return(*((double *) abyData));
// else {
// return 0.0;
// }
case DDFBinaryFormat.NotBinary:
case DDFBinaryFormat.FPReal:
case DDFBinaryFormat.FloatComplex:
return 0.0;
}
break;
// end of 'b'/'B' case.
default:
}
return 0.0;
}
/**
* Extract a subfield value as an integer. Given a pointer to the data for
* this subfield (from within a DDFRecord) this method will return the int
* data for this subfield. The number of bytes consumed as part of this
* field can also be fetched. This method may be called for any type of
* subfield, and will return zero if the subfield is not numeric.
*
* @param pachSourceData
* The pointer to the raw data for this field. This may have come
* from DDFRecord::GetData(), taking into account skip factors
* over previous subfields data.
* @param nMaxBytes
* The maximum number of bytes that are accessible after
* pachSourceData.
* @param pnConsumedBytes
* Pointer to an integer into which the number of bytes consumed
* by this field should be written. May be null to ignore. This
* is used as a skip factor to increment pachSourceData to point
* to the next subfields data.
*
* @return The subfield's numeric value (or zero if it isn't numeric).
*/
public int extractIntData(byte[] pachSourceData, int nMaxBytes,
MutableInt pnConsumedBytes) {
switch (pszFormatString.charAt(0)) {
case 'A':
case 'I':
case 'R':
case 'S':
case 'C':
String dataString = extractStringData(pachSourceData, nMaxBytes,
pnConsumedBytes);
if (dataString.length() == 0) {
return 0;
}
try {
return Double.valueOf(dataString).intValue();
} catch (NumberFormatException nfe) {
if (Debug.debugging("iso8211")) {
Debug
.output("DDFSubfieldDefinition.extractIntData: number format problem: "
+ dataString);
}
return 0;
}
case 'B':
case 'b':
byte[] abyData = new byte[4];
if (nFormatWidth > nMaxBytes) {
Debug
.error("DDFSubfieldDefinition: format width is greater than max bytes for int");
return 0;
}
if (pnConsumedBytes != null) {
pnConsumedBytes.value = nFormatWidth;
}
// System.arraycopy(pachSourceData, 0, abyData, 4-nFormatWidth,
// nFormatWidth);
System.arraycopy(pachSourceData, 0, abyData, 0, nFormatWidth);
// Interpret the bytes of data.
switch (eBinaryFormat) {
case DDFBinaryFormat.UInt:
case DDFBinaryFormat.SInt:
case DDFBinaryFormat.FloatReal:
return (int) pszFormatString.charAt(0) == 'B' ? MoreMath
.BuildIntegerBE(abyData) : MoreMath
.BuildIntegerLE(abyData);
// case DDFBinaryFormat.UInt:
// if (nFormatWidth == 4)
// return((int) *((GUInt32 *) abyData));
// else if (nFormatWidth == 1)
// return(abyData[0]);
// else if (nFormatWidth == 2)
// return(*((GUInt16 *) abyData));
// else {
// CPLAssert(false);
// return 0;
// }
// case DDFBinaryFormat.SInt:
// if (nFormatWidth == 4)
// return(*((GInt32 *) abyData));
// else if (nFormatWidth == 1)
// return(*((signed char *) abyData));
// else if (nFormatWidth == 2)
// return(*((GInt16 *) abyData));
// else {
// CPLAssert(false);
// return 0;
// }
// case DDFBinaryFormat.FloatReal:
// if (nFormatWidth == 4)
// return((int) *((float *) abyData));
// else if (nFormatWidth == 8)
// return((int) *((double *) abyData));
// else {
// CPLAssert(false);
// return 0;
// }
case DDFBinaryFormat.NotBinary:
case DDFBinaryFormat.FPReal:
case DDFBinaryFormat.FloatComplex:
return 0;
}
break;
// end of 'b'/'B' case.
default:
return 0;
}
return 0;
}
/**
* Dump subfield value to debugging file.
*
* @param pachData
* Pointer to data for this subfield.
* @param nMaxBytes
* Maximum number of bytes available in pachData.
*/
public String dumpData(byte[] pachData, int nMaxBytes) {
StringBuffer sb = new StringBuffer();
if (eType == DDFDataType.DDFFloat) {
sb.append(" Subfield ").append(pszName).append("=")
.append(extractFloatData(pachData, nMaxBytes, null)).append("\n");
} else if (eType == DDFDataType.DDFInt) {
sb.append(" Subfield ").append(pszName).append("=")
.append(extractIntData(pachData, nMaxBytes, null)).append("\n");
} else if (eType == DDFDataType.DDFBinaryString) {
sb.append(" Subfield ").append(pszName).append("=")
.append(extractStringData(pachData, nMaxBytes, null)).append("\n");
} else {
sb.append(" Subfield ").append(pszName).append("=")
.append(extractStringData(pachData, nMaxBytes, null)).append("\n");
}
return sb.toString();
}
}