/* * FileReaderCDF.cc * * Created on: Nov 24, 2014 * Author: AKKA */ #include "FileReaderCDF.hh" #include "ParameterManager.hh" #include "LocalFileInterfaceConfig.hh" #include "TimeUtil.hh" #include namespace AMDA { namespace LocalFileInterface { FileReaderCDF::FileReaderCDF() : _cdfid(NULL), _workingBuffers(new CDFWorkingBuffers) { } FileReaderCDF::~FileReaderCDF() { } bool FileReaderCDF::open(std::string filePath) { boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); if (isOpened()) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::open - A file is already opened"); return false; } CDFstatus status = CDFopenCDF(filePath.c_str(), &_cdfid); return (status == CDF_OK); } bool FileReaderCDF::close(void) { boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); if (isOpened()) { CDFstatus status = CDFcloseCDF(_cdfid); if (status == CDF_OK) _cdfid = NULL; } return (_cdfid == NULL); } bool FileReaderCDF::isOpened(void) { return (_cdfid != NULL); } std::string FileReaderCDF::getTimeParamId(void) { boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); std::string result = ""; //search in rVariables long numrVars; CDFstatus status = CDFgetNumrVars (_cdfid, &numrVars); if (status == CDF_OK) { for (long i = 0; i < numrVars; ++i) { CDFVarInfo varInfo; status = CDFvarInquire(_cdfid, i, varInfo._name, &varInfo._dataType, &varInfo._numElts, &varInfo._recVary, varInfo._dimVarys); if (status == CDF_OK) { if ((varInfo._dataType == CDF_EPOCH) || (varInfo._dataType == CDF_EPOCH16) || (varInfo._dataType == CDF_TIME_TT2000)) { result = varInfo._name; return result; } } } } //search in zVariables long numzVars; status = CDFgetNumzVars (_cdfid, &numzVars); if (status == CDF_OK) { for (long i = 0; i < numzVars; ++i) { CDFVarInfo varInfo; status = CDFinquirezVar(_cdfid, i, varInfo._name, &varInfo._dataType, &varInfo._numElts, &varInfo._numDims, varInfo._dimSizes, &varInfo._recVary, varInfo._dimVarys); if (status == CDF_OK) { if ((varInfo._dataType == CDF_EPOCH) || (varInfo._dataType == CDF_EPOCH16) || (varInfo._dataType == CDF_TIME_TT2000)) { result = varInfo._name; return result; } } } } return result; } bool FileReaderCDF::getParamInfo(std::string& paramId, LocalParamType& paramType, int& dim1Size, int& dim2Size) { boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); paramType = LocalParamType::TYPE_UNKNOWN; dim1Size = 0; dim2Size = 0; //get CDF var info CDFVarInfo varInfo; if (!getCDFVarInfo(paramId, varInfo)) return false; //get ParamType in relation to the CDF var type switch (varInfo._dataType) { case CDF_REAL4 : case CDF_FLOAT : paramType = LocalParamType::TYPE_FLOAT; break; case CDF_REAL8 : case CDF_DOUBLE : case CDF_EPOCH : paramType = LocalParamType::TYPE_DOUBLE; break; case CDF_EPOCH16 : paramType = LocalParamType::TYPE_EPOCH16; break; case CDF_BYTE : case CDF_INT1 : case CDF_UINT1 : case CDF_INT2 : case CDF_UINT2 : paramType = LocalParamType::TYPE_SHORT; break; case CDF_INT4 : case CDF_UINT4 : paramType = LocalParamType::TYPE_INT; break; case CDF_TIME_TT2000 : case CDF_INT8 : paramType = LocalParamType::TYPE_TT2000; break; case CDF_CHAR : case CDF_UCHAR : default : LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamInfo - Unknown data type"); return false; } //set param size if (varInfo._numDims > 2) { dim1Size = 0; dim2Size = 0; LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamInfo - Unknown dimension"); return false; } else if (varInfo._numDims == 2) { //Dimensions for a Tab2D dim1Size = varInfo._dimSizes[0]; dim2Size = varInfo._dimSizes[1]; } else if (varInfo._numDims == 1) { //Dimensions for a Vector dim1Size = varInfo._dimSizes[0]; dim2Size = 1; } else { //Dimensions for a Scalar dim1Size = 1; dim2Size = 1; } return true; } bool FileReaderCDF::getCDFVarInfo(std::string paramName, CDFVarInfo& varInfo) { if (paramName.empty()) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Empty param name"); return false; } if (paramName.size() >= CDF_VAR_NAME_LEN256) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Param name too large"); return false; } //reset info structure memset(&varInfo,0,sizeof(CDFVarInfo)); //confirm variable existence memcpy(varInfo._name,paramName.c_str(),paramName.size()*sizeof(char)); varInfo._name[paramName.size()] = '\0'; varInfo._num = CDFgetVarNum(_cdfid,varInfo._name); if (varInfo._num < 0) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Cannot find variable"); return false; } //Is it a zVariable? CDFstatus status = CDFconfirmzVarExistence(_cdfid,varInfo._name); bool isZVar = (status == CDF_OK); if (isZVar) { //get info about a zVariable varInfo._type = CDFVT_Z; status = CDFinquirezVar(_cdfid, varInfo._num, varInfo._name, &varInfo._dataType, &varInfo._numElts, &varInfo._numDims, varInfo._dimSizes, &varInfo._recVary, varInfo._dimVarys); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFinquirezVar"); return false; } status = CDFgetzVarMaxWrittenRecNum(_cdfid, varInfo._num, &varInfo._maxRecNum); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFgetzVarMaxWrittenRecNum"); return false; } status = CDFgetDataTypeSize(varInfo._dataType,&varInfo._numBytes); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFgetDataTypeSize"); return false; } } else { //get info about a rVariable varInfo._type = CDFVT_R; status = CDFvarInquire(_cdfid, varInfo._num, varInfo._name, &varInfo._dataType, &varInfo._numElts, &varInfo._recVary, varInfo._dimVarys); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFvarInquire"); return false; } long encoding; long majority; long numVars; long numAttrs; status = CDFinquire (_cdfid, &varInfo._numDims, varInfo._dimSizes, &encoding, &majority, &varInfo._maxRecNum, &numVars, &numAttrs); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFinquire"); return false; } status = CDFgetDataTypeSize(varInfo._dataType,&varInfo._numBytes); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Error to call CDFgetDataTypeSize"); return false; } } return true; } int FileReaderCDF::getRecordIndex(std::string& timeId, double time) { LOG4CXX_DEBUG(gLogger, "FileReaderCDF::getRecordIndex"); boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); //get corresponding epoch time double timeEPOCH = timeStampToEPOCHTime(time); //get all time data long nbRec; long dimSize; long dataType; long numBytes; if (!getCDFData(timeId, 0, CDFWorkingBuffers::BufferType::BUFFER_TIME, dataType, numBytes, nbRec, dimSize)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Cannot get time var data"); return -1; } if ((dataType != CDF_EPOCH) && (dataType != CDF_EPOCH16) && (dataType != CDF_TIME_TT2000)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Not a time var " << timeId); return -1; } //search nearest record for (long i = 0; i < nbRec; ++i) { double crtTime; switch (dataType) { case CDF_EPOCH : if (!extractDouble(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimSize,dataType,numBytes,crtTime)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Error to extract time data"); return -1; } break; case CDF_EPOCH16 : double crtEpoch16Time[2]; if (!extractEpoch16(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimSize,dataType,numBytes,crtEpoch16Time)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Error to extract time data"); return -1; } crtTime = epoch16ToEpochTime(crtEpoch16Time); break; case CDF_TIME_TT2000 : long long crtTT2000Time; if (!extractTT2000(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimSize,dataType,numBytes,crtTT2000Time)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Error to extract time data"); return -1; } crtTime = tt2000ToEpochTime(crtTT2000Time); break; default: LOG4CXX_ERROR(gLogger, "FileReaderCDF::getRecordIndex - Error to extract time data"); return -1; } if (timeEPOCH <= crtTime) return i; } LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFVarInfo - Cannot get record index"); return -1; } FileReaderStatus FileReaderCDF::getParamPacketData(std::string& timeId, std::string& paramId, int recordIndex, double stopTime, LocalParamDataPacket *packet) { LOG4CXX_DEBUG(gLogger, "FileReaderCDF::getParamPacketData"); boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); //get the size of one value of a data in the packet int packetValueSize; switch (packet->getType()) { case LocalParamType::TYPE_FLOAT: packetValueSize = sizeof(float); break; case LocalParamType::TYPE_DOUBLE: packetValueSize = sizeof(double); break; case LocalParamType::TYPE_SHORT: packetValueSize = sizeof(short); break; case LocalParamType::TYPE_INT: packetValueSize = sizeof(int); break; default: LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - ParamType not implemented for CDF format"); return FRS_ERROR; } //get time data long nbTimeRec; long dimTimeSize; long dataTimeType; long numTimeBytes; if (!getCDFData(timeId, recordIndex, CDFWorkingBuffers::BufferType::BUFFER_TIME, dataTimeType, numTimeBytes, nbTimeRec, dimTimeSize)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Cannot get time var data for " << timeId); return FRS_ERROR; } if ((dataTimeType != CDF_EPOCH) && (dataTimeType != CDF_EPOCH16) && (dataTimeType != CDF_TIME_TT2000)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Bad time format " << timeId); return FRS_ERROR; } //get data long nbRec; long dimSize; long dataType; long numBytes; if (!getCDFData(paramId, recordIndex, CDFWorkingBuffers::BufferType::BUFFER_DATA, dataType, numBytes, nbRec, dimSize)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Cannot get var data for " << paramId); return FRS_ERROR; } //check dimension integrity if (dimSize != packet->getDimsSize()) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Incompatibility between dim size of the packet and the CDF var"); return FRS_ERROR; } //alloc buffer for one record of the packet _workingBuffers->reallocBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC, packetValueSize*packet->getDimsSize()); for (int i = 0; i < nbTimeRec; ++i) { //time double crtTime; double crtTimeEPOCH; switch (dataTimeType) { case CDF_EPOCH : if (!extractDouble(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimTimeSize,dataTimeType,numTimeBytes,crtTimeEPOCH)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract time data"); return FRS_ERROR; } break; case CDF_EPOCH16 : double crtTimeEPOCH16[2]; if (!extractEpoch16(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimTimeSize,dataTimeType,numTimeBytes,crtTimeEPOCH16)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract time data"); return FRS_ERROR; } crtTimeEPOCH = epoch16ToEpochTime(crtTimeEPOCH16); break; case CDF_TIME_TT2000 : long long crtTimeTT2000; if (!extractTT2000(CDFWorkingBuffers::BufferType::BUFFER_TIME, i, 0, dimTimeSize,dataTimeType,numTimeBytes,crtTimeTT2000)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract time data"); return FRS_ERROR; } crtTimeEPOCH = tt2000ToEpochTime(crtTimeTT2000); break; default: LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract time data"); return FRS_ERROR; } crtTime = epochTimeToTimeStamp(crtTimeEPOCH); //check if stop time is reached if (crtTime > stopTime) return FRS_FINISH; bool packetFull; if (i >= nbRec) { //add record data if (!packet->addData(crtTime, _workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC), packetFull)) { if (packetFull) return FRS_MORE; LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to add data in packet"); return FRS_ERROR; } continue; } for (int j = 0; j < dimSize; ++j) { switch (packet->getType()) { case LocalParamType::TYPE_FLOAT: { //extract and copy value in the buffer float f; if (!extractFloat(CDFWorkingBuffers::BufferType::BUFFER_DATA, i, j, dimSize, dataType, numBytes, f)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract a data"); return FRS_ERROR; } void* p = (void*)((intptr_t)_workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC) + j*packetValueSize); memcpy(p,&f,sizeof(float)); } break; case LocalParamType::TYPE_DOUBLE: { //extract and copy value in the buffer double d; if (!extractDouble(CDFWorkingBuffers::BufferType::BUFFER_DATA, i, j, dimSize, dataType, numBytes, d)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract a data"); return FRS_ERROR; } void* p = (void*)((intptr_t)_workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC) + j*packetValueSize); memcpy(p,&d,sizeof(double)); } break; case LocalParamType::TYPE_SHORT: { //extract and copy value in the buffer short s; if (!extractShort(CDFWorkingBuffers::BufferType::BUFFER_DATA, i, j, dimSize, dataType, numBytes, s)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract a data"); return FRS_ERROR; } void* p = (void*)((intptr_t)_workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC) + j*packetValueSize); memcpy(p,&s,sizeof(short)); } break; case LocalParamType::TYPE_INT: { //extract and copy value in the buffer int ii; if (!extractInt(CDFWorkingBuffers::BufferType::BUFFER_DATA, i, j, dimSize, dataType, numBytes, ii)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to extract a data"); return FRS_ERROR; } void* p = (void*)((intptr_t)_workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC) + j*packetValueSize); memcpy(p,&ii,sizeof(int)); } break; default: LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - ParamType not implemented for CDF format"); return FRS_ERROR; } } //add data record in the packet if (!packet->addData(crtTime, _workingBuffers->getBuffer(CDFWorkingBuffers::BufferType::BUFFER_PACKETREC), packetFull)) { if (packetFull) return FRS_MORE; LOG4CXX_ERROR(gLogger, "FileReaderCDF::getParamPacketData - Error to add data in packet"); return FRS_ERROR; } } return FRS_EOF; } double FileReaderCDF::timeStampToEPOCHTime(double timeStamp) { std::stringstream isoTime; TimeUtil::formatTimeDateInIso(timeStamp, isoTime); std::string timeString = isoTime.str(); char timeChar[EPOCH4_STRING_LEN+1]; memset(timeChar,0,(EPOCH4_STRING_LEN+1)*sizeof(char)); if ((timeString.size() > 0) && (timeString.size() <= EPOCH4_STRING_LEN)) memcpy(timeChar,timeString.c_str(),timeString.size()*sizeof(char)); return parseEPOCH4(timeChar); } double FileReaderCDF::epochTimeToTimeStamp(double epochTime) { char isoTime[EPOCH4_STRING_LEN+1]; encodeEPOCH4(epochTime,isoTime); return TimeUtil::readTimeInIso(isoTime); } double FileReaderCDF::epoch16ToEpochTime(double epoch16Time[]) { long year, month, day, hour, minute, second, msec, microsec, nanosec, picosec; EPOCH16breakdown(epoch16Time, &year, &month, &day, &hour, &minute, &second, &msec, µsec, &nanosec, &picosec); return computeEPOCH(year, month, day, hour, minute, second, msec); } double FileReaderCDF::tt2000ToEpochTime(long long tt2000Time) { return CDF_TT2000_to_UTC_EPOCH(tt2000Time); } bool FileReaderCDF::getCDFData(std::string& varId, long startRec, CDFWorkingBuffers::BufferType bufferType, long& dataType, long& numBytes, long& nbRec, long& dimSize) { nbRec = 0; dimSize = 0; CDFVarInfo varInfo; if (!getCDFVarInfo(varId, varInfo)) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFData - Cannot get param info " << varId); return false; } dataType = varInfo._dataType; numBytes = varInfo._numBytes; if (varInfo._numElts > 1) { //only possible if CDF_CHAR //not implemented (CDF_CHAR variable is not a valid variable for the ParamGet) LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFData - _numElts >= 1! " << varId); return false; } if (varInfo._maxRecNum <= 0) //no record in file return true; nbRec = varInfo._maxRecNum - startRec + 1; dimSize = 1; if (varInfo._numDims > 0) for (int i = 0; i < varInfo._numDims; ++i) dimSize *= varInfo._dimSizes[i]; _workingBuffers->reallocBuffer(bufferType, nbRec*varInfo._numBytes*dimSize); CDFstatus status = CDFgetVarRangeRecordsByVarName(_cdfid, varInfo._name, startRec, varInfo._maxRecNum, _workingBuffers->getBuffer(bufferType)); if (status != CDF_OK) { LOG4CXX_ERROR(gLogger, "FileReaderCDF::getCDFData - Error to get data for " << varId); return false; } return true; } bool FileReaderCDF::extractShort(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, short& value) { void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch(dataType) { case CDF_BYTE : case CDF_INT1 : { char* c = (char*)p; value = (short)(*c); break; } case CDF_UINT1 : { unsigned char* uc = (unsigned char*)p; value = (short)(*uc); break; } case CDF_INT2 : { short* s = (short *)p; value = (short)(*s); break; } case CDF_UINT2 : { unsigned short* us = (unsigned short*)p; value = (short)(*us); break; } default: return false; } return true; } bool FileReaderCDF::extractInt(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, int& value) { //try to extract a short short s; if (extractShort(bufferType, recIndex, dimIndex, dimSize, dataType, numBytes, s)) { //cast short to int value = (int)s; return true; } void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch (dataType) { case CDF_INT4 : { int* i = (int*)p; value = (int)(*i); break; } case CDF_UINT4 : { unsigned int* ui = (unsigned int*)(p); value = (int)(*ui); break; } default: return false; } return true; } bool FileReaderCDF::extractFloat(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, float& value) { //try to extract an int int i; if (extractInt(bufferType, recIndex, dimIndex, dimSize, dataType, numBytes, i)) { //cast int to float value = (float)i; return true; } void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch (dataType) { case CDF_REAL4 : case CDF_FLOAT : { float* f = (float*)p; value = (float)(*f); break; } default : return false; } return true; } bool FileReaderCDF::extractDouble(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, double& value) { //try to extract an float float f; if (extractFloat(bufferType, recIndex, dimIndex, dimSize, dataType, numBytes, f)) { //cast float to double value = (double)f; return true; } void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch(dataType) { case CDF_REAL8 : case CDF_DOUBLE : case CDF_EPOCH : { double* d = (double*)p; value = (double)(*d); break; } default : return false; } return true; } bool FileReaderCDF::extractEpoch16(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, double value[]) { void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch(dataType) { case CDF_EPOCH16 : { std::memcpy(value, p, 2*sizeof(double)); return true; } default : return false; } return true; } bool FileReaderCDF::extractTT2000(CDFWorkingBuffers::BufferType bufferType, long recIndex, long dimIndex, long dimSize, long dataType, long numBytes, long long& value) { void *p = (void*)((intptr_t)_workingBuffers->getBuffer(bufferType) + (recIndex*dimSize + dimIndex)*numBytes); switch(dataType) { case CDF_TIME_TT2000 : { long long* ll = (long long*)p; value = (long long)(*ll); break; } default : return false; } return true; } /* * @brief Get an information */ bool FileReaderCDF::getInfo(const char* /*pInfoName*/, std::vector& /*res*/) { LOG4CXX_DEBUG(gLogger, "FileReaderCDF::getInfo"); boost::mutex::scoped_lock scoped_lock(AMDA::Parameters::ParameterManager::mutexCDFLib); //ToDo return false; } } /* LocalFileInterface */ } /* AMDA */