blob: 68ea8502a688693d3a66fe02b3236d1102ab852f [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
package org.eclipse.mdm.openatfx.mdf.mdf3;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asam.ods.AoException;
import org.asam.ods.ApplicationElement;
import org.asam.ods.ApplicationRelation;
import org.asam.ods.ApplicationStructure;
import org.asam.ods.DataType;
import org.asam.ods.ErrorCode;
import org.asam.ods.InstanceElement;
import org.asam.ods.InstanceElementIterator;
import org.asam.ods.SeverityFlag;
import org.asam.ods.T_ExternalReference;
import org.asam.ods.T_LONGLONG;
import org.eclipse.mdm.openatfx.mdf.ConvertException;
import org.eclipse.mdm.openatfx.mdf.mdf4.MDF4Util;
import org.eclipse.mdm.openatfx.mdf.util.BitInputStream;
import org.eclipse.mdm.openatfx.mdf.util.FileUtil;
import org.eclipse.mdm.openatfx.mdf.util.LookupTableHelper;
import org.eclipse.mdm.openatfx.mdf.util.ODSHelper;
import org.eclipse.mdm.openatfx.mdf.util.ODSInsertStatement;
import org.eclipse.mdm.openatfx.mdf.util.ODSModelCache;
/**
* Main class for writing the MDF3 file content into an ATFX file
*
* @author Christian Rechner
*/
public class AoSessionWriter {
private static final Log LOG = LogFactory.getLog(AoSessionWriter.class);
private static final String MDF_DATEFORMAT = "dd:MM:yyyy HH:mm:ss";
/** The number format having 5 digits used for count formatting */
private final NumberFormat countFormat;
/** The date format in the MDF file */
private final DateFormat mdfDateFormat;
/** helper class for lookup tables */
private final LookupTableHelper lookupTableHelper;
// Properties
// replace channel name characters '[' with '{' and ']' with '}'
private boolean replaceSquareBrackets; // default = false
// whether to use the file name as AoMeasurement name, if false, property
// value of 'result_name' is used
private boolean useFileNameAsResultName = true; // default = true
// the name of the created AoMeasurement (only used if
// use_file_name_as_result_name=false)
private String resultName = "Messdaten"; // default = "Messdaten"
// the name suffix of the created AoMeasurement, e.g. if suffix='_123' then
// 'test.mf4' will be converted to 'test_123.mf4'
private String resultSuffix = ""; // default = null
// the mimetype of the created AoMeasurement
private String resultMimeType = "application/x-asam.aomeasurement.timeseries"; // default
// =
// "application/x-asam.aomeasurement.timeseries"
// whether to add a reference to the original file to the AoMeasurement
// instance
private boolean addMDF3FileAsResultAttachment; // default = false
// stop parsing after hd block.
private boolean readOnlyHeader = false;
// skip empty channels
private boolean skipEmptyChannels = false;
// skip channels with an unsupporter donversion formula
private boolean skipUnsupportedFormula = false;
// skip channels with unsigned 64 bit data
private boolean skipUINT64Channels = false;
// skip channels with DT_BYTESTR
// remove this once it is save to import channels with byte stream data
@Deprecated
private boolean skipByteStreamChannels = false;
private Path customRatConfPath;
/**
* Constructor.
*/
public AoSessionWriter() {
mdfDateFormat = new SimpleDateFormat(MDF_DATEFORMAT);
countFormat = new DecimalFormat("00000");
lookupTableHelper = new LookupTableHelper();
}
/**
* Appends the content of the MDF4 file to the ASAM ODS session.
*
* @param modelCache
* The application model cache.
* @param idBlock
* The IDBLOCK.
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
public void writeTst(ODSModelCache modelCache, IDBLOCK idBlock, Properties props) throws AoException, IOException {
ApplicationElement aeEnv = modelCache.getApplicationElement("env");
InstanceElement ieEnv = aeEnv.getInstanceById(new T_LONGLONG(0, 1));
Path fileName = idBlock.getMdfFilePath().getFileName();
if (fileName == null) {
throw new IOException("Unable to obtain file name!");
}
if (props != null) {
// replace channel name characters '[' with '{' and ']' with '}'
if (props.containsKey("replace_square_brackets")) {
replaceSquareBrackets = Boolean.valueOf(props.getProperty("replace_square_brackets")); // default
// =
// false
}
// whether to use the file name as AoMeasurement name, if false,
// property value of 'result_name' is used
if (props.containsKey("use_file_name_as_result_name")) {
useFileNameAsResultName = Boolean.valueOf(props.getProperty("use_file_name_as_result_name")); // default
// =
// true
}
// the name of the created AoMeasurement (only used if
// use_file_name_as_result_name=false)
if (props.containsKey("result_name")) {
resultName = props.getProperty("result_name"); // default =
// "Messdaten"
}
// the name suffix of the created AoMeasurement, e.g. if
// suffix='_123' then 'test.mf4' will be converted to 'test_123.mf4'
if (props.containsKey("result_suffix")) {
resultSuffix = props.getProperty("result_suffix"); // default =
// null
}
// the mimetype of the created AoMeasurement
if (props.containsKey("result_mimetype")) {
resultMimeType = props.getProperty("result_mimetype"); // default
// =
// "application/x-asam.aomeasurement.timeseries"
}
// whether to add a reference to the original file to the
// AoMeasurement instance
if (props.containsKey("attach_source_files")) {
addMDF3FileAsResultAttachment = Boolean.valueOf(props.getProperty("attach_source_files")); // default
// =
// false
}
if (props.containsKey("read_only_header")) {
readOnlyHeader = Boolean.valueOf(props.getProperty("read_only_header"));
}
if (props.containsKey("skip_empty_channels")) {
skipEmptyChannels = Boolean.valueOf(props.getProperty("skip_empty_channels"));
}
if (props.containsKey("skip_unsupported_formula")) {
skipUnsupportedFormula = Boolean.valueOf(props.getProperty("skip_unsupported_formula"));
}
if (props.containsKey("skip_uint64_data_channels")) {
skipUINT64Channels = Boolean.valueOf(props.getProperty("skip_uint64_data_channels"));
}
if (props.containsKey("skip_byte_stream_channels")) {
skipByteStreamChannels = Boolean.valueOf(props.getProperty("skip_byte_stream_channels"));
}
}
// read and validate IDBLOCK
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "tst");
ins.setStringVal("iname", FileUtil.stripExtension(fileName.toString()));
ins.setStringVal("mdf_file_id", idBlock.getIdFile());
ins.setStringVal("mdf_version_str", idBlock.getIdVers());
ins.setLongVal("mdf_version", idBlock.getIdVer());
ins.setStringVal("mdf_program", idBlock.getIdProg());
ins.setLongVal("mdf_unfin_flags", idBlock.getIdUnfinFlags());
ins.setLongVal("mdf_custom_unfin_flags", idBlock.getIdCustomUnfinFlags());
ins.setLongLongVal("env", ieEnv.getId());
long iidTst = ins.execute();
// write properties
if (props != null) {
MDF4Util.writeProperites(ins, props);
}
// write 'AoMeasurement' instance
writeMea(modelCache, iidTst, idBlock);
}
/**
* Write the instance of 'AoMeasurement'.
*
* @param modelCache
* The application model cache.
* @param iidTst
* The instance if of the parent 'AoTest' instance.
* @param idBlock
* The IDBLOCK.
* @return the instance id of the created 'AoMeasurement' instance.
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
private long writeMea(ODSModelCache modelCache, long iidTst, IDBLOCK idBlock) throws AoException, IOException {
Path fileName = idBlock.getMdfFilePath().getFileName();
if (fileName == null) {
throw new IOException("Unable to obtain file name!");
}
// create "AoMeasurement" instance and write descriptive data to
// instance attributes
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "mea");
if (useFileNameAsResultName) {
ins.setStringVal("iname", FileUtil.getResultName(fileName.toString(), resultSuffix));
} else {
ins.setStringVal("iname", resultName + resultSuffix);
}
ins.setStringVal("mt", resultMimeType);
if (addMDF3FileAsResultAttachment) {
ins.setExtRefVal("attachment", new T_ExternalReference("original MDF-File", "", fileName.toString()));
}
ins.setLongLongVal("tst", iidTst);
// write header attributes
HDBLOCK hdBlock = idBlock.getHDBlock();
TXBLOCK fileComment = hdBlock.getFileCommentTxt();
if (fileComment != null) {
ins.setStringVal("desc", fileComment.getText().trim());
}
// default date/time handling
Date date = null;
if (hdBlock.getDateStarted().length() > 0 && hdBlock.getTimeStarted().length() > 0) {
try {
date = mdfDateFormat.parse(hdBlock.getDateStarted() + " " + hdBlock.getTimeStarted());
} catch (ParseException e) {
LOG.warn(e.getMessage(), e);
}
} else {
throw new IOException("No date information found in MDF file!");
}
ins.setDateVal("date_created", ODSHelper.asODSDate(date));
ins.setDateVal("mea_begin", ODSHelper.asODSDate(date));
// special date/time handling
handleCLExportDate(ins, hdBlock, fileComment);
ins.setStringVal("author", hdBlock.getAuthor().trim());
ins.setStringVal("organization", hdBlock.getDepartment().trim());
ins.setStringVal("project", hdBlock.getProjectName().trim());
ins.setStringVal("meaObject", hdBlock.getMeaObject().trim());
long meaIid = ins.execute();
if (!readOnlyHeader) {
// remember channel names to avoid duplicates
// (key=channelName,value=number of)
Map<String, Integer> meqNames = new HashMap<String, Integer>();
// write 'AoSubMatrix' instances
writeSm(modelCache, meaIid, idBlock, hdBlock, meqNames);
}
return meaIid;
}
/**
* Write the instances of 'AoSubMatrix'.
*
* @param modelCache
* The application model cache.
* @param iidMea
* The instance id of the parent 'AoMeasurement' instance.
* @param hdBlock
* The HDBLOCK.
* @param meqNames
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
private void writeSm(ODSModelCache modelCache, long iidMea, IDBLOCK idBlock, HDBLOCK hdBlock,
Map<String, Integer> meqNames) throws AoException, IOException {
// iterate over data group blocks
int grpNo = 1;
DGBLOCK dgBlock = hdBlock.getFirstFileGroup();
Map<String, Long> meqInstances = new HashMap<String, Long>();
while (dgBlock != null) {
// ONLY SORTED MDF files can be converted - check this!
if (dgBlock.getNoChannelGroups() > 1) {
throw new IOException(
"Currently only 'sorted' MDF3 files are supported, found 'unsorted' data! [DGBLOCK=" + dgBlock
+ "]");
}
// if sorted, only one channel group block is available
CGBLOCK cgBlock = dgBlock.getNextCgBlock();
// skip channel groups having no channels (or optionally no values)
boolean skipNoValues = skipEmptyChannels && cgBlock.getNoOfRecords() < 1;
if (cgBlock != null && cgBlock.getNoOfChannels() > 0 && !skipNoValues) {
// create SubMatrix instance
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "sm");
ins.setStringVal("iname", "sm_" + countFormat.format(grpNo));
ins.setLongLongVal("mea", iidMea);
ins.setLongVal("rows", (int) cgBlock.getNoOfRecords());
// TODO: parse name:
// DATA_SysOpmHvES.SysOpmHvES_wElMinDrv_C_VW\ETKC:1\SingleShotGroup
TXBLOCK channelGroupComment = cgBlock.getChannelGroupComment();
if (channelGroupComment != null) {
ins.setStringVal("desc", channelGroupComment.getText());
}
long iidSm = ins.execute();
// write LocalColumns
writeLc(modelCache, iidMea, iidSm, idBlock, dgBlock, cgBlock, meqNames, meqInstances);
}
dgBlock = dgBlock.getNextDgBlock();
grpNo++;
}
}
/**
* Write the instances of 'AoLocalColumn'.
*
* @param modelCache
* The application model cache.
* @param iidMea
* The instance id of 'AoMeasurement'.
* @param iidSm
* The instance idof 'AoSubMatrix'.
* @param dgBlock
* The MDF data group block.
* @param cgBlock
* The MDF channel group block.
* @param meqNames
* @param meqTime
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
private void writeLc(ODSModelCache modelCache, long iidMea, long iidSm, IDBLOCK idBlock, DGBLOCK dgBlock,
CGBLOCK cgBlock, Map<String, Integer> meqNames, Map<String, Long> meqInstances)
throws AoException, IOException {
ApplicationElement aeMeq = modelCache.getApplicationElement("meq");
ApplicationElement aeLc = modelCache.getApplicationElement("lc");
ApplicationElement aeMea = modelCache.getApplicationElement("mea");
ApplicationElement aeSm = modelCache.getApplicationElement("sm");
ApplicationRelation relLcSm = modelCache.getApplicationRelation("lc", "sm", "LookupTable");
// iterate over channel blocks
CNBLOCK cnBlock = cgBlock.getFirstCnBlock();
while (cnBlock != null) {
// build signal name - parse the device info
String meqName = readMeqName(cnBlock);
String device = null;
String[] str = meqName.split("\\\\");
if (str.length > 0) {
meqName = str[0].trim();
}
if (str.length > 1) {
device = str[1].trim();
}
// replace channel names (concerto invalid characters)
if (replaceSquareBrackets) {
meqName = meqName.replaceAll("\\[", "{");
meqName = meqName.replaceAll("\\]", "}");
}
// check for duplicate signal names and add suffix (except time
// channel)
if (cnBlock.getChannelType() == 0) {
Integer noChannels = meqNames.get(meqName);
if (noChannels == null) {
noChannels = 0;
}
noChannels++;
meqNames.put(meqName, noChannels);
if (noChannels > 1) {
meqName = meqName + "_" + noChannels;
}
}
// sequence representation
CCBLOCK ccBlock = cnBlock.getCcBlock();
if (ccBlock != null && ccBlock.getFormulaIdent() == 10 && skipUnsupportedFormula) {
LOG.info("Channel '" + meqName + "' with unsupported formula (10 MCD2 Text Formular) skipped: "
+ ccBlock);
// jump to next channel
cnBlock = cnBlock.getNextCnBlock();
continue;
} else if (skipByteStreamChannels && 8 == cnBlock.getSignalDataType()) {
// remove this block once it is save to import channels with
// byte stream data
LOG.info("Channel '" + meqName + "' with byte stream data skipped: " + ccBlock);
cnBlock = cnBlock.getNextCnBlock();
continue;
} else if (8 == cnBlock.getSignalDataType() && cnBlock.getLnkCdBlock() != 0) {
LOG.info("Channel '" + meqName + "' with composed byte stream data skipped: " + ccBlock);
cnBlock = cnBlock.getNextCnBlock();
continue;
} else if (64 == cnBlock.getNumberOfBits() && (0 /* LEO */ == cnBlock.getSignalDataType()
|| 9 /* BEO */ == cnBlock.getSignalDataType())) {
// UNSIGNED 64 bit: if cnBlock represents a time channel (channel_type == 1)
// => it is considered save to interpret data as signed 64 bit
// => otherwise either fail or skip
if (1 != cnBlock.getChannelType()) {
// this is a data channel with 64 bit unsigned data; it is not possible to represent such data
// => either throw an error or skip channel
if (skipUINT64Channels) {
LOG.info("Channel '" + meqName + "' with unsigned 64 bit data skipped: " + cnBlock);
cnBlock = cnBlock.getNextCnBlock();
continue;
} else {
throw new IOException("unable to write unsigned 64 bit data channel");
}
}
}
int seqRep = getSeqRep(ccBlock, meqName);
if (cnBlock.getNumberOfBits() == 1) { // bit will be stored as bytes
seqRep = 7;
}
// create 'AoMeasurementQuantity' instance if not yet existing
Long iidMeq = meqInstances.get(meqName);
double[] genParams = getGenerationParameters(ccBlock);
if (iidMeq == null) {
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "meq");
ins.setStringVal("iname", meqName);
ins.setStringVal("desc", cnBlock.getSignalDescription().trim());
boolean expandDataType = genParams != null && genParams.length > 0 && seqRep != 0 && seqRep != 7;
DataType dt = DataType.from_int(getDataType(expandDataType, cnBlock, ccBlock));
ins.setEnumVal("dt", seqRep == 1 || isRatConv2ExtComp(ccBlock) ? DataType.DT_DOUBLE.value() : dt.value());
if (ccBlock != null && ccBlock.isKnownPhysValue()) {
ins.setDoubleVal("min", ccBlock.getMinPhysValue());
ins.setDoubleVal("max", ccBlock.getMaxPhysValue());
}
if (device != null && device.length() > 0) {
ins.setStringVal("src_path", device);
}
// CEBLOCK (extension block) info
CEBLOCK ceBlock = cnBlock.getCeblock();
if (ceBlock != null && ceBlock.getCeBlockDim() != null) {
CEBLOCK_DIM ext = ceBlock.getCeBlockDim();
ins.setLongVal("NumberOfModule", ext.getNumberOfModule());
ins.setLongLongVal("Address", ext.getAddress());
ins.setStringVal("DIMDescription", ext.getDescription());
ins.setStringVal("ECUIdent", ext.getEcuIdent());
} else if (ceBlock != null && ceBlock.getCeBlockVectorCAN() != null) {
CEBLOCK_VectorCAN ext = ceBlock.getCeBlockVectorCAN();
ins.setLongLongVal("CANIndex", ext.getCanIndex());
ins.setLongLongVal("MessageId", ext.getMessageId());
ins.setStringVal("MessageName", ext.getMessageName());
ins.setStringVal("SenderName", ext.getSenderName());
}
ins.setLongLongVal("mea", iidMea);
iidMeq = ins.execute();
meqInstances.put(meqName, iidMeq);
}
// create 'AoLocalColumn' instance
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "lc");
ins.setStringVal("iname", meqName);
ins.setLongLongVal("sm", iidSm);
// sequence_representation: string channel cannot be referenced in
// ASAM ODS: read and write external
if (cnBlock.getSignalDataType() == 7) {
String[] stringDataValues = readStringDataValues(dgBlock, cgBlock, cnBlock);
ins.setEnumVal("srp", 0);
ins.setNameValueUnit(ODSHelper.createStringSeqNVU("val", stringDataValues));
LOG.info("Unable to reference into MDF3, extracting string values. [Channel=" + meqName + "]");
} else {
ins.setEnumVal("srp", seqRep);
}
// independent flag
short idp = cnBlock.getChannelType() > 0 ? (short) 1 : (short) 0;
ins.setShortVal("idp", idp);
// global flag
ins.setShortVal("glb", (short) 15);
// generation parameters or values (for implicit_constant ONLY)
if (seqRep == 1) {
// implicit constant
ins.setDoubleSeq("val", new double[] { genParams[0] });
ins.setEnumVal("rdt", DataType.DT_DOUBLE.value());
} else {
// any other sequence representation
ins.setDoubleSeq("par", genParams);
// raw_datatype
int valueType = getValueType(cnBlock);
int rawDataType = getRawDataTypeForValueType(valueType, cnBlock);
ins.setEnumVal("rdt", isRatConv2ExtComp(ccBlock) ? DataType.DT_DOUBLE.value() : rawDataType);
}
// axistype
int axistype = cnBlock.getChannelType() == 0 ? 1 : 0;
ins.setEnumVal("axistype", axistype);
// minimum/maximum
if (cnBlock.isKnownImplValue()) {
ins.setDoubleVal("min", cnBlock.getMinImplValue());
ins.setDoubleVal("max", cnBlock.getMaxImplValue());
}
// relation to submatrix
ins.setLongLongVal("sm", iidSm);
ins.setLongLongVal("meq", iidMeq);
long iidLc = ins.execute();
// create 'AoExternalComponent' instance
if (cnBlock.getSignalDataType() != 7
&& seqRep != 1 /* skip implicit_constant */) {
writeEc(modelCache, iidLc, idBlock, dgBlock, cgBlock, cnBlock, ccBlock);
}
// create 'AoUnit' instance if not yet existing
InstanceElement ieMeq = aeMeq.getInstanceById(ODSHelper.asODSLongLong(iidMeq));
writeUnit(ieMeq, ccBlock);
// special handling for formula 11 'ASAM-MCD2 Text Table,
// (COMPU_VTAB)': create lookup table
InstanceElement ieMea = aeMea.getInstanceById(ODSHelper.asODSLongLong(iidMea));
InstanceElement ieLc = aeLc.getInstanceById(ODSHelper.asODSLongLong(iidLc));
if (ccBlock != null && ccBlock.getFormulaIdent() == 11) {
double[] keys = ccBlock.getKeysForTextTable();
String[] values = ccBlock.getValuesForTextTable();
// this.lookupTableHelper.createMCD2TextTableMeasurement(modelCache,
// ieMea, ieLc, keys, values);
long iidLookup = lookupTableHelper.createValueToTextTable(modelCache, ieMea, ieLc, keys, values, null);
InstanceElement ieSmLookup = aeSm.getInstanceById(ODSHelper.asODSLongLong(iidLookup));
ieLc.createRelation(relLcSm, ieSmLookup);
}
// special handling for formula 12 'ASAM-MCD2 Text Range Table
// (COMPU_VTAB_RANGE)': create lookup table
else if (ccBlock != null && ccBlock.getFormulaIdent() == 12) {
double[] keysMin = ccBlock.getLowerRangeKeysForTextRangeTable();
double[] keysMax = ccBlock.getUpperRangeKeysForTextRangeTable();
String[] values = ccBlock.getValuesForTextRangeTable();
String defaultValue = ccBlock.getDefaultTextForTextRangeTable();
// this.lookupTableHelper.createMCD2TextRangeTableMeasurement(modelCache,
// ieMea, ieLc, keysMin, keysMax,
// values, defaultValue);
long iidLookup = lookupTableHelper.createValueRangeToTextTable(modelCache, ieMea, ieLc, keysMin,
keysMax, values, defaultValue);
InstanceElement ieSmLookup = aeSm.getInstanceById(ODSHelper.asODSLongLong(iidLookup));
ieLc.createRelation(relLcSm, ieSmLookup);
}
// jump to next channel
cnBlock = cnBlock.getNextCnBlock();
}
}
private static String readMeqName(CNBLOCK cnBlock) throws IOException {
String meqName = cnBlock.getSignalName();
TXBLOCK signalDisplayIdentifier = cnBlock.getSignalDisplayIdentifier();
TXBLOCK mcdUniqueName = cnBlock.getMcdUniqueName();
if (signalDisplayIdentifier != null) {
String signalDisplayIdentifierTxt = signalDisplayIdentifier.getText().trim();
if (signalDisplayIdentifierTxt.length() > 0) {
meqName = signalDisplayIdentifierTxt;
}
} else if (mcdUniqueName != null) {
String mcdUniqueNameTxt = mcdUniqueName.getText().trim();
if (mcdUniqueNameTxt.length() > 0) {
meqName = mcdUniqueNameTxt;
}
}
return meqName;
}
private void writeUnit(InstanceElement ieMeq, CCBLOCK ccBlock) throws AoException {
if (ieMeq == null) {
return;
}
ApplicationElement aeMeq = ieMeq.getApplicationElement();
ApplicationStructure as = aeMeq.getApplicationStructure();
ApplicationElement aeUnt = as.getElementByName("unt");
ApplicationRelation relMeqUnt = as.getRelations(aeMeq, aeUnt)[0];
// create 'AoUnit' instance if not yet existing
String unitName = "";
if (ccBlock != null) {
unitName = ccBlock.getPhysUnit().trim();
}
if (ieMeq != null && unitName.length() > 0) {
InstanceElementIterator iter = aeUnt.getInstances(unitName);
InstanceElement ieUnit = null;
if (iter.getCount() > 0) {
ieUnit = iter.nextOne();
} else {
ieUnit = aeUnt.createInstance(unitName);
ieUnit.setValue(ODSHelper.createDoubleNVU("factor", 1d));
ieUnit.setValue(ODSHelper.createDoubleNVU("offset", 0d));
}
iter.destroy();
ieMeq.createRelation(relMeqUnt, ieUnit);
}
}
/**
* Write the instances of 'AoExternalComponent'.
*
* @param modelCache
* The application model cache.
* @param iidLc
* The instance id of the 'AoLocalColumn' instance.
* @param dgBlock
* The MDF data group block.
* @param cgBlock
* The MDF channel group block.
* @param cnBlock
* The MDF channel block.
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
private void writeEc(ODSModelCache modelCache, long iidLc, IDBLOCK idBlock, DGBLOCK dgBlock, CGBLOCK cgBlock,
CNBLOCK cnBlock, CCBLOCK ccBlock) throws AoException, IOException {
if (isRatConv2ExtComp(ccBlock)) {
// NOTE: once CCBLOCK is no longer required, it should be removed
// from this method's signature!
createCustomRatConvEC(modelCache, iidLc, idBlock, dgBlock, cgBlock, cnBlock, ccBlock);
} else {
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "ec");
ins.setStringVal("iname", "ec_" + countFormat.format(1));
Path mdfFilePath = idBlock.getMdfFilePath().getFileName();
if (mdfFilePath == null) {
throw new IOException("mdfFilePath must not be null");
}
ins.setStringVal("fl", mdfFilePath.toString());
ins.setEnumVal("vt", getValueType(cnBlock));
ins.setLongLongVal("so", dgBlock.getLnkDataRecords());
ins.setLongVal("cl", (int) cgBlock.getNoOfRecords());
ins.setLongVal("vb", 1);
int recordIdOffset = dgBlock.getNoRecordIds() > 0 ? 1 : 0;
ins.setLongVal("bs", cgBlock.getDataRecordSize() + recordIdOffset);
int valOffset = recordIdOffset + cnBlock.getByteOffset() + cnBlock.getNumberOfFirstBits() / 8;
ins.setLongVal("vo", valOffset);
short bitOffset = (short) (cnBlock.getNumberOfFirstBits() % 8);
if (bitOffset != 0 || cnBlock.getNumberOfBits() % 8 != 0 || cnBlock.getNumberOfBits() == 24) {
ins.setShortVal("bo", bitOffset);
ins.setShortVal("bc", (short) cnBlock.getNumberOfBits());
}
ins.setLongLongVal("lc", iidLc);
ins.execute();
}
}
/**
* Returns the target ASAM ODS external component type specification enum
* value for a MDF3 channel description.<br/>
* The data type is determined by the signal data type and the number of
* bits.<br/>
*
* @param cnBlock
* The MDF3 CNBLOCK.
* @return The ASAM ODS type specification enumeration value.
* @throws IOException
* Unsupported MDF3 data type.
*/
private static int getValueType(CNBLOCK cnBlock) throws IOException {
int dt = cnBlock.getSignalDataType();
int nb = cnBlock.getNumberOfBits();
int bitOffset = cnBlock.getNumberOfFirstBits() % 8;
// 0 = unsigned integer
if (dt == 0) {
if (nb == 8 && bitOffset == 0) { // 8 bit: dt_byte
return 1;
} else if (nb == 16 && bitOffset == 0) { // 16 bit: dt_ushort
return 21;
} else if (nb == 32 && bitOffset == 0) { // 32 bit: dt_ulong
return 23;
} else if (nb == 64 && bitOffset == 0) { // 64 bit: dt_longlong
return 4;
} else { // variable bit length: dt_bit_uint
return 29;
}
}
// 1 = signed integer
else if (dt == 1) {
if (nb == 8 && bitOffset == 0) { // 8 bit: dt_sbyte
return 19;
} else if (nb == 16 && bitOffset == 0) { // 16 bit: dt_short
return 2;
} else if (nb == 32 && bitOffset == 0) { // 32 bit: dt_long
return 3;
} else if (nb == 64 && bitOffset == 0) { // 64 bit: dt_longlong
return 4;
} else { // variable bit length: dt_bit_int
return 27;
}
}
// 2,3 = IEEE 754 floating-point format
else if (dt == 2 || dt == 3) {
if (nb == 32 && bitOffset == 0) { // 32 bit: ieeefloat4
return 5;
} else if (nb == 64 && bitOffset == 0) { // 64 bit: ieeefloat8
return 6;
} else { // variable bit length: dt_bit_float
return 31;
}
}
// 7 = String (NULL terminated): dt_string
else if (dt == 7) {
return 12;
}
// 8 = Byte Array: dt_bytestr
else if (dt == 8) {
return 13;
}
// 9 = unsigned integer BEO
else if (dt == 9) {
if (nb == 8 && bitOffset == 0) { // 8 bit: dt_byte
return 1;
} else if (nb == 16 && bitOffset == 0) { // 16 bit: dt_ushort_beo
return 22;
} else if (nb == 32 && bitOffset == 0) { // 32 bit: dt_ulong_beo
return 24;
} else if (nb == 64 && bitOffset == 0) { // 32 bit: dt_longlong_beo
return 9;
} else { // variable bit length: dt_bit_uint_beo
return 30;
}
}
// 10 = signed integer BEO
else if (dt == 10) {
if (nb == 8 && bitOffset == 0) { // 8 bit: dt_byte
return 1;
} else if (nb == 16 && bitOffset == 0) { // 16 bit: dt_short_beo
return 7;
} else if (nb == 32 && bitOffset == 0) { // 32 bit: dt_long_beo
return 8;
} else if (nb == 64 && bitOffset == 0) { // 64 bit: dt_longlong_beo
return 9;
} else { // variable bit length: dt_bit_int_beo
return 28;
}
}
// 11,12 = IEEE 754 floating-point format BEO
else if (dt == 11 || dt == 12) {
if (nb == 32 && bitOffset == 0) { // 32 bit: ieeefloat4_beo
return 10;
} else if (nb == 64 && bitOffset == 0) { // 64 bit: ieeefloat8_beo
return 11;
} else { // variable bit length: dt_bit_float_beo
return 32;
}
}
throw new IOException("Unsupported channel block: " + cnBlock);
}
/**
* Returns the raw dataType for given type specification.
*
* @param typeSpec
* The TypeSpec.
* @param cnBlock
* The CNBLOCK.
* @return The raw dataType.
* @throws AoException
* unable to obtain raw datatype
*/
private int getRawDataTypeForValueType(int typeSpec, CNBLOCK cnBlock) throws AoException {
int ret = 0;
if (typeSpec == 0) { // dt_boolean
ret = 4; // DT_BOOLEAN
} else if (typeSpec == 1) { // dt_byte
ret = 5; // DT_BYTE
} else if (typeSpec == 2) { // dt_short
ret = 2; // DT_SHORT
} else if (typeSpec == 3) { // dt_long
ret = 6; // DT_LONG
} else if (typeSpec == 4) { // dt_longlong
ret = 8; // DT_LONGLONG
} else if (typeSpec == 5) { // ieeefloat4
ret = 3; // DT_FLOAT
} else if (typeSpec == 6) { // ieeefloat8
ret = 7; // DT_DOUBLE
} else if (typeSpec == 7) { // dt_short_beo
ret = 2; // DT_SHORT
} else if (typeSpec == 8) { // dt_long_beo
ret = 6; // DT_LONG
} else if (typeSpec == 9) { // dt_longlong_beo
ret = 8; // DT_LONGLONG
} else if (typeSpec == 10) { // ieeefloat4_beo
ret = 3; // DT_FLOAT
} else if (typeSpec == 11) { // ieeefloat8_beo
ret = 7; // DT_DOUBLE
} else if (typeSpec == 12) { // dt_string
ret = 1; // DT_STRING
} else if (typeSpec == 13) { // dt_bytestr
ret = 11; // DT_BYTESTR
} else if (typeSpec == 14) { // dt_blob
ret = 12; // DT_BLOB
} else if (typeSpec == 15) { // dt_boolean_flags_beo
ret = 4; // DT_BOOLEAN
} else if (typeSpec == 16) { // dt_byte_flags_beo
ret = 5; // DT_BYTE
} else if (typeSpec == 17) { // dt_string_flags_beo
ret = 1; // DT_STRING
} else if (typeSpec == 18) { // dt_bytestr_beo
ret = 11; // DT_BYTESTR
} else if (typeSpec == 19) { // dt_sbyte
ret = 2; // DT_SHORT
} else if (typeSpec == 20) { // dt_sbyte_flags_beo
ret = 2; // DT_SHORT
} else if (typeSpec == 21) { // dt_ushort
ret = 6; // DT_LONG
} else if (typeSpec == 22) { // dt_ushort_beo
ret = 6; // DT_LONG
} else if (typeSpec == 23) { // dt_ulong
ret = 8; // DT_LONGLONG
} else if (typeSpec == 24) { // dt_ulong_beo
ret = 8; // DT_LONGLONG
} else if (typeSpec == 25) { // dt_string_utf8
ret = 1; // DT_STRING
} else if (typeSpec == 26) { // dt_string_utf8_beo
ret = 1; // DT_STRING
}
// dt_bit_int [27], dt_bit_int_beo [28], dt_bit_uint [29],
// dt_bit_uint_beo [30]
else if (typeSpec == 27 || typeSpec == 28 || typeSpec == 29 || typeSpec == 30) {
int dt = cnBlock.getSignalDataType();
int nb = cnBlock.getNumberOfBits();
if ((dt == 0 || dt == 9 || dt == 13) && nb >= 1 && nb <= 8) { // unsigned
// byte
ret = 5; // DT_BYTE
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 1 && nb <= 8) { // signed
// byte
ret = 5; // DT_BYTE
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 9 && nb <= 16) { // unsigned
// short
ret = 6; // DT_LONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 9 && nb <= 16) { // signed
// short
ret = 2; // DT_SHORT
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 17 && nb <= 32) { // unsigned
// int
ret = 8; // DT_LONGLONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 17 && nb <= 32) { // int
ret = 6; // DT_LONG
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 33) { // unsigned
// int
// >32
// bit
ret = 8; // DT_LONGLONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 33) { // signed
// int
// >32
// bit
ret = 8; // DT_LONGLONG
}
}
// dt_bit_float [31], dt_bit_float_beo [32]
else if (typeSpec == 31 || typeSpec == 32) {
int dt = cnBlock.getSignalDataType();
int nb = cnBlock.getNumberOfBits();
if ((dt == 2 || dt == 3 || dt == 11 || dt == 12 || dt == 15 || dt == 16) && nb == 32) { // ieeefloat4,
// ieeefloat4_beo
ret = 3; // DT_FLOAT
} else if ((dt == 2 || dt == 3 || dt == 11 || dt == 12 || dt == 15 || dt == 16) && nb == 64) { // ieeefloat8,
// ieeefloat8_beo
ret = 7; // DT_DOUBLE
}
}
// not found!
else {
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"Unsupported typeSpec: " + typeSpec);
}
return ret;
}
/**
* Returns the target ASAM ODS measurement quantity data type for a MDF3
* channel description.<br/>
* The data type is determined by the formula, the signal data type and the
* number of bits.
*
* @param expandDataType
* Indicates whether to expand data type or not.
* @param cnBlock
* The MDF CNBLOCK.
* @param ccBlock
* The MDF CCBLOCK.
* @return The ASAM ODS data type.
* @throws IOException
* Unable to determine data type.
*/
private static int getDataType(boolean expandDataType, CNBLOCK cnBlock, CCBLOCK ccBlock) throws IOException {
// CCBLOCK may be null
int formula = 65535;
if (ccBlock != null) {
formula = ccBlock.getFormulaIdent();
}
int dt = cnBlock.getSignalDataType();
int nb = cnBlock.getNumberOfBits();
// STRING
if (dt == 7) {
return 1; // DT_STRING
} else if (dt == 8) {
return 11; // DT_BYTESTR
}
// 0 = parametric, linear
// 6 = polynomial function
// 7 = exponential function
// 8 = logarithmic function
else if (formula == 0 || formula == 1 || formula == 6 || formula == 7 || formula == 8) {
if (nb == 0 && dt == 0) {
return 8; // DT_LONGLONG
} else if (nb == 1) {
// 1 bit should be DT_BOOLEAN, but most of tools do not support
// this
return 5; // DT_BYTE
} else if (nb >= 2 && nb <= 32) {
return 3; // DT_FLOAT
} else if (nb >= 33 && nb <= 64) {
return 7; // DT_DOUBLE
}
}
// 9 = ASAP2 Rational conversion formula
// 11 = ASAM-MCD2 Text Table, (COMPU_VTAB)
// 12 = ASAM-MCD2 Text Range Table (COMPU_VTAB_RANGE)
// 65535 = 1:1 conversion formula (Int = Phys)
else if (formula == 9 || formula == 11 || formula == 12 || formula == 65535) {
if (dt == 8) {
return 11; // DT_BYTESTR
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 1 && nb <= 8) { // dt_byte
return expandDataType ? 3 : 5; // [DT_FLOAT] DT_BYTE
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 1 && nb <= 8) { // dt_sbyte
return expandDataType ? 3 : 2; // [DT_FLOAT] DT_SHORT
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 9 && nb <= 16) { // dt_ushort,
// dt_ushort_beo
return expandDataType ? 3 : 6; // [DT_FLOAT] DT_LONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 9 && nb <= 16) { // dt_short,
// dt_short_beo
return expandDataType ? 3 : 2; // [DT_FLOAT] DT_SHORT
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 17 && nb <= 32) { // dt_ulong,
// dt_ulong_beo
return expandDataType ? 7 : 8; // [DT_DOUBLE] DT_LONGLONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 17 && nb <= 32) { // dt_long,
// dt_long_beo
return expandDataType ? 3 : 6; // [DT_FLOAT] DT_LONG
} else if ((dt == 0 || dt == 9 || dt == 13) && nb >= 33) { // unsigned
// int
// >32
// bit
return expandDataType ? 7 : 8; // [DT_DOUBLE] DT_LONGLONG
} else if ((dt == 1 || dt == 10 || dt == 14) && nb >= 33 && nb <= 64) { // dt_longlong,
// dt_longlong_beo
return expandDataType ? 7 : 8; // [DT_DOUBLE] DT_LONGLONG
} else if ((dt == 2 || dt == 3 || dt == 11 || dt == 12 || dt == 15 || dt == 16) && nb == 32) { // ieeefloat4,
// ieeefloat4_beo
return 3; // DT_FLOAT
} else if ((dt == 2 || dt == 3 || dt == 11 || dt == 12 || dt == 15 || dt == 16) && nb == 64) { // ieeefloat8,
// ieeefloat8_beo
return 7; // DT_DOUBLE
}
}
throw new IOException("Unsupported MDF3 datatype: " + cnBlock + "\n " + ccBlock);
}
/**
* Returns the target ASAM ODS sequence representation for the external
* component description.<br/>
* List of MDF3 formula types:
* <ul>
* <li>0 = parametric, linear</li>
* <li>1 = tabular with interpolation</li>
* <li>2 = tabular</li>
* <li>6 = polynomial function</li>
* <li>7 = exponential function</li>
* <li>8 = logarithmic function</li>
* <li>9 = ASAP2 Rational conversion formula</li>
* <li>10 = ASAM-MCD2 Text formula</li>
* <li>11 = ASAM-MCD2 Text Table, (COMPU_VTAB)</li>
* <li>12 = ASAM-MCD2 Text Range Table (COMPU_VTAB_RANGE)</li>
* <li>132 = Date (Based on 7 Byte Date data structure)</li>
* <li>133 = time (Based on 6 Byte Time data structure)</li>
* <li>65535 = 1:1 conversion formula (Int = Phys)</li>
* </ul>
*
* @return The ASAM ODS sequence representation enum value.
* @throws AoException
* @throws ConvertException
*/
private static int getSeqRep(CCBLOCK ccBlock, String meqName) throws AoException, IOException {
// CCBLOCK may be null, assume explicit
if (ccBlock == null) {
return 7;
}
int formula = ccBlock.getFormulaIdent();
// 'parametric, linear' => 'raw_linear_external'
if (formula == 0) {
double[] genParams = ccBlock.getValuePairsForFormula();
if (genParams != null && genParams.length == 2 && Double.compare(genParams[1], 0) == 0) {
// this is a special case with a given offset and a scaling
// factor of '0'
// -> therefore sequence representation has to be implicit
// constant
return 1;
} else {
return 8;
}
}
// 'tabular with interpolation' => 'external_component'
else if (formula == 1) {
return 7;
}
// 'polynomial function' => 'raw_polynomial_external'
else if (formula == 6) {
return 9;
}
// 'ASAP2 Rational conversion formula' => 'external_component'
else if (formula == 9) {
if (isRatConv2RawLinExt(ccBlock)) {
// redirect => 'raw_linear_external'
return 8;
} else if (isRatConv2ExtComp(ccBlock)) {
// manual calculation => 'external_component'
return 7;
} else {
throw new AoException(ErrorCode.AO_IMPLEMENTATION_PROBLEM, SeverityFlag.ERROR, 0,
"unable write channel with rational conversion for ccBlock: " + ccBlock);
}
}
// 'ASAM-MCD2 Text Table, (COMPU_VTAB)' => 'external_component'
else if (formula == 11) {
return 7;
}
// 'ASAM-MCD2 Text Range Table (COMPU_VTAB_RANGE)'
else if (formula == 12) {
// 'ASAM-MCD2 Text Range Table (COMPU_VTAB_RANGE) with formula' =>
// 'raw_linear_external'
// CANape specific feature: default value contains macro for linear
// formula
if (ccBlock.getDefaultTextForTextRangeTable() != null
&& ccBlock.getDefaultTextForTextRangeTable().startsWith("{LINEAR_CONV")) {
return 8;
}
// 'external_component'
else {
return 7;
}
}
// '1:1 conversion formula' => external_component
else if (formula == 65535) {
return 7;
}
throw new IOException(
"Unsupported MDF Conversion formula identifier for channel '" + meqName + "': " + formula);
}
/**
* Returns the generation parameters
*
* @return The ASAM ODS sequence representation enum value.
* @throws AoException
* @throws IOException
*/
private static double[] getGenerationParameters(CCBLOCK ccBlock) throws AoException, IOException {
// CCBLOCK may be null, assume explicit
if (ccBlock == null) {
return new double[0];
}
int formula = ccBlock.getFormulaIdent();
// 'parametric, linear'
if (formula == 0) {
double[] genParams = ccBlock.getValuePairsForFormula();
if (Double.compare(genParams[1], 0D) == 0) {
// scaling factor is '0', reduce to offset (implicit_constant)
return new double[] { genParams[0] };
} else {
return genParams;
}
}
// 'polynomial function'
else if (formula == 6) {
double[] genParams = new double[7];
genParams[0] = 5;
genParams[1] = ccBlock.getValuePairsForFormula()[0];
genParams[2] = ccBlock.getValuePairsForFormula()[1];
genParams[3] = ccBlock.getValuePairsForFormula()[2];
genParams[4] = ccBlock.getValuePairsForFormula()[3];
genParams[5] = ccBlock.getValuePairsForFormula()[4];
genParams[6] = ccBlock.getValuePairsForFormula()[5];
return genParams;
}
else if (formula == 9) {
double[] genParams = ccBlock.getValuePairsForFormula();
if (isRatConv2RawLinExt(ccBlock)) {
// sequence rep.: 'raw_linear_external'
return new double[] { genParams[2], genParams[1] };
} else if (isRatConv2ExtComp(ccBlock)) {
// sequence rep.: 'external_component'
return new double[0];
} else {
throw new AoException(ErrorCode.AO_IMPLEMENTATION_PROBLEM, SeverityFlag.ERROR, 0,
"unable write channel with rational conversion for ccBlock: " + ccBlock);
}
}
// 'ASAM-MCD2 Text Range Table (COMPU_VTAB_RANGE) with formula' =>
// 'raw_linear_external'
// CANape specific feature: default value contains macro for linear
// formula
// example: {LINEAR_CONV "0.3*{X}-30"}
else if (formula == 12 && ccBlock.getDefaultTextForTextRangeTable() != null
&& ccBlock.getDefaultTextForTextRangeTable().startsWith("{LINEAR_CONV")) {
Pattern pattern = Pattern.compile("\\{LINEAR_CONV\\s\\\"(.*)\\*\\{X\\}(.*)\\\"\\}");
Matcher matcher = pattern.matcher(ccBlock.getDefaultTextForTextRangeTable());
if (matcher.matches()) {
double[] genParams = new double[2];
genParams[0] = Double.valueOf(matcher.group(2));
genParams[1] = Double.valueOf(matcher.group(1));
return genParams;
} else {
throw new IOException("Unparsable formula: " + ccBlock.getDefaultTextForTextRangeTable());
}
}
return new double[0];
}
/**************************************************************************
* handling of special MDF3 contents
**************************************************************************/
/**
* MDF3 files created by the G.i.N. CLExport tools may contain the correct
* measurement date/time only within the comment text. The predefined date
* fields in this case are filled with a dummy value.
*
* @param ieMea
* The target AoMeasurement instance.
* @param hdBlock
* The MDF3 header block.
* @param fileComment
* The MDF3 file comment.
* @throws AoException
* error setting date to instance.
*/
private static void handleCLExportDate(ODSInsertStatement ins, HDBLOCK hdBlock, TXBLOCK fileComment)
throws AoException {
if (hdBlock.getDateStarted() != null && !hdBlock.getDateStarted().equals("01:01:1980")) {
return;
}
if (hdBlock.getTimeStarted() != null && !hdBlock.getTimeStarted().equals("00:00:00")) {
return;
}
if (fileComment == null || fileComment.getText() == null || fileComment.getText().length() < 1) {
return;
}
// 'Data date/time: 28.08.2015 17:00:40': REPRESENTS THE END-TIME OF A
// MEASUREMENT!
Pattern pattern = Pattern.compile("^.*Data date/time:\\s*(.*)$", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(fileComment.getText());
if (matcher.find()) {
try {
DateFormat clDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
Date date = clDateFormat.parse(matcher.group(1));
ins.setDateVal("date_created", ODSHelper.asODSDate(date));
ins.setDateVal("mea_begin", null);
ins.setDateVal("mea_end", ODSHelper.asODSDate(date));
LOG.info("Found special CLExport date format in comment: " + matcher.group(1));
} catch (ParseException e) {
LOG.warn(e.getMessage(), e);
}
}
}
private static String[] readStringDataValues(DGBLOCK dgBlock, CGBLOCK cgBlock, CNBLOCK cnBlock) throws IOException {
List<String> list = new ArrayList<String>();
SeekableByteChannel sbc = dgBlock.sbc;
int recordIdOffset = dgBlock.getNoRecordIds() > 0 ? 1 : 0;
long mapStart = dgBlock.getLnkDataRecords();
long mapSize = (cgBlock.getDataRecordSize() + recordIdOffset) * cgBlock.getNoOfRecords();
ByteBuffer bb = ByteBuffer.allocate((int) mapSize);
sbc.position(mapStart);
sbc.read(bb);
bb.rewind();
// iterate over records
for (int i = 0; i < cgBlock.getNoOfRecords(); i++) {
// read record
byte[] record = new byte[cgBlock.getDataRecordSize() + recordIdOffset];
bb.get(record);
// skip first bits and read value
BitInputStream bis = new BitInputStream(record);
int skipBits = recordIdOffset * 8 + cnBlock.getByteOffset() * 8 + cnBlock.getNumberOfFirstBits();
bis.skip(skipBits);
byte[] b = bis.readByteArray(cnBlock.getNumberOfBits());
// build string value and append
list.add(Mdf3Util.readChars(ByteBuffer.wrap(b), b.length));
}
return list.toArray(new String[0]);
}
/**
* Checks whether channel with rational conversion may be stored as a
* 'raw_linear_external'.
* <p>
* NOTE: This is a workaround to provide channels with formula == 9 with a
* reduced set of generation parameters. Such channel values are described
* with a rational conversion, which can not be described in ODS. To do so
* the generation parameters p1, p3 and p4 must be ZERO and p5 must be ONE.
* <p>
* <b>ATTENTION: THIS IS JUST A WORKAROUND UND HAS TO BE REMOVED AS SOON AS
* IT IS POSSIBLE TO DESCRIBE SUCH CHANNELS IN ODS!</b>
*
* @param ccBlock
* the conversion block
* @return true if the values must be calculated and stored in an external
* file
*/
@Deprecated
private static boolean isRatConv2RawLinExt(CCBLOCK ccBlock) {
if (ccBlock == null) {
return false;
} else if (ccBlock.getFormulaIdent() == 9) {
// rational conversion
double[] genParams = ccBlock.getValuePairsForFormula();
if (genParams == null || genParams.length != 6) {
return false;
} else {
boolean p1Zero = Double.compare(genParams[0], 0D) == 0;
boolean p4Zero = Double.compare(genParams[3], 0D) == 0;
boolean p5Zero = Double.compare(genParams[4], 0D) == 0;
boolean p6One = Double.compare(genParams[5], 1D) == 0;
return p1Zero && p4Zero && p5Zero && p6One;
}
}
return false;
}
/**
* Checks whether channel with rational conversion may be stored as a
* 'external_component'.
* <p>
* NOTE: This is a workaround to provide channels with formula == 9. Such
* channel values are described with a rational conversion, which can not be
* described in ODS. Therefore the whole value sequence is calculated and
* referenced as an 'external_component'.
* <p>
* <b>ATTENTION: THIS IS JUST A WORKAROUND UND HAS TO BE REMOVED AS SOON AS
* IT IS POSSIBLE TO DESCRIBE SUCH CHANNELS IN ODS!</b>
*
* @param ccBlock
* the conversion block
* @return true if the values must be calculated and stored in an external
* file
*/
@Deprecated
private static boolean isRatConv2ExtComp(CCBLOCK ccBlock) {
if (ccBlock == null) {
return false;
} else if (ccBlock.getFormulaIdent() == 9) {
// rational conversion
double[] genParams = ccBlock.getValuePairsForFormula();
if (genParams == null || genParams.length != 6) {
return false;
} else {
boolean p1Zero = Double.compare(genParams[0], 0D) != 0;
boolean p4Zero = Double.compare(genParams[3], 0D) != 0;
boolean p5Zero = Double.compare(genParams[4], 0D) != 0;
boolean p6One = Double.compare(genParams[5], 1D) != 0;
return p1Zero || p4Zero || p5Zero || p6One;
}
}
return false;
}
/**
* Write the instances of 'AoExternalComponent'.
* <p>
* One instance is written for all calculated vaues.
* <p>
* NOTE: This is a workaround to provide channels with formula == 2. Such
* channel values are described with a rational conversion, which can not be
* described in ODS. Therefore the whole value sequence is calculated and
* referenced as an 'external_component'.
* <p>
* <b>ATTENTION: THIS IS JUST A WORKAROUND UND HAS TO BE REMOVED AS SOON AS
* IT IS POSSIBLE TO DESCRIBE SUCH CHANNELS IN ODS!</b>
*
* @param modelCache
* The application model cache.
* @param iidLc
* The instance id of the 'AoLocalColumn' instance.
* @param idBlock
* The IDBLOCK.
* @param dgBlock
* The DGBLOCK.
* @param cgBlock
* The CGBLOCK.
* @param cnBlock
* The CNBLOCK.
* @param ccBlock
* The CCBLOCK.
* @throws AoException
* Error writing to session.
* @throws IOException
* Error reading from MDF file.
*/
@Deprecated
private void createCustomRatConvEC(ODSModelCache modelCache, long iidLc, IDBLOCK idBlock, DGBLOCK dgBlock,
CGBLOCK cgBlock, CNBLOCK cnBlock, CCBLOCK ccBlock) throws AoException, IOException {
if (customRatConfPath == null) {
customRatConfPath = idBlock.getMdfFilePath().resolveSibling("rational_conversion.calc");
if (!Files.exists(customRatConfPath)) {
Files.createFile(customRatConfPath);
}
}
short bo = (short) (cnBlock.getNumberOfFirstBits() % 8);
short bc = bo != 0 && cnBlock.getNumberOfBits() % 8 != 0 ? (short) cnBlock.getNumberOfBits() : 0;
if (bo != 0 || bc != 0 || cnBlock.getNumberOfBits() % 8 != 0) {
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0, "bit count '" + bc
+ "' and bit offset '" + bo + "' is not supported for custom ration conversion");
}
try (SeekableByteChannel channel = Files.newByteChannel(customRatConfPath, StandardOpenOption.APPEND)) {
ByteBuffer writeBuffer = ByteBuffer.wrap(new byte[8]);
writeBuffer.order(ByteOrder.LITTLE_ENDIAN);
long startOffset = channel.position();
long count = 0;
int recordIdOffset = dgBlock.getNoRecordIds() > 0 ? 1 : 0;
long vo = recordIdOffset + cnBlock.getByteOffset() + cnBlock.getNumberOfFirstBits() / 8;
long bs = cgBlock.getDataRecordSize() + recordIdOffset;
long so = dgBlock.getLnkDataRecords();
double[] p = ccBlock.getValuePairsForFormula();
int bits = (int) cnBlock.getNumberOfBits();
int dt = cnBlock.getSignalDataType();
boolean isInteger = dt == 0 || dt == 1 || dt == 9 || dt == 10 || dt == 13 || dt == 14;
boolean isReal = dt == 2 || dt == 3 || dt == 11 || dt == 12 || dt == 15 || dt == 16;
boolean isUnsigned = dt == 0 || dt == 9 || dt == 13;
ByteOrder byteOrder;
if (dt < 4) {
byteOrder = idBlock.getIdByteOrder() == 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
} else {
if (dt > 8 && dt < 13) {
byteOrder = ByteOrder.BIG_ENDIAN;
} else if (dt > 12 && dt < 17) {
byteOrder = ByteOrder.LITTLE_ENDIAN;
} else {
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"unable to determine byte order for CN block with rational conversion :" + cnBlock);
}
}
for (long i = 0; i < cgBlock.getNoOfRecords(); i++) {
idBlock.sbc.position(so + i * bs + vo);
ByteBuffer byteBuffer = ByteBuffer.allocate(bits / 8);
byteBuffer.order(byteOrder);
idBlock.sbc.read(byteBuffer);
byteBuffer.rewind();
double internal = 0;
if (isInteger) {
if (bits == 8) {
// isUnsigned ? short : byte
internal = isUnsigned ? byteBuffer.get() & 0xFF : byteBuffer.get();
} else if (bits == 16) {
// isUnsigned ? int : short
internal = isUnsigned ? byteBuffer.getShort() & 0xFFFF : byteBuffer.getShort();
} else if (bits == 32) {
// isUnsigned ? long : int
internal = isUnsigned ? byteBuffer.getInt() & 0xFFFFFFFF : byteBuffer.getInt();
} else if (bits == 64) {
if (isUnsigned) {
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"reading unsigned 64 bit integeres is not implemented");
}
// to support unsigned 64 bit, BigInteger has to be
// used -> performance costs
internal = byteBuffer.getLong();
} else {
String unsigned = isUnsigned ? "unsigned" : "signed";
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"customized reading of '" + bits + "' bit '" + unsigned
+ "' intergers is not implemented");
}
} else if (isReal) {
if (bits == 32) {
// ieee754 floating point
internal = byteBuffer.getFloat();
} else if (bits == 64) {
// ieee754 floating point
internal = byteBuffer.getDouble();
} else {
String unsigned = isUnsigned ? "unsigned" : "signed";
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"customized reading of '" + bits + "' bit '" + unsigned
+ "' real is not implemented");
}
} else {
throw new AoException(ErrorCode.AO_BAD_PARAMETER, SeverityFlag.ERROR, 0,
"given value is neither an integer nor a real number");
}
double phys = (p[0] * Math.pow(internal, 2) + p[1] * internal + p[2])
/ (p[3] * Math.pow(internal, 2) + p[4] * internal + p[5]);
writeBuffer.putDouble(phys);
writeBuffer.rewind();
channel.write(writeBuffer);
writeBuffer.rewind();
count++;
}
if (count < 0 || count != (int) count || count * 8 > Integer.MAX_VALUE) {
throw new AoException(ErrorCode.AO_IMPLEMENTATION_PROBLEM, SeverityFlag.ERROR, 0,
"value count exceeded max supported block size supported by ODS");
}
// values have been calculated and written to an external file
ODSInsertStatement ins = new ODSInsertStatement(modelCache, "ec");
ins.setStringVal("iname", "ec_custom_rat_conv");
ins.setLongVal("cl", (int) count);
ins.setEnumVal("vt", 6); // ieeefloat8 (little endian; 11 would be
// big endian)
ins.setLongLongVal("so", startOffset);
ins.setLongVal("bs", (int) count * 8);
ins.setLongVal("vb", (int) count);
ins.setLongVal("vo", 0);
ins.setStringVal("fl", customRatConfPath.getFileName().toString());
ins.setLongLongVal("lc", iidLc);
ins.execute();
}
}
}