Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
#
# $Id$
# $HeadURL$
# $Author$
#
ADD_SUBDIRECTORY(CodeGen)
ADD_SUBDIRECTORY(httq)
#ADD_SUBDIRECTORY(LtsBotan)
ADD_SUBDIRECTORY(LtsCommandLineParser)
ADD_SUBDIRECTORY(LtsCoordTrans)
ADD_SUBDIRECTORY(LtsCoordinate)
ADD_SUBDIRECTORY(LtsElevation)
ADD_SUBDIRECTORY(LtsLicence)
ADD_SUBDIRECTORY(LtsPluginManager)
ADD_SUBDIRECTORY(LtsSession)
ADD_SUBDIRECTORY(LtsUtils)
ADD_SUBDIRECTORY(LtsWidgets)
ADD_SUBDIRECTORY(LtsAppDescriptor)
ADD_SUBDIRECTORY(LtsAppManager)
ADD_SUBDIRECTORY(LtsMultiMonitor)
ADD_SUBDIRECTORY(LtsLogin)
ADD_SUBDIRECTORY(LtsNotification)
ADD_SUBDIRECTORY(LtsTimeline)
ADD_SUBDIRECTORY(LtsPrinting)
ADD_SUBDIRECTORY(LtsSerialPortQuerry)
ADD_SUBDIRECTORY(LtsScriptTimeline)
ADD_SUBDIRECTORY(LtsWebSocket)
ADD_SUBDIRECTORY(LtsProjectionWorkstation)
ADD_SUBDIRECTORY(LtsVirtualKeyboard)
ADD_SUBDIRECTORY(PluginInterfaces)
ADD_SUBDIRECTORY(ProjectConfig)
ADD_SUBDIRECTORY(TopicEditorBase) # TODO: rename / move ?
#ADD_SUBDIRECTORY(LtsEventTimeline)
#ADD_SUBDIRECTORY(LtsTranslator)
+128
View File
@@ -0,0 +1,128 @@
#
# $Id$
# $HeadURL$
# $Author$
#
FIND_PACKAGE(RTI REQUIRED)
FIND_PACKAGE(Grantlee5 REQUIRED)
NGL_ADD_LIBRARY(ALL codegen_base SHARED
codegen_export.h
SqlGenerator.cpp
SqlGenerator.h
StructBuilder.cpp
StructBuilder.h
XmlReader.cpp
XmlReader.h
)
NGL_TARGET_LINK_LIBRARIES(codegen_base
PRIVATE
Qt5::Xml
NGLLogger
ngltools
PUBLIC
Qt5::Core
)
NGL_TARGET_INCLUDE_DIRECTORIES(codegen_base
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
$<TARGET_PROPERTY:RTI::nddscpp2,INTERFACE_INCLUDE_DIRECTORIES>
)
NGL_TARGET_COMPILE_DEFINITIONS(codegen_base
PUBLIC
$<TARGET_PROPERTY:RTI::nddscpp2,INTERFACE_COMPILE_DEFINITIONS>
)
NGL_INSTALL_TARGET(codegen_base)
NGL_ADD_LIBRARY(ALL codegen_grantlee STATIC
CppTopicGeneratorGrantlee.cpp
CppTopicGeneratorGrantlee.h
DaoGeneratorGrantlee.cpp
DaoGeneratorGrantlee.h
DatabaseGeneratorGrantlee.cpp
DatabaseGeneratorGrantlee.h
GeneratorGrantleeBase.cpp
GeneratorGrantleeBase.h
GrantleeWrappers.cpp
GrantleeWrappers.h
svc.idl.grantlee
svc_enums.idl.grantlee
svc_translation.cpp.grantlee
svc_translation.h.grantlee
svc_dao.cpp.grantlee
svc_dao.h.grantlee
svc_db.h.grantlee
svc_db_impl.h.grantlee
svc_db_impl.cpp.grantlee
svc_dtc.h.grantlee
svc_dtc_impl.cpp.grantlee
svc_dtc_impl.h.grantlee
svc_dts.h.grantlee
svc_dts_impl.cpp.grantlee
svc_dts_impl.h.grantlee
svc_ftc.h.grantlee
svc_ftc_impl.cpp.grantlee
svc_ftc_impl.h.grantlee
svc_fts.h.grantlee
svc_fts_impl.cpp.grantlee
svc_fts_impl.h.grantlee
svc_json.cpp.grantlee
svc_json.h.grantlee
svc_topic_type_plugin.cpp.grantlee
svc_topic_type_plugin.h.grantlee
svc_record_replay_plugin.cpp.grantlee
svc_record_replay_plugin.h.grantlee
codegen_simple.py
)
NGL_TARGET_LINK_LIBRARIES(codegen_grantlee
PRIVATE
Grantlee5::Templates
NGLLogger
ProjectConfig
PUBLIC
codegen_base
)
NGL_TARGET_INCLUDE_DIRECTORIES(codegen_grantlee
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_ADD_EXECUTABLE(ALL codegen
main_grantlee.cpp
)
NGL_TARGET_LINK_LIBRARIES(codegen
codegen_grantlee
LtsCommandLineParser
nglcoreapplication
Qt5::Core
)
NGL_WRAP_UI(codegen_gui_uics
main_grantlee_gui.ui
)
NGL_ADD_EXECUTABLE(WS AAR SV NO_TRANSLATION codegen_gui
${codegen_gui_uics}
main_grantlee_gui.cpp
main_grantlee_gui.ui
)
NGL_TARGET_LINK_LIBRARIES(codegen_gui
codegen_grantlee
LtsCommandLineParser
nglapplication
Qt5::Widgets
Grantlee5::Templates
)
+354
View File
@@ -0,0 +1,354 @@
/**
* @RDE_HEADER@
* Generate an dtc/dts interface file from idl-struct defined in a xml-file
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2016-01-05
*/
#include "CppTopicGeneratorGrantlee.h"
#include "StructBuilder.h"
#include <QCryptographicHash>
#include <NGLLogger/INGLLogger.h>
#include <grantlee/context.h>
using namespace CodeGen;
CppTopicGeneratorGrantlee::CppTopicGeneratorGrantlee()
: GeneratorGrantleeBase("CodeGen.CppTopicGeneratorGrantlee")
{}
bool CppTopicGeneratorGrantlee::init(const XmlReader *reader, StructBuilder *structBuilder)
{
m_reader = reader;
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
return false;
addCustomFunctions(reader->getCustomFunctions());
return initDataTopics() && initFunctionTopics() && initIdlStructs();
}
/*
* DataTopics -> DataTopicWrapper
*/
bool CppTopicGeneratorGrantlee::initDataTopics()
{
DataTopicsWrapper *dataTopicsWrapper = new DataTopicsWrapper(QString(), this);
const auto dataTopics = m_reader->getIdlStructInformation();
QVector<IdlData> keyColumns;
QVector<IdlData> trColumns;
for (const CodeGen::TopicTypeInformation &tti : dataTopics)
{
if (!tti.isDataObject)
continue;
keyColumns.clear();
trColumns.clear();
const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(tti.name);
if (!ctti.isValid())
{
ERROR_LOG(m_logger) << "could not find informations for struct " << tti.name;
continue;
}
DataTopicWrapper *dtw = dataTopicsWrapper->addDataTopic(ctti);
getSpecialColumns(ctti, keyColumns, trColumns);
if (keyColumns.size() == 1)
dtw->setKeyColumnData(keyColumns.first());
dtw->setTrColumns(trColumns);
if (ctti.recordTopic)
{
const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name));
if (!cti.isEmpty())
dtw->setRecordTableInformation(cti);
}
else
{
if (ctti.isDataObject)
m_structBuilder->buildJSonStructureInformationFromIdlData(ctti);
}
}
m_ctx->insert(QStringLiteral("dataTopics"), dataTopicsWrapper);
QSet<QString> exportedSubTopics;
DataTopicsWrapper *subTopicsWrapper = new DataTopicsWrapper(QString(), this);
const QVector<QSharedPointer<DataTypeInfo>> serializeTopics = m_structBuilder->getSerializeFromToJsonTopics();
for (const QSharedPointer<DataTypeInfo> &dti : serializeTopics)
{
if (exportedSubTopics.contains(dti->idlType()))
continue;
if (dti->isStructure())
{
const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(dti->idlType());
if (!ctti.isValid())
{
ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType();
continue;
}
const CompleteTableInformation cti(m_structBuilder->getRecordingTopicTable(ctti.name));
if (cti.isEmpty())
{
ERROR_LOG(m_logger) << "could not find informations for struct " << dti->idlType();
continue;
}
DEBUG_LOG(m_logger) << "Adding substruct topic " << ctti.name;
DataTopicWrapper *dtw = subTopicsWrapper->addDataTopic(ctti);
dtw->setRecordTableInformation(cti);
exportedSubTopics += dti->idlType();
}
}
m_ctx->insert(QStringLiteral("serializeTopics"), subTopicsWrapper);
QVector<MemberInformationListWrapper *> jsonTopics;
const QMap<QString, QVector<MemberInformation>> jsonExport = m_structBuilder->jsonStructureInformation();
for (auto it = jsonExport.begin(); it != jsonExport.end(); ++it)
{
MemberInformationListWrapper *mil = new MemberInformationListWrapper(it.key(), it.value(), this);
jsonTopics += mil;
}
m_ctx->insert(QStringLiteral("jsonTopics"), QVariant::fromValue(jsonTopics));
return true;
}
bool CppTopicGeneratorGrantlee::getSpecialColumns(const CompleteTopicTypeInformation &tti,
QVector<IdlData> &keyColumns,
QVector<IdlData> &trColumns) const
{
DEBUG_LOG(m_logger) << "Get special columns for " << tti.name << " - base is " << tti.baseValueType;
if (!tti.isValid())
{
ERROR_LOG(m_logger) << "tti is not valid ...";
return false;
}
for (const IdlData &ci : tti.members)
{
if (ci.isKey)
keyColumns += ci;
if (ci.isTranslatable)
trColumns += ci;
}
if (tti.baseValueType.isEmpty())
return true;
return getSpecialColumns(topicTypeInformationByDerivedTopic(tti), keyColumns, trColumns);
}
/*
* FunctionTopics -> FunctionTopicWrapper
*/
bool CppTopicGeneratorGrantlee::initFunctionTopics()
{
//INFO_LOG(m_logger) << "IN CppFunctionTopicInterfaceGenerator:init()";
const QVector<FunctionTopic> functionTopics = m_reader->getFunctionTopics();
FunctionTopicsWrapper *functionTopicsWrapper(new FunctionTopicsWrapper(QStringLiteral("functionTopics"), this));
for (const FunctionTopic &ft : functionTopics)
{
if (!handleStruct(ft, functionTopicsWrapper))
return false;
}
m_ctx->insert(QStringLiteral("functionTopics"), functionTopicsWrapper);
return true;
}
bool CppTopicGeneratorGrantlee::handleStruct(const FunctionTopic &ft,
FunctionTopicsWrapper *functionTopicsWrapper)
{
DEBUG_LOG(m_logger) << "handling FunctionTopic: " << ft.name;
const CompleteTopicTypeInformation siRequest = m_structBuilder->topicTypeInformationByStructType(ft.requestTopicType);
if (!siRequest.isValid())
{
WARNING_LOG(m_logger) << "Could not get topic informations about request topic "
<< ft.requestTopicName;
return false;
}
const CompleteTopicTypeInformation siReply = m_structBuilder->topicTypeInformationByStructType(ft.replyTopicType);
if (!siReply.isValid())
{
WARNING_LOG(m_logger) << "Could not get topic informations about reply topic "
<< ft.replyTopicType;
return false;
}
FunctionTopicWrapper *ftw = functionTopicsWrapper->addFunctionTopic(ft, siRequest, siReply);
const QByteArray hash = QCryptographicHash::hash(ft.requestTopicName.toUtf8(), QCryptographicHash::Sha3_512).toHex().left(16);
ftw->setReplyHandlerHash(QStringLiteral("0x") + QString::fromLatin1(hash) + QStringLiteral("LL"));
if (!addRequesterRequestParams(ft.name, siRequest, ftw) ||
!addRequesterReplyParams(ft.name, siReply, ftw))
return false;
return true;
}
bool CppTopicGeneratorGrantlee::addRequesterRequestParams(const QString &request_name,
const CompleteTopicTypeInformation &request,
FunctionTopicWrapper *ftw)
{
if (request.hasBaseValue())
{
const CompleteTopicTypeInformation siBase = m_structBuilder->topicTypeInformationByStructType(request.baseValueType);
if (!siBase.isValid())
{
WARNING_LOG(m_logger) << "Could not get topic informations about base request topic "
<< request.baseValueType;
return false;
}
if (!addRequesterRequestParams(request_name, siBase, ftw))
return false;
}
ftw->addRequestParams(request.members);
return true;
}
bool CppTopicGeneratorGrantlee::addRequesterReplyParams(const QString &request_name,
const CompleteTopicTypeInformation &reply,
FunctionTopicWrapper *ftw)
{
if (reply.hasBaseValue())
{
const CompleteTopicTypeInformation siBase = m_structBuilder->topicTypeInformationByStructType(reply.baseValueType);
if (!siBase.isValid())
{
WARNING_LOG(m_logger) << "Could not get topic informations about base reply topic "
<< reply.baseValueType;
return false;
}
if (!addRequesterReplyParams(request_name, siBase, ftw))
return false;
}
ftw->addReplyParams(reply.members);
return true;
}
/*
* IDL / DDS -> IdlStructWrapper
*/
bool CppTopicGeneratorGrantlee::handleIdlStruct(const CompleteTopicTypeInformation &csi,
QSet<QString> &idlStructNames,
QVector<IdlStructWrapper *> &idlStructs)
{
if (!csi.isValid())
return true;
// Struct already exported?
if (idlStructNames.contains(csi.name))
return true;
//INFO_LOG(m_logger) << "handle idlStruct: " << csi.name << "\t : " << csi.baseValueType;
// Is the baseStruct already exported & part of the current idl file?
if (csi.hasBaseValue())
{
const CompleteTopicTypeInformation ctti = m_structBuilder->topicTypeInformationByStructType(csi.baseValueType, false);
if (ctti.isValid())
{
if (ctti.extensibility == dds::core::xtypes::ExtensibilityKind_def::FINAL)
{
ERROR_LOG(m_logger) << "Base struct (" << ctti.name
<< ") has final extensibility - this is not allowed";
return false;
}
if (!idlStructNames.contains(csi.baseValueType))
{
if (!handleIdlStruct(ctti, idlStructNames, idlStructs))
return false;
}
}
else
{
const int idx = csi.baseValueType.indexOf(QLatin1String("::"));
if (idx < 0)
{
ERROR_LOG(m_logger) << "idlStruct '" << csi.baseValueType
<< "' not found in idl-file";
return false;
}
}
}
// Are parts of columns idlStructs?
for (const IdlData &idlData : csi.members)
{
QString idlType;
DEBUG_LOG(m_logger) << "member: " << idlData.type << " - " << idlData.name
<< " " << idlData.dataTypeInfo->isStructure()
<< " " << idlData.dataTypeInfo->isSequence();
if (idlData.dataTypeInfo->isStructure())
idlType = idlData.dataTypeInfo->idlType();
else if (idlData.dataTypeInfo->isSequence())
{
if (idlData.dataTypeInfo->innerDataType()->isStructure())
idlType = idlData.dataTypeInfo->innerDataType()->idlType();
}
if (idlType.isEmpty())
continue;
if (!handleIdlStruct(m_structBuilder->topicTypeInformationByStructType(idlType,
false),
idlStructNames,
idlStructs))
return false;
}
// Handle current struct
DEBUG_LOG(m_logger) << "Current structName: " << csi.name;
idlStructs += new IdlStructWrapper(csi, this);
// Add structName to list of exported idlStructs
idlStructNames.insert(csi.name);
return true;
}
bool CppTopicGeneratorGrantlee::initIdlStructs()
{
//INFO_LOG(m_logger) << "IN IdlGeneratorGrantlee:init()";
const QVector<CompleteTopicTypeInformation> csiData = m_structBuilder->topicTypeInformations();
QSet<QString> idlStructNames;
QVector<IdlStructWrapper *> idlStructs;
idlStructs.reserve(csiData.size());
for (const CompleteTopicTypeInformation &csi : csiData)
{
if (!handleIdlStruct(csi, idlStructNames, idlStructs))
return false;
}
// (!) No sorting of idlStruct since dependencies of idlStructs
m_ctx->insert(QStringLiteral("idl_structs"), QVariant::fromValue(idlStructs));
const QVector<EnumInformation> enumerations = m_structBuilder->enumInformations();
QVector<IdlEnumWrapper *> idlEnumerations;
idlEnumerations.reserve(enumerations.size());
for (const EnumInformation &ei : enumerations)
idlEnumerations += new IdlEnumWrapper(ei, this);
m_ctx->insert(QStringLiteral("idl_enums"), QVariant::fromValue(idlEnumerations));
// export the used services with the corresponding idl files
exportUsedServices(m_reader);
return true;
}
// Common
CompleteTopicTypeInformation CppTopicGeneratorGrantlee::topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation &tti) const
{
if (tti.baseValueType.isEmpty())
return CompleteTopicTypeInformation();
QString baseValueType = tti.baseValueType;
// if tti is from another service, baseValue is also from this other service
// except it's from another service, then baseValue has '::' in it's name
if (tti.name.contains(QLatin1String("::")) &&
!tti.baseValueType.contains(QLatin1String("::")))
{
// Add Service name to baseValueType name
const QVector<QStringRef> sl = tti.name.splitRef(QStringLiteral("::"));
baseValueType = sl.at(0) + QLatin1String("::") + tti.baseValueType;
}
DEBUG_LOG(m_logger) << "topicTypeInformationByStructName(" << baseValueType << ")";
return m_structBuilder->topicTypeInformationByStructType(baseValueType);
}
+53
View File
@@ -0,0 +1,53 @@
/**
* @RDE_HEADER@
* Generate an dtc/dts interface file from idl-struct defined in a xml-file
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2016-01-05
*/
#pragma once
#include "GeneratorGrantleeBase.h"
#include "StructBuilder.h"
#include "GrantleeWrappers.h"
#include "codegen_export.h"
namespace CodeGen
{
class CppTopicGeneratorGrantlee final : public GeneratorGrantleeBase
{
Q_OBJECT
public:
explicit CppTopicGeneratorGrantlee();
bool init(const XmlReader *reader, StructBuilder *structBuilder) override;
private:
// DataTopics
bool initDataTopics();
bool getSpecialColumns(const CompleteTopicTypeInformation &tti,
QVector<IdlData> &keyColumns,
QVector<IdlData> &trColumns) const;
// FunctionTopics
bool initFunctionTopics();
bool handleStruct(const FunctionTopic &ft,
FunctionTopicsWrapper *functionTopicsWrapper);
bool addRequesterRequestParams(const QString &request_name,
const CompleteTopicTypeInformation &request,
FunctionTopicWrapper *ftw);
bool addRequesterReplyParams(const QString &request_name,
const CompleteTopicTypeInformation &reply,
FunctionTopicWrapper *ftw);
// Idl
bool handleIdlStruct(const CompleteTopicTypeInformation &csi,
QSet<QString> &idlStructNames,
QVector<IdlStructWrapper *> &idlStructs);
bool initIdlStructs();
// Common
CompleteTopicTypeInformation topicTypeInformationByDerivedTopic(const CompleteTopicTypeInformation &tti) const;
const XmlReader *m_reader = nullptr;
};
} // namespace CodeGen
+62
View File
@@ -0,0 +1,62 @@
/**
* @RDE_HEADER@
* Generate cpp structs from xml
* $Id: DaoGeneratorGrantlee.cpp 50446 2020-03-18 10:29:57Z ehrlicher $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/DaoGeneratorGrantlee.cpp $
* $Author: ehrlicher $
*/
#include "DaoGeneratorGrantlee.h"
#include "StructBuilder.h"
#include <NGLLogger/INGLLogger.h>
#include <grantlee/context.h>
using namespace CodeGen;
DaoGeneratorGrantlee::DaoGeneratorGrantlee()
: GeneratorGrantleeBase("CodeGen.DaoGeneratorGrantlee")
{}
bool DaoGeneratorGrantlee::init(const XmlReader *reader,
StructBuilder *structBuilder)
{
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
return false;
// Is Service exercise-independent?
if (reader->isServiceExerciseIndependent())
m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1"));
bool bHasDbNotifications = false;
QVector<DbStructWrapper *> vlServiceStructs;
QVector<DbStructWrapper *> vlStaticDataStructs;
const QVector<CompleteStructInformation> csiData = structBuilder->structInformations();
vlServiceStructs.reserve(csiData.size());
vlStaticDataStructs.reserve(csiData.size());
for (const CompleteStructInformation &csi : csiData)
{
DbStructWrapper *dbStructWrapper(new DbStructWrapper(csi, this));
if (csi.hasStaticContent)
vlStaticDataStructs += dbStructWrapper;
else
vlServiceStructs += dbStructWrapper;
if (!csi.dbNotifications.isEmpty())
bHasDbNotifications = true;
}
if (structBuilder->needPoint3d())
m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1"));
if (structBuilder->needPgArrayInclude())
m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1"));
m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs));
m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs));
if (bHasDbNotifications)
m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1"));
m_ctx->insert(QStringLiteral("library_target"), QStringLiteral("COMMON"));
exportUsedServices(reader);
return true;
}
+30
View File
@@ -0,0 +1,30 @@
/**
* @RDE_HEADER@
* Generate cpp structs from xml
* $Id: DaoGeneratorGrantlee.h 50446 2020-03-18 10:29:57Z ehrlicher $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/DaoGeneratorGrantlee.h $
* $Author: ehrlicher $
*/
#pragma once
#include "GeneratorGrantleeBase.h"
#include "StructBuilder.h"
#include "GrantleeWrappers.h"
#include "codegen_export.h"
namespace CodeGen
{
struct CompleteStructInformation;
class StructBuilder;
class XmlReader;
class DaoGeneratorGrantlee final : public GeneratorGrantleeBase
{
Q_OBJECT
public:
explicit DaoGeneratorGrantlee();
bool init(const XmlReader *reader, StructBuilder *structBuilder) override;
};
} // namespace CodeGen
+64
View File
@@ -0,0 +1,64 @@
/**
* @RDE_HEADER@
* Generate cpp structs from xml
* $Id$
* $HeadURL$
* $Author$
*/
#include "DatabaseGeneratorGrantlee.h"
#include "StructBuilder.h"
#include <NGLLogger/INGLLogger.h>
#include <grantlee/context.h>
using namespace CodeGen;
DatabaseGeneratorGrantlee::DatabaseGeneratorGrantlee(bool targetIsServer)
: GeneratorGrantleeBase("CodeGen.CppStructGeneratorGrantlee")
, m_targetIsServer(targetIsServer)
{}
bool DatabaseGeneratorGrantlee::init(const XmlReader *reader,
StructBuilder *structBuilder)
{
if (!GeneratorGrantleeBase::initInternal(reader, structBuilder))
return false;
// Is Service exercise-independent?
if (reader->isServiceExerciseIndependent())
m_ctx->insert(QStringLiteral("isExerciseIndependent"), QLatin1String("1"));
bool bHasDbNotifications = false;
QVector<DbStructWrapper *> vlServiceStructs;
QVector<DbStructWrapper *> vlStaticDataStructs;
const QVector<CompleteStructInformation> csiData = structBuilder->structInformations();
vlServiceStructs.reserve(csiData.size());
vlStaticDataStructs.reserve(csiData.size());
for (const CompleteStructInformation &csi : csiData)
{
DbStructWrapper *dbStructWrapper(new DbStructWrapper(csi, this));
if (csi.hasStaticContent)
vlStaticDataStructs += dbStructWrapper;
else
vlServiceStructs += dbStructWrapper;
if (!csi.dbNotifications.isEmpty())
bHasDbNotifications = true;
}
addCustomFunctions(reader->getCustomFunctions());
if (structBuilder->needPoint3d())
m_ctx->insert(QStringLiteral("need_point3d"), QLatin1String("1"));
if (structBuilder->needPgArrayInclude())
m_ctx->insert(QStringLiteral("need_pgarray"), QLatin1String("1"));
m_ctx->insert(QStringLiteral("cpp_structs"), QVariant::fromValue(vlServiceStructs));
m_ctx->insert(QStringLiteral("data_structs"), QVariant::fromValue(vlStaticDataStructs));
if (bHasDbNotifications)
m_ctx->insert(QStringLiteral("has_db_notifications"), QLatin1String("1"));
m_ctx->insert(QStringLiteral("library_target"), m_targetIsServer ? QStringLiteral("SERVER") : QStringLiteral("COMMON"));
exportUsedServices(reader);
return true;
}
+32
View File
@@ -0,0 +1,32 @@
/**
* @RDE_HEADER@
* Generate cpp structs from xml
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include "GeneratorGrantleeBase.h"
#include "StructBuilder.h"
#include "GrantleeWrappers.h"
#include "codegen_export.h"
namespace CodeGen
{
struct CompleteStructInformation;
class StructBuilder;
class XmlReader;
class DatabaseGeneratorGrantlee final : public GeneratorGrantleeBase
{
Q_OBJECT
public:
explicit DatabaseGeneratorGrantlee(bool targetIsServer);
bool init(const XmlReader *reader, StructBuilder *structBuilder) override;
private:
bool m_targetIsServer;
};
} // namespace CodeGen
+205
View File
@@ -0,0 +1,205 @@
/**
* @RDE_HEADER@
* Generate enum access functions (static enums, values from xml-file)
* $Id$
* $HeadURL$
* $Author$
*/
#include "GeneratorGrantleeBase.h"
#include "XmlReader.h"
#include "GrantleeWrappers.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QFile>
#include <grantlee/context.h>
#include <grantlee/engine.h>
using namespace CodeGen;
GeneratorGrantleeBase::GeneratorGrantleeBase(const char *loggerName)
: m_engine(new Grantlee::Engine)
, m_ctx(new Grantlee::Context)
, m_structBuilder(nullptr)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName))
{
}
GeneratorGrantleeBase::~GeneratorGrantleeBase()
{
delete m_engine;
delete m_ctx;
}
bool GeneratorGrantleeBase::initInternal(const XmlReader *reader, StructBuilder *structBuilder)
{
m_structBuilder = structBuilder;
if (reader)
{
m_ctx->insert(QStringLiteral("namespace"), reader->getServiceNamespace());
m_ctx->insert(QStringLiteral("namespace_abbr"), reader->getServiceAbbr().toLower());
m_ctx->insert(QStringLiteral("namespace_abbr_upper"), reader->getServiceAbbr().toUpper());
if (reader->getServiceNamespace() == QLatin1String("Core"))
m_ctx->insert(QStringLiteral("include_prefix"), QStringLiteral("DDS/%1").arg(reader->getServiceNamespace()));
else
m_ctx->insert(QStringLiteral("include_prefix"), reader->getServiceNamespace());
m_ctx->insert(QStringLiteral("custom_record_replay"), reader->hasCustomRecordReplayPlugin());
}
return true;
}
bool GeneratorGrantleeBase::setTemplate(const QString &templateName, const QString &tmpl)
{
m_template = m_engine->newTemplate(tmpl, QStringLiteral("myTemplate"));
if (m_template->error())
{
WARNING_LOG(m_logger) << "Error parsing template '" << templateName
<< "': " << m_template->errorString();
return false;
}
return true;
}
QString GeneratorGrantleeBase::render() const
{
QString out = m_template->render(m_ctx);
QRegExp rx(QLatin1String(";\\n([ ]+\\n)*"));
out = out.replace(rx, QStringLiteral(";\n"));
return out;
}
QString GeneratorGrantleeBase::errorString() const
{
return m_template->errorString();
}
bool GeneratorGrantleeBase::generateData(const QString &templateName,
const QString &outDirectory)
{
QString content;
if (!readFile(templateName, content))
return false;
if (!setTemplate(templateName, content))
return false;
content = render();
QString outFilename;
if (!getFilenameFromData(content, outFilename))
return false;
//INFO_LOG(m_logger) << "outfilename: " << outFilename;
QFile f(outDirectory + QLatin1Char('/') + outFilename);
if (!f.open(QIODevice::WriteOnly))
{
ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: "
<< f.errorString() << "(" << f.error() << ")";
return false;
}
const QByteArray outData = content.toUtf8();
f.write(outData);
f.close();
DEBUG_LOG(m_logger) << "Written data to " << f.fileName();
return true;
}
bool GeneratorGrantleeBase::readFile(const QString &filename, QString &data) const
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
{
ERROR_LOG(m_logger) << "Error opening file " << filename
<< " - error: " << f.errorString();
return false;
}
data = QString::fromUtf8(f.readAll());
f.close();
return true;
}
bool GeneratorGrantleeBase::getFilenameFromData(const QString &dataToParse, QString &filename) const
{
// scan for '// filename:
const QString filenameMarker(QStringLiteral("// filename:"));
int idx = dataToParse.indexOf(filenameMarker);
if (idx < 0)
{
ERROR_LOG(m_logger) << "Could not find marker '" << filenameMarker
<< "' in data!";
return false;
}
idx += filenameMarker.length() + 1;
int endIdx = dataToParse.indexOf(QLatin1Char('\n'), idx);
if (endIdx < 0)
{
ERROR_LOG(m_logger) << "Could not find end marker '\\n' in data!";
return false;
}
filename = dataToParse.midRef(idx, endIdx - idx).trimmed().toString();
//INFO_LOG(m_logger) << "found filename: '" << filename << "'";
return true;
}
void GeneratorGrantleeBase::addCustomFunctions(const QVector<CustomServiceFunctionInformation> &customFunctions)
{
// QVector<CustomFunctionWrapper*> - Abnfrage mittels {% if custom_dtc_procs %} geht dann nicht... ?
QVariantList vlCustomDbFunctions;
QVariantList vlCustomDtcFunctions;
vlCustomDbFunctions.reserve(customFunctions.size());
vlCustomDtcFunctions.reserve(customFunctions.size());
for (const CustomServiceFunctionInformation &csfi : customFunctions)
{
const QString signature(csfi.returnValue + QLatin1Char(' ') +
csfi.methodName + QLatin1Char('(') + csfi.signature + QLatin1Char(')') +
(csfi.isConst ? QStringLiteral(" const") : QString()));
CustomFunctionWrapper *customFunctionWrapper(new CustomFunctionWrapper(signature, this));
switch (csfi.type)
{
case CustomServiceFunctionInformation::Type::Db:
vlCustomDbFunctions += QVariant::fromValue(customFunctionWrapper);
break;
case CustomServiceFunctionInformation::Type::Dtc:
vlCustomDtcFunctions += QVariant::fromValue(customFunctionWrapper);
break;
case CustomServiceFunctionInformation::Type::DbNotify:
// should not occour here - handled in addDbNotifications()
WARNING_LOG(m_logger) << "Internal error - Trying to handle DbNotify in addCustomFunctions(): "
<< csfi.methodName;
delete customFunctionWrapper;
break;
}
DEBUG_LOG(m_logger) << "Added custom db function " << csfi.methodName;
}
m_ctx->insert(QStringLiteral("custom_dtc_procs"), vlCustomDtcFunctions);
m_ctx->insert(QStringLiteral("custom_db_procs"), vlCustomDbFunctions);
}
void GeneratorGrantleeBase::exportUsedServices(const XmlReader *reader)
{
IdlServicesWrapper *idlServicesWrapper(new IdlServicesWrapper(QString(), this));
const QMap<QString, bool> usedServices = reader->usedServices();
for (auto it = usedServices.begin(); it != usedServices.end(); ++it)
{
// loaded, but not used
if (!it.value())
continue;
const QString &svc = it.key();
const QString fullPath = reader->idlFileForService(svc);
const int idx = fullPath.lastIndexOf(QLatin1Char('.'));
if (idx < 0)
{
ERROR_LOG(m_logger) << "Could not parse file path: " << fullPath;
continue;
}
const QString idlFile = fullPath.left(idx);
if (idlFile.isEmpty())
{
WARNING_LOG(m_logger) << "Could not find idl file for service " << svc;
continue;
}
idlServicesWrapper->addService(svc, idlFile);
DEBUG_LOG(m_logger) << "Added service " << svc;
}
m_ctx->insert(QStringLiteral("ext_services"), idlServicesWrapper);
}
+58
View File
@@ -0,0 +1,58 @@
/**
* @RDE_HEADER@
* Generate enum access functions (static enums, values from xml-file)
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include "codegen_export.h"
#include "XmlReader.h"
#include <QObject>
#include <QSharedPointer>
namespace NGL
{
class INGLLogger;
}
namespace Grantlee
{
class Context;
class Engine;
class TemplateImpl;
using Template = QSharedPointer<TemplateImpl>;
}
namespace CodeGen
{
class StructBuilder;
class GeneratorGrantleeBase : public QObject
{
Q_OBJECT
public:
virtual ~GeneratorGrantleeBase();
virtual bool init(const XmlReader *reader, StructBuilder *structBuilder) = 0;
bool setTemplate(const QString &templateName, const QString &tmpl);
QString render() const;
QString errorString() const;
bool generateData(const QString &templateName,
const QString &outDirectory);
protected:
explicit GeneratorGrantleeBase(const char *loggerName);
bool initInternal(const XmlReader *reader, StructBuilder *structBuilder);
bool readFile(const QString &filename, QString &data) const;
bool getFilenameFromData(const QString &dataToParse, QString &filename) const;
void addCustomFunctions(const QVector<CustomServiceFunctionInformation> &customFunctions);
void exportUsedServices(const XmlReader *reader);
protected:
Grantlee::Engine *m_engine;
Grantlee::Context *m_ctx;
Grantlee::Template m_template;
StructBuilder *m_structBuilder;
NGL::INGLLogger &m_logger;
};
} // namespace CodeGen
+243
View File
@@ -0,0 +1,243 @@
/**
* @RDE_HEADER@
* Wrapper-Objects for Grantlee engine
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-08-04
*/
#include "GrantleeWrappers.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
using namespace CodeGen;
QString IdlStructWrapper::extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity)
{
switch (extensiblity.underlying())
{
case dds::core::xtypes::ExtensibilityKind_def::FINAL:
return QStringLiteral("FINAL_EXTENSIBILITY");
case dds::core::xtypes::ExtensibilityKind_def::MUTABLE:
return QStringLiteral("MUTABLE_EXTENSIBILITY");
case dds::core::xtypes::ExtensibilityKind_def::EXTENSIBLE:
return QStringLiteral("EXTENSIBLE_EXTENSIBILITY");
}
Q_ASSERT_X(false, "IdlStructWrapper::extensibility2String", "Unknown extensiblity kind");
return QString();
}
QString DataTopicWrapper::typeFq() const
{
const QString &t = type();
const QString &s = serviceName();
if (s.isEmpty())
return t;
if (t.startsWith(s))
return t;
return s + QStringLiteral("::") + t;
}
QString MemberInformationWrapper::ddsToQVariant() const
{
return QStringLiteral("toQVariant(data.") + cppMemberName() + QStringLiteral("())");
}
QString MemberInformationWrapper::qVariantToDds() const
{
const QString ddsType = isOptional() ?
(QStringLiteral("dds::core::optional<") + cppDdsType() + QLatin1Char('>')) :
(cppDdsType());
return QStringLiteral("data.") + cppMemberName() +
QStringLiteral("() = fromQVariant<") + ddsType + QStringLiteral(">(q.value(i++))");
}
QString MemberInformationWrapper::ddsToJsonElement() const
{
// POD
// obj[QStringLiteral("inner")] = element;
// mandatory
// obj[QStringLiteral("vulnerabilityState")] = element.vulnerabilityState().underlying();
// optional
// if (element.chargeTypeId().is_set()) obj[QStringLiteral("chargeTypeId")] = element.chargeTypeId().get();
if (cppMemberName().isEmpty())
return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;");
if (!isOptional())
return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") +
cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + QStringLiteral("()") + cppToJsonPost();
return QStringLiteral("if (element.") + cppMemberName() + QStringLiteral("().is_set()) ") +
QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") +
cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + QStringLiteral("().get()") + cppToJsonPost();
}
QString MemberInformationWrapper::jsonElementToDds() const
{
// POD
// element = objElement.value(QStringLiteral("inner")).toInt();
// common (mandatory and optional)
// const QJsonValue chargeTypeId(objElement.value(QStringLiteral("chargeTypeId")));
// mandatory
// element.vulnerabilityId(vulnerabilityId.toInt());
// optional
// if (!chargeTypeId.isNull()) element.chargeTypeId(chargeTypeId.toInt());
// POD?
if (cppMemberName().isEmpty())
return QStringLiteral("element = ") +
cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") + cppType() + QStringLiteral("\"))") + cppFromJsonPost();
QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() + QStringLiteral("\"))); "));
if (isOptional())
ret += QStringLiteral("if (!val.isNull()) ");
ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral("(") +
cppFromJsonPre2() + QStringLiteral("val") + cppFromJsonPost() + QStringLiteral(")");
return ret;
}
QString MemberInformationWrapper::dbToJsonElement() const
{
// POD
// obj[QStringLiteral("inner")] = element;
// mandatory
// obj[QStringLiteral("vulnerabilityId")] = element.vulnerabilityId.toInt());
// optional
// if (!element.chargeTypeId.isNull()) obj[QStringLiteral("chargeTypeId")] = element.chargeTypeId().toInt();
if (cppMemberName().isEmpty())
return QStringLiteral("obj[QStringLiteral(\"") + cppType() + QStringLiteral("\")] = element;");
if (isOptional() || cppType() == QStringLiteral("QDateTime"))
{
QString ret = QStringLiteral("if (") + ((cppType() == QStringLiteral("QDateTime")) ? QString() : QStringLiteral("!")) +
QStringLiteral("element.") + cppMemberName() +
((cppType() == QStringLiteral("QDateTime")) ? QStringLiteral(".isValid()) ") : QStringLiteral(".isNull()) ")) +
QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ");
if (isOptional())
return ret += cppFromQvPre2() + QStringLiteral("element.") + cppMemberName() + cppFromQvPost();
}
return QStringLiteral("obj[QStringLiteral(\"") + cppMemberName() + QStringLiteral("\")] = ") +
cppToJsonPre() + QStringLiteral("element.") + cppMemberName() + cppToJsonPost();
}
QString MemberInformationWrapper::jsonElementToDb() const
{
// POD
// element = objElement.value(QStringLiteral("inner")).toInt();
// common (mandatory and optional)
// const QJsonValue val(objElement.value(QStringLiteral("chargeTypeId")));
// mandatory
// element.vulnerabilityId = (val.toInt());
// optional
// if (!chargeTypeId.isNull()) element.chargeTypeId(chargeTypeId.toInt());
// POD?
if (cppMemberName().isEmpty())
return QStringLiteral("element = ") +
cppFromQvPre2() + QStringLiteral("objElement.value(QStringLiteral(\"") + cppType() + QStringLiteral("\"))") + cppFromJsonPost();
QString ret(QStringLiteral("const QJsonValue val(objElement.value(QStringLiteral(\"") + cppMemberName() + QStringLiteral("\"))); "));
if (isOptional())
ret += QStringLiteral("if (!val.isUndefined()) ");
ret += QStringLiteral("element.") + cppMemberName() + QStringLiteral(" = ") +
cppFromJsonPre2() + QStringLiteral("val") + cppFromJsonPost();
return ret;
}
QVariantList DbStructWrapper::insertStructures()
{
// todo: only buildup once
QStringList tableNames; // must be sorted
QMap<QString, QVector<MemberInformationWrapper *>> structures;
QMap<QString, MemberInformationWrapper *> primaryKeys;
QMap<QString, MemberInformationWrapper *> fkComplexTableKeys;
// silce clazy...
tableNames.reserve(5);
for (const QVariant &v : qAsConst(m_member))
{
MemberInformationWrapper *mi = v.value<MemberInformationWrapper *>();
const QString &tableName = mi->info().db.table;
if (mi->info().db.usedForInsert)
{
auto &tnStructure = structures[tableName];
tnStructure.append(mi);
if (!tableNames.contains(tableName))
tableNames.push_back(tableName);
}
if (mi->info().fk)
fkComplexTableKeys[mi->info().fk->destination.tableName] = mi;
if (mi->info().db.isPrimaryKey)
{
if (!primaryKeys.contains(tableName))
primaryKeys[tableName] = mi;
}
if (mi->info().db.isAssignmentForPrimaryTablePk)
fkComplexTableKeys[mi->info().db.table] = mi;
}
/*
* When a dbstruct is built from more than one table, we need to propagate
* the sequence ids from the base to the derived class:
* ppm_aggregate_entity : ppm_participant : ppm_entity
* ppm_participant.entity_id = ppm_entity.id
* ppm_aggregate_entity.participant_id = ppm_participant.id
* In the resulting structure we've 4 members:
* id -> ppm_entity.id
* entity_id -> ppm_entity.id
* participant_id -> ppm_participant.id
* aggregate_entity_id -> ppm_aggregate_entity.id
* Therefore we have to know the pk and corresponding fk in the single tables
*/
QVariantList vl;
vl.reserve(tableNames.size());
for (int i = 0; i < tableNames.size(); ++i)
{
const QString &tn = tableNames.at(i);
MemberInformationWrapper *serialMember = fkComplexTableKeys.value(tn);
MemberInformationWrapper *baseId = nullptr;
MemberInformationWrapper *primaryKey = primaryKeys.value(tn);
if (i == 0 && tableNames.size() > 1)
baseId = primaryKeys.value(tn);
// for structs with one table, all three MemberInformationWrapper - ptr
// are equal
if (tableNames.size() == 1)
{
baseId = nullptr; // should be null - otherwise we generate two assignments with the same values which is superfluous
// there are also primary keys without a serial on it
if (!primaryKey)
{
NGL::INGLLogger &logger = NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("DbStructWrapper");
WARNING_LOG(logger) << "Table " << tn << " has no primary key!";
}
else
{
if (primaryKey->dbHasSequence())
serialMember = primaryKey;
}
}
const QVector<MemberInformationWrapper *> members = structures.value(tn);
TableWrapper *tw = new TableWrapper(tn, members, serialMember, baseId, primaryKey, this);
vl += QVariant::fromValue(tw);
}
return vl;
}
+649
View File
@@ -0,0 +1,649 @@
/**
* @RDE_HEADER@
* Wrapper-Objects for Grantlee engine
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-08-04
*/
#pragma once
#include <QObject>
#include <QString>
#include <QVariant>
#include "StructBuilder.h"
namespace CodeGen
{
class NameWrapper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name)
Q_PROPERTY(QString nameCc READ nameCc)
Q_PROPERTY(QString nameFlc READ nameFlc)
public:
NameWrapper(const QString &name, QObject *parent)
: QObject(parent)
, m_name(name)
{}
QString name() const { return m_name; }
QString nameCc() const { return StructBuilder::camelCaseString(name(), true); }
QString nameFlc() const { return StructBuilder::firstCharLowercase(nameCc()); }
private:
const QString m_name;
};
class VariantListWrapper : public NameWrapper
{
Q_OBJECT
Q_PROPERTY(QVariantList member READ member)
public:
using NameWrapper::NameWrapper;
~VariantListWrapper()
{
for (const QVariant &v : qAsConst(m_member))
{
auto ptr = v.value<QObject *>();
delete ptr;
}
}
QVariantList member() const { return m_member; }
protected:
QVariantList m_member;
};
// DB specific wrapper
class MemberInformationWrapper final : public QObject
{
Q_OBJECT
Q_PROPERTY(QString cppMemberName READ cppMemberName)
Q_PROPERTY(QString cppMemberNameCc READ cppMemberNameCc)
Q_PROPERTY(QString cppStructName READ cppStructName)
Q_PROPERTY(QString cppType READ cppType)
Q_PROPERTY(QString cppToQvPre READ cppToQvPre)
Q_PROPERTY(QString cppToQvPost READ cppToQvPost)
Q_PROPERTY(QString cppFromQvPre1 READ cppFromQvPre1)
Q_PROPERTY(QString cppFromQvPre2 READ cppFromQvPre2)
Q_PROPERTY(QString cppFromQvPost READ cppFromQvPost)
Q_PROPERTY(QString cppDefault READ cppDefault)
Q_PROPERTY(QString dbTableName READ dbTableName)
Q_PROPERTY(QString dbColumnName READ dbColumnName)
Q_PROPERTY(QString dbColumnNameView READ dbColumnNameView)
Q_PROPERTY(QString ddsToQVariant READ ddsToQVariant)
Q_PROPERTY(QString qVariantToDds READ qVariantToDds)
Q_PROPERTY(QString ddsToJsonElement READ ddsToJsonElement)
Q_PROPERTY(QString jsonElementToDds READ jsonElementToDds)
Q_PROPERTY(QString dbToJsonElement READ dbToJsonElement)
Q_PROPERTY(QString jsonElementToDb READ jsonElementToDb)
Q_PROPERTY(bool dbHasSequence READ dbHasSequence)
Q_PROPERTY(bool isOptional READ isOptional)
public:
MemberInformationWrapper(const MemberInformation &mi, QObject *parent)
: QObject(parent)
, m_mi(mi)
{}
QString cppMemberName() const { return m_mi.cpp.memberName; }
QString cppMemberNameCc() const { return StructBuilder::camelCaseString(m_mi.cpp.memberName, true); }
QString cppStructName() const { return m_mi.cpp.structName; }
QString cppType() const { return m_mi.cpp.type; }
QString cppToQvPre() const { return m_mi.cpp.toVariantPre; }
QString cppToQvPost() const { return m_mi.cpp.toVariantPost; }
QString cppFromQvPre1() const { return m_mi.cpp.fromVariantPre1; }
QString cppFromQvPre2() const { return m_mi.cpp.fromVariantPre2; }
QString cppFromQvPost() const { return m_mi.cpp.fromVariantPost; }
QString cppDefault() const { return m_mi.cpp.defaultValue; }
QString dbTableName() const { return m_mi.db.table; }
QString dbColumnName() const { return m_mi.db.columnName; }
QString dbColumnNameView() const { return m_mi.db.getColumnNameView(); }
QString cppDdsType() const { return m_mi.cpp.ddsType.isEmpty() ? cppType() : m_mi.cpp.ddsType; }
QString ddsToQVariant() const; // dds type to QVariant (used vars: data)
QString qVariantToDds() const; // QVariant to dds type (used vars: data, q, i)
QString ddsToJsonElement() const; // dds type to json (used vars: obj, element)
QString jsonElementToDds() const; // json to dds type (used vars: objElement, element)
QString dbToJsonElement() const; // db struct to json (used vars: dbStruct, element)
QString jsonElementToDb() const; // json to db struct (used vars: objElement, element)
bool dbHasSequence() const { return m_mi.db.hasSequence; }
bool isOptional() const { return m_mi.cpp.isOptional; }
// only internally used
QString cppToJsonPre() const { return m_mi.cpp.toJsonPre.isNull() ? m_mi.cpp.toVariantPre : m_mi.cpp.toJsonPre; }
QString cppToJsonPost() const { return m_mi.cpp.toJsonPost.isNull() ? m_mi.cpp.toVariantPost : m_mi.cpp.toJsonPost; }
QString cppFromJsonPre2() const { return m_mi.cpp.fromJsonPre2.isNull() ? m_mi.cpp.fromVariantPre2: m_mi.cpp.fromJsonPre2; }
QString cppFromJsonPost() const { return m_mi.cpp.fromJsonPost.isNull() ? m_mi.cpp.fromVariantPost : m_mi.cpp.fromJsonPost; }
MemberInformation info() const
{
return m_mi;
}
private:
const MemberInformation m_mi;
};
class MemberInformationListWrapper : public VariantListWrapper
{
Q_OBJECT
Q_PROPERTY(QVariantList cppMember READ cppMember)
Q_PROPERTY(QVariantList jsonMember READ jsonMember)
Q_PROPERTY(QVariantList fkMember READ fkMember)
Q_PROPERTY(QVariantList selectRemoveMember READ selectRemoveMember)
Q_PROPERTY(QVariantList viewMember READ viewMember)
Q_PROPERTY(MemberInformationWrapper *ddsKey READ ddsKey)
Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey)
public:
explicit MemberInformationListWrapper(const QString &name, const QVector<MemberInformation> &member, QObject *parent)
: VariantListWrapper(name, parent)
{
for (const MemberInformation &mi : member)
m_member += QVariant::fromValue(new MemberInformationWrapper(mi, this));
}
// getters
QVariantList memberByFilter(const std::function<bool(const MemberInformation &mi)> &acceptFilter) const
{
QVariantList vl;
vl.reserve(m_member.size());
for (const QVariant &v : m_member)
{
MemberInformationWrapper *mi = v.value<MemberInformationWrapper *>();
if (mi && acceptFilter(mi->info()))
vl += v;
}
return vl;
}
QVariantList cppMember() const
{
return memberByFilter([](const MemberInformation &mi) { return mi.cpp.usedForCppStruct; });
}
QVariantList jsonMember() const
{
return memberByFilter([](const MemberInformation &mi) { return mi.cpp.usedForJson; });
}
QVariantList fkMember() const
{
return memberByFilter([](const MemberInformation &mi) { return mi.db.isForeignKey; });
}
QVariantList selectRemoveMember() const
{
return memberByFilter([](const MemberInformation &mi) { return mi.db.generateSelectRemove; });
}
QVariantList viewMember() const
{
return memberByFilter([](const MemberInformation &mi) { return mi.db.usedForView; });
}
MemberInformationWrapper *ddsKey() const
{
for (const QVariant &v : m_member)
{
MemberInformationWrapper *mi = v.value<MemberInformationWrapper *>();
if (mi->info().isDdsKey)
return mi;
}
return nullptr;
}
MemberInformationWrapper *primaryKey() const
{
for (const QVariant &v : m_member)
{
MemberInformationWrapper *mi = v.value<MemberInformationWrapper *>();
if (mi->info().db.isPrimaryKey)
return mi;
}
return nullptr;
}
};
class TableWrapper final : public NameWrapper
{
Q_OBJECT
Q_PROPERTY(QVector<MemberInformationWrapper *> member READ member)
// for insert: data.fooId = m_query->lastInsertId().toFoo();
Q_PROPERTY(MemberInformationWrapper *serialMember READ serialMember)
// for insert: data.id = m_query->lastInsertId().toFoo();
// -> may be different to serialMember when more than one table is involved
Q_PROPERTY(MemberInformationWrapper *baseId READ baseId)
// for update: m_query->bindValue(QLatin1String(":id"), data.fooId)
Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey)
public:
// does not take ownership!
explicit TableWrapper(const QString &name,
const QVector<MemberInformationWrapper *> &member,
MemberInformationWrapper *serialMember,
MemberInformationWrapper *baseId,
MemberInformationWrapper *primaryKey,
QObject *parent)
: NameWrapper(name, parent)
, m_member(member)
, m_serialMember(serialMember)
, m_baseId(baseId)
, m_primaryKey(primaryKey)
{}
QVector<MemberInformationWrapper *> member() const { return m_member; }
MemberInformationWrapper *serialMember() const { return m_serialMember; }
MemberInformationWrapper *baseId() const { return m_baseId; }
MemberInformationWrapper *primaryKey() const { return m_primaryKey; }
private:
QVector<MemberInformationWrapper *> m_member;
MemberInformationWrapper *m_serialMember;
MemberInformationWrapper *m_baseId;
MemberInformationWrapper *m_primaryKey;
};
class DbStructWrapper final : public MemberInformationListWrapper
{
Q_OBJECT
Q_PROPERTY(QVariantList insertStructures READ insertStructures)
Q_PROPERTY(QString viewTableName READ viewTableName)
Q_PROPERTY(bool deleteWithoutPkAllowed READ deleteWithoutPkAllowed)
Q_PROPERTY(MemberInformationWrapper *primaryKey READ primaryKey)
Q_PROPERTY(QVariantList dbNotifications READ dbNotifications)
public:
explicit DbStructWrapper(const CompleteStructInformation &csi, QObject *parent)
: MemberInformationListWrapper(csi.structName, csi.member, parent)
, m_csi(csi)
{
m_dbNotifications.reserve(m_csi.dbNotifications.size());
for (const auto &notification : qAsConst(m_csi.dbNotifications))
m_dbNotifications += QVariant(notification.methodName);
}
MemberInformationWrapper *primaryKey() const
{
for (const QVariant &v : m_member)
{
MemberInformationWrapper *mi = v.value<MemberInformationWrapper *>();
if (mi->info().db.isPrimaryKey && mi->info().db.table == primaryTableName())
return mi;
}
return nullptr;
}
QVariantList insertStructures();
bool deleteWithoutPkAllowed() const { return m_csi.tables.size() == 1; }
QVariantList dbNotifications() const { return m_dbNotifications; }
QString primaryTableName() const { return m_csi.primaryTable->name; }
QString viewTableName() const { return (m_csi.tables.size() == 1) ? primaryTableName() : m_csi.viewName; }
private:
const CompleteStructInformation m_csi;
QVariantList m_dbNotifications;
};
class CustomFunctionWrapper final : public QObject
{
Q_OBJECT
Q_PROPERTY(QString signature READ signature)
public:
explicit CustomFunctionWrapper(const QString &signature, QObject *parent)
: QObject(parent)
, m_signature(signature)
{}
QString signature() const { return m_signature; }
private:
const QString m_signature;
};
// IDL / DDS specific wrapper
class IdlEnumKeyValueWrapper final : public QObject
{
Q_OBJECT
Q_PROPERTY(QString key READ key)
Q_PROPERTY(int value READ value)
public:
explicit IdlEnumKeyValueWrapper(const EnumKeyValuePair &ekvp, QObject *parent)
: QObject(parent)
, m_ekvp(ekvp)
{}
QString key() const { return m_ekvp.key; }
int value() const { return m_ekvp.value; }
private:
const EnumKeyValuePair m_ekvp;
};
class IdlDataWrapper final : public NameWrapper
{
Q_OBJECT
Q_PROPERTY(QString type READ type)
Q_PROPERTY(QString comment READ comment)
Q_PROPERTY(QString isKey READ isKey)
Q_PROPERTY(QString isOptional READ isOptional)
Q_PROPERTY(QString cppType READ cppType)
Q_PROPERTY(QString cppTypeCr READ cppTypeCr)
Q_PROPERTY(QString nameGetter READ nameGetter)
public:
IdlDataWrapper(const IdlData &data,
QObject *parent)
: NameWrapper(data.name, parent)
, m_data(data)
{}
QString type() const { return m_data.dataTypeInfo->idlType(); }
QString comment() const { return m_data.comment; }
QString isKey() const { return m_data.isKey ? QStringLiteral(" \t //@key") : QString(); }
QString isOptional() const { return m_data.isOptional ? QStringLiteral(" \t //@Optional") : QString(); }
QString cppType() const { return m_data.dataTypeInfo->cppType(); }
QString cppTypeCr() const { return m_data.dataTypeInfo->cppTypeConstRef(); }
QString nameGetter() const { return m_data.name + (m_data.isOptional ? QStringLiteral("().get") : QString()); }
private:
const IdlData m_data;
};
class IdlEnumWrapper final : public VariantListWrapper
{
Q_OBJECT
public:
explicit IdlEnumWrapper(const EnumInformation &ei, QObject *parent)
: VariantListWrapper(ei.enumName, parent)
{
m_member.reserve(ei.values.size());
for (const auto &val : ei.values)
m_member += QVariant::fromValue(new IdlEnumKeyValueWrapper(val, this));
}
};
class IdlStructWrapper final : public VariantListWrapper
{
Q_OBJECT
Q_PROPERTY(QString comment READ comment)
Q_PROPERTY(QString baseValueType READ baseValueType)
Q_PROPERTY(QString extensibility READ extensibility)
public:
explicit IdlStructWrapper(const CompleteTopicTypeInformation &ctti, QObject *parent)
: VariantListWrapper(ctti.name, parent)
, m_ctti(ctti)
{
m_member.reserve(m_ctti.members.size());
for (const auto &val : qAsConst(m_ctti.members))
m_member += QVariant::fromValue(new IdlDataWrapper(val, this));
}
QString comment() const { return m_ctti.comment; }
QString baseValueType() const { return m_ctti.baseValueType; }
QString extensibility() const { return extensibility2String(m_ctti.extensibility); }
private:
const CompleteTopicTypeInformation m_ctti;
static QString extensibility2String(dds::core::xtypes::ExtensibilityKind extensiblity);
};
class IdlServiceWrapper final : public QObject
{
Q_OBJECT
Q_PROPERTY(QString svcName READ svcName)
Q_PROPERTY(QString baseFilePath READ baseFilePath)
public:
IdlServiceWrapper(const QString &svcName, const QString &baseFilePath)
: m_svcName(svcName)
, m_baseFilePath(baseFilePath)
{}
QString svcName() const { return m_svcName; }
QString baseFilePath() const { return m_baseFilePath; }
private:
const QString m_svcName;
const QString m_baseFilePath;
};
class FunctionTopicWrapper final : public NameWrapper
{
Q_OBJECT
Q_PROPERTY(QString onReply READ onReply)
Q_PROPERTY(QString onRequest READ onRequest)
Q_PROPERTY(QString onError READ onError)
Q_PROPERTY(bool isCoreFt READ isCoreFt)
Q_PROPERTY(bool routeInReplay READ routeInReplay)
Q_PROPERTY(QString reply READ reply)
Q_PROPERTY(QString replyFlc READ replyFlc)
Q_PROPERTY(QString replyHandler READ replyHandler)
Q_PROPERTY(QString replyHandlerFlc READ replyHandlerFlc)
Q_PROPERTY(QString replyType READ replyType)
Q_PROPERTY(QString replyTypeFro READ replyTypeFro)
Q_PROPERTY(QVariantList replyParams READ replyParams)
Q_PROPERTY(QString replyHandlerHash READ replyHandlerHash WRITE setReplyHandlerHash)
Q_PROPERTY(QString request READ request)
Q_PROPERTY(QString requestFlc READ requestFlc)
Q_PROPERTY(QString requestHandler READ requestHandler)
Q_PROPERTY(QString requestHandlerFlc READ requestHandlerFlc)
Q_PROPERTY(QString requestType READ requestType)
Q_PROPERTY(QVariantList requestParams READ requestParams)
public:
FunctionTopicWrapper(const FunctionTopic &ft,
const CompleteTopicTypeInformation &siRequest,
const CompleteTopicTypeInformation &siReply,
QObject *parent)
: NameWrapper(ft.name, parent)
, m_ft(ft)
, m_request(siRequest)
, m_reply(siReply)
{}
QString onReply() const { return QLatin1String("on") + m_ft.replyTopicName; }
QString onRequest() const { return QLatin1String("on") + m_ft.requestTopicName; }
QString onError() const { return QLatin1String("on") + m_ft.name + QLatin1String("Error"); }
bool isCoreFt() const { return (m_ft.replyTopicType == QStringLiteral("Core::FunctionReply") || m_ft.replyTopicType == QStringLiteral("FunctionReply")); }
bool routeInReplay() const { return m_ft.routeInReplay; }
QString reply() const { return m_ft.replyTopicName; }
QString replyFlc() const { return StructBuilder::firstCharLowercase(reply()); }
QString replyHandler() const { return m_ft.replyTopicName + QLatin1String("Handler"); }
QString replyHandlerFlc() const { return StructBuilder::firstCharLowercase(replyHandler()); }
QString replyType() const { return m_reply.name; }
QString replyTypeFro() const { return isCoreFt() ? QStringLiteral("Core::FunctionReplyObject") : m_reply.name; }
QString request() const { return m_ft.requestTopicName; }
QString requestFlc() const { return StructBuilder::firstCharLowercase(request()); }
QString requestHandler() const { return m_ft.requestTopicName + QLatin1String("Handler"); }
QString requestHandlerFlc() const { return StructBuilder::firstCharLowercase(requestHandler()); }
QString requestType() const { return m_request.name; }
void addRequestParams(const QVector<IdlData> &params)
{
for (const auto &param : params)
m_requestParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
}
QVariantList requestParams() const
{
return m_requestParams;
}
void addReplyParams(const QVector<IdlData> &params)
{
for (const auto &param : params)
m_replyParams.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
}
QVariantList replyParams() const
{
return m_replyParams;
}
QString replyHandlerHash() const
{
return m_replyHandlerHash;
}
void setReplyHandlerHash(const QString &hash)
{
m_replyHandlerHash = hash;
}
private:
const FunctionTopic m_ft;
const CompleteTopicTypeInformation m_request;
const CompleteTopicTypeInformation m_reply;
QVariantList m_requestParams;
QVariantList m_replyParams;
QString m_replyHandlerHash;
};
class FunctionTopicsWrapper final : public VariantListWrapper
{
Q_OBJECT
public:
Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay)
using VariantListWrapper::VariantListWrapper;
FunctionTopicWrapper *addFunctionTopic(const FunctionTopic &ft,
const CompleteTopicTypeInformation &siRequest,
const CompleteTopicTypeInformation &siReply)
{
FunctionTopicWrapper *ret = new FunctionTopicWrapper(ft, siRequest, siReply, this);
m_member += QVariant::fromValue(ret);
return ret;
}
// getters
QVariantList topicsToRouteInReplay() const
{
QVariantList vl;
vl.reserve(m_member.size());
for (const QVariant &v : m_member)
{
FunctionTopicWrapper *ft = v.value<FunctionTopicWrapper *>();
if (ft->routeInReplay())
vl += v;
}
return vl;
}
};
class DataTopicWrapper final : public NameWrapper
{
Q_OBJECT
Q_PROPERTY(QString serviceName READ serviceName)
Q_PROPERTY(QString type READ type)
Q_PROPERTY(QString typeFq READ typeFq)
Q_PROPERTY(QVariant key READ key)
Q_PROPERTY(bool dtcImpl READ dtcImpl)
Q_PROPERTY(bool dtsImpl READ dtsImpl)
Q_PROPERTY(bool recordTopic READ recordTopic)
Q_PROPERTY(QVariant recTable READ recTable)
Q_PROPERTY(QVariantList trColumns READ trColumns)
Q_PROPERTY(bool hasTrColumns READ hasTrColumns)
Q_PROPERTY(bool routeToClient READ routeToClient)
Q_PROPERTY(bool routeToServer READ routeToServer)
Q_PROPERTY(bool routeInReplay READ routeInReplay)
Q_PROPERTY(bool autosubscribe READ isAutoSubscribe)
public:
DataTopicWrapper(const CompleteTopicTypeInformation &ctti, QObject *parent)
: NameWrapper(ctti.name, parent)
, m_ctti(ctti)
, m_keyData(nullptr)
, m_recordData(nullptr)
{}
void setKeyColumnData(const IdlData &data)
{
delete m_keyData;
m_keyData = new IdlDataWrapper(data, this);
}
void setTrColumns(const QVector<IdlData> &params)
{
for (const auto &param : params)
m_trColumns.push_back(QVariant::fromValue(new IdlDataWrapper(param, this)));
}
QVariantList trColumns() const
{
return m_trColumns;
}
bool hasTrColumns() const
{
return !m_trColumns.empty();
}
void setRecordTableInformation(const CompleteTableInformation &cti)
{
delete m_recordData;
m_recordData = new MemberInformationListWrapper(cti.name, cti.columns, this);
m_ctti.recordTopic = true; // mhhh
}
QString serviceName() const { return m_ctti.serviceName; }
QString type() const { return m_ctti.name; }
QString typeFq() const;
QVariant key() const { return m_keyData ? QVariant::fromValue(m_keyData) : QVariant(); }
bool dtsImpl() const { return m_ctti.dtsAutogen; }
bool dtcImpl() const { return m_ctti.dtcAutogen; }
bool recordTopic() const { return m_ctti.recordTopic && m_recordData; }
bool routeToClient() const { return m_ctti.routeToClient; }
bool routeToServer() const { return m_ctti.routeToServer; }
bool routeInReplay() const { return m_ctti.routeInReplay; }
bool isAutoSubscribe() const { return m_ctti.autosubscribe; }
QVariant recTable() const { return recordTopic() ? QVariant::fromValue(m_recordData) : QVariant(); }
private:
CompleteTopicTypeInformation m_ctti;
IdlDataWrapper *m_keyData;
MemberInformationListWrapper *m_recordData;
QVariantList m_trColumns;
};
class DataTopicsWrapper final : public VariantListWrapper
{
Q_OBJECT
public:
Q_PROPERTY(QVariantList topicsToRouteInReplay READ topicsToRouteInReplay)
Q_PROPERTY(QVariantList topicsToRouteToClient READ topicsToRouteToClient)
Q_PROPERTY(QVariantList topicsToRouteToServer READ topicsToRouteToServer)
using VariantListWrapper::VariantListWrapper;
DataTopicWrapper *addDataTopic(const CompleteTopicTypeInformation &ctti)
{
DataTopicWrapper *ret = new DataTopicWrapper(ctti, this);
m_member += QVariant::fromValue(ret);
return ret;
}
// getters
QVariantList memberByFilter(const std::function<bool(const DataTopicWrapper *dtw)> &acceptFilter) const
{
QVariantList vl;
vl.reserve(m_member.size());
for (const QVariant &v : m_member)
{
DataTopicWrapper *dtw = v.value<DataTopicWrapper *>();
if (dtw && acceptFilter(dtw))
vl += v;
}
return vl;
}
QVariantList topicsToRouteInReplay() const
{
return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeInReplay(); });
}
QVariantList topicsToRouteToClient() const
{
return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeToClient(); });
}
QVariantList topicsToRouteToServer() const
{
return memberByFilter([](const DataTopicWrapper *dtw) { return dtw->routeToServer(); });
}
};
class IdlServicesWrapper final : public VariantListWrapper
{
Q_OBJECT
public:
using VariantListWrapper::VariantListWrapper;
void addService(const QString &svcName, const QString &baseFilePath)
{
m_member += QVariant::fromValue(new IdlServiceWrapper(svcName, baseFilePath));
}
};
} // namespace CodeGen
+37
View File
@@ -0,0 +1,37 @@
#
# Hinweise für den CodeGen
#
# $Id$
# $HeadURL$
# $Author$
#
Erstellt: Mai 2022, Norbert Thoden
Der Aufruf des CodeGen kann z.B. so aussehen:
codegen -s /tmp/ppm_serviceORG.xml -i /shd/CTC/TOOLS/TDS/TDS-build/service_idl_files.txt -a dds_topics -o /tmp /shd/CTC/TOOLS/TDS/TDS-source/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee
Edit Juni 2023, Niklas Schmidt
Ebenso ist es nicht möglich einen Struct aus einem anderen Service(Service B) zu verwenden,
der in sich widerum einen anderen Struct aus dem anderen Service (Service B) beinhaltet.
Bsp:
Service A = Struct1{ int id,
Struct2 test
}
Service B = Struct2{
int id2,
Struct3 test
}
Service B = Struct3{
std::string test
}
Mögliche Diagnose:
Wenn Struct3 aus Service A aufgerufen wird, ist wahrscheinlich nicht mehr gekennzeichnet dass der Struct3 aus Service B kommt. Dieser kann somit nicht
aufgelöst werden
+948
View File
@@ -0,0 +1,948 @@
/**
* @RDE_HEADER@
* Generate SQL statements from Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#include "SqlGenerator.h"
#include "StructBuilder.h"
#include "XmlReader.h"
#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QStringList>
#include <NGLLogger/NGLLoggerBootstrap.h>
using namespace CodeGen;
// to avoid dependency to ngldb
static QString getExerciseSchemaName(int exerciseId)
{
return QStringLiteral("ex_") + QString::number(exerciseId);
}
SqlGenerator::SqlGenerator(XmlReader *xmlReader, StructBuilder *structBuilder)
: m_xmlReader(xmlReader)
, m_structBuilder(structBuilder)
, m_createdByXmlFile(false)
, m_isValid(true)
, m_exerciseId(0)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator"))
{}
SqlGenerator::SqlGenerator(const QString &xmlFile)
: m_xmlReader(new XmlReader)
, m_structBuilder(new StructBuilder(m_xmlReader))
, m_createdByXmlFile(true)
, m_isValid(false)
, m_exerciseId(0)
, m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.SqlGenerator"))
{
m_isValid = m_xmlReader->readServiceFile(xmlFile, QString()) && m_structBuilder->buildStructures();
}
SqlGenerator::~SqlGenerator()
{
if (m_createdByXmlFile)
{
delete m_xmlReader;
delete m_structBuilder;
}
}
bool SqlGenerator::generateData(int exerciseId)
{
DEBUG_LOG(m_logger) << "Generating SQL data for service " << getServiceAbbr() << " in exercise " << exerciseId << ", this: " << this;
m_exerciseId = exerciseId;
m_sqlGlobal = SqlStatements();
m_sqlExercise = SqlStatements();
m_sqlItemsGlobal = SqlItems();
m_sqlItemsExercise = SqlItems();
m_referencedSevices.clear();
if (!handleTables())
return false;
if (!handleViews())
return false;
if (!handleEnumerations())
return false;
return true;
}
bool SqlGenerator::createStatementsForTable(const QString &tableName, const QString &tableNameOut)
{
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
for (const CompleteTableInformation &cti : tables)
{
if (cti.name != tableName)
continue;
CompleteTableInformation ctiOut = cti;
ctiOut.name = tableNameOut;
return handleTable(ctiOut);
}
return false;
}
bool SqlGenerator::handleTables()
{
const QMap<QString, CompleteTableInformation> tables = m_structBuilder->getDbTables();
for (const CompleteTableInformation &cti : tables)
{
if (!handleTable(cti))
return false;
}
const QMap<QString, CompleteTableInformation> recTables = m_structBuilder->getRecordingTopicTables();
MemberInformation miColumnIsValid;
miColumnIsValid.db.columnName = QStringLiteral("dds_is_alive");
miColumnIsValid.db.datatype = QStringLiteral("boolean");
miColumnIsValid.db.defaultValue = QStringLiteral("true");
MemberInformation miInsertionTime;
miInsertionTime.cpp.type = QStringLiteral("timestamp");
miInsertionTime.db.columnName = QStringLiteral("insert_time");
miInsertionTime.db.defaultValue = QStringLiteral("now()");
miInsertionTime.db.usedForInsert = false;
m_structBuilder->fixupQVariantConversions(miInsertionTime);
ConstraintInformation ci;
ci.name = QStringLiteral("IDX_sample_time");
ci.affectedColumns = QStringList({QStringLiteral("sample_time")});
ci.type = ConstraintInformation::Type::index;
for (const CompleteTableInformation &cti : recTables)
{
CompleteTableInformation ctiTmp(cti);
ctiTmp.columns.push_back(miColumnIsValid);
ctiTmp.columns.push_back(miInsertionTime);
ctiTmp.constraints.push_back(ci);
if (!handleTable(ctiTmp))
return false;
}
return true;
}
bool SqlGenerator::handleViews()
{
const QMap<QString, ViewInformation> views = m_structBuilder->getDbViews();
for (const ViewInformation &vi : views)
{
if (!handleView(vi))
return false;
}
return true;
}
bool SqlGenerator::handleView(const ViewInformation &vi)
{
DEBUG_LOG(m_logger) << "Handle view " << vi.name;
const QString parent = vi.baseTable;
DEBUG_LOG(m_logger) << "Parent = " << parent;
if (!parent.isEmpty())
{
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(parent);
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
DEBUG_LOG(m_logger) << "csi.primaryTable = " << csi.primaryTable.data()->name;
if (!csi.primaryTable.isNull())
{
QStringList fromTables;
fromTables += vi.baseSchema+QLatin1Char('.')+vi.baseTable;
QStringList whereConditions;
if (vi.filter.contains(QLatin1String("exercise_id")))
whereConditions += QLatin1String("exercise_id=")+QString::number(m_exerciseId);
QStringList fqColumnNames;
// fqColumnNames += QLatin1String("*");
// buildup view create statement
for (const MemberInformation &mi : csi.member)
{
if (mi.db.usedForView)
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") + mi.db.getColumnNameView();
}
DEBUG_LOG(m_logger) << "fqColumnNames = " << fqColumnNames.join(QStringLiteral(", "));
if (!whereConditions.isEmpty())
addToCreateViews(createViewStmt.arg(csi.viewName,
fqColumnNames.join(QStringLiteral(", ")),
fromTables.join(QStringLiteral(", ")),
whereConditions.join(QStringLiteral(" AND\n"))),
vi.exerciseDependency);
addToDropViews(dropViewStmt.arg(csi.viewName), vi.exerciseDependency);
}
}
return true;
}
bool SqlGenerator::handleTable(const CompleteTableInformation &cti)
{
DEBUG_LOG(m_logger) << "Handle table " << cti.name;
if (cti.columns.isEmpty())
{
WARNING_LOG(m_logger) << "Table " << cti.name
<< " has no DatabaseServiceStruct - not generating anything for this!";
return true;
}
const QString dropTableStmt(QStringLiteral("DROP TABLE IF EXISTS %1 CASCADE;"));
const QString createTableStmt(QStringLiteral("CREATE TABLE IF NOT EXISTS %1 (%2\n);"));
const QString commentTableStmt(QStringLiteral("COMMENT ON TABLE %1 IS '%2';"));
QStringList tableComments;
addToDropTables(dropTableStmt.arg(cti.name), cti.exerciseDependency);
if ((cti.exerciseDependency & Global) == Global)
{
addToCreateTables(createTableStmt.arg(cti.name,
getTableColumnCreateStatements(cti.name,
cti.columns,
&tableComments,
Global)),
Global);
}
if ((cti.exerciseDependency & Exercise) == Exercise)
{
addToCreateTables(createTableStmt.arg(cti.name,
getTableColumnCreateStatements(cti.name,
cti.columns,
&tableComments,
Exercise)),
Exercise);
}
addToTables(cti.name, cti.exerciseDependency);
if (!cti.comment.isEmpty())
addToCreateTables(commentTableStmt.arg(cti.name, fixupComment(cti.comment)), cti.exerciseDependency);
addToCreateTables(tableComments, cti.exerciseDependency);
const QString parent = m_xmlReader->getStruct2BaseClassRelations().value(cti.name);
const CompleteStructInformation csi = m_structBuilder->structInformationByTableName(cti.name);
if (!parent.isEmpty())
{
const QString createViewStmt(QStringLiteral("CREATE OR REPLACE VIEW %1 AS SELECT %2\nFROM %3\nWHERE %4;\n"));
const QString dropViewStmt(QStringLiteral("DROP VIEW IF EXISTS %1;"));
if (!csi.primaryTable.isNull())
{
QStringList fqColumnNames;
QStringList fromTables;
QStringList whereConditions;
fromTables += cti.name;
// buildup view create statement
for (const MemberInformation &mi : csi.member)
{
if (mi.db.usedForView)
fqColumnNames += mi.db.table + QLatin1Char('.') + mi.db.columnName + QLatin1String(" AS ") + mi.db.getColumnNameView();
if (mi.cpp.isReadOnly && mi.db.isForeignKey)
{
// readOnly -> FK
// get FK Informations
// get correct TableInformation
for (const QSharedPointer<TableInformation> &tiPtr : csi.tables)
{
if (tiPtr->name == mi.db.table)
{
const QSharedPointer<ForeignKeyInformation> fki = tiPtr->fkInformationForColumn(mi.db.table, mi.db.columnName);
if (!fki.isNull())
{
// IO 26.05.2016: ignore second foreign key on same tables, the create view sql is more complicated in this case (see ppm_sensor_view)
if (!fromTables.contains(fki->destination.tableName))
{
whereConditions += fki->destination.tableName + QLatin1Char('.') + fki->destination.columnName +
QLatin1String(" = ") +
fki->source.tableName + QLatin1Char('.') + fki->source.columnName;
fromTables += fki->destination.tableName;
}
}
break;
}
}
}
}
if (!whereConditions.isEmpty())
{
addToCreateViews(createViewStmt.arg(csi.viewName,
fqColumnNames.join(QStringLiteral(", ")),
fromTables.join(QStringLiteral(", ")),
whereConditions.join(QStringLiteral(" AND\n"))),
cti.exerciseDependency);
}
addToDropViews(dropViewStmt.arg(csi.viewName), cti.exerciseDependency);
}
}
if (!generateDbNotifications(cti.name, csi.dbNotifications, cti.exerciseDependency))
return false;
generateForeignKeys(cti.foreignKeys, cti.exerciseDependency);
generateConstraints(cti.name, cti.constraints, cti.exerciseDependency);
return true;
}
// must not be called with ExerciseDependency::Undefined or Exercisedependency::GlobalAndExercise !!
QString SqlGenerator::getTableColumnCreateStatements(const QString &tableName,
const QVector<MemberInformation> &columns,
QStringList *tableComments,
ExerciseDependency ed)
{
Q_ASSERT(ed == Global || ed == Exercise);
const QString commentColumnStmt(QStringLiteral("COMMENT ON COLUMN %1 IS '%2';"));
// only for beautification of the output
int maxLength = 1;
for (const MemberInformation &mi : columns)
maxLength = std::max(maxLength, mi.db.columnName.length());
maxLength += 1;
QStringList retCols;
retCols.reserve(columns.size() + 1);
retCols += QString();
for (int i = 0; i < columns.size(); ++i)
{
const MemberInformation &mi = columns.at(i);
const FullQualifiedColumnInformation &ci = mi.db;
const QString spaces(maxLength - ci.columnName.length(), QLatin1Char(' '));
QString column = ci.columnName + spaces + ci.datatype;
if (ci.hasSequence)
{
// TODO: dbSchema needs added to sequence
const QString seqDescr = generateSequence(tableName, ci, ed);
QString schema;
if (ed == Global)
schema = m_xmlReader->getServiceAbbr();
else
schema = getExerciseSchemaName(m_exerciseId);
column += QLatin1String(" DEFAULT nextval(( '") +
schema + QLatin1Char('.') + seqDescr +
QLatin1String("'::text)::regclass)");
}
if (!ci.canBeNull)
column += QLatin1String(" NOT NULL");
if (!ci.hasSequence && !ci.defaultValue.isEmpty())
column += QLatin1String(" DEFAULT ") + ci.defaultValue;
if (i < columns.size() - 1)
column += QLatin1Char(',');
if (!ci.comment.isEmpty())
{
const QString comment(fixupComment(ci.comment));
const QString fqn = ci.table + QLatin1Char('.') + ci.columnName;
*tableComments += commentColumnStmt.arg(fqn, comment);
column += QLatin1String(" -- ") + comment;
}
retCols += column;
if (ci.isPrimaryKey)
generatePrimaryKey(tableName, ci, ed);
}
return retCols.join(QStringLiteral("\n "));
}
QString SqlGenerator::generateSequence(const QString &tableName,
const FullQualifiedColumnInformation &ci,
ExerciseDependency ed)
{
const QString sequenceName = tableName + QLatin1Char('_') + ci.columnName + QLatin1String("_seq");
// "IF NOT EXISTS" for sequences possible now from 9.5 on
// http://www.postgresql.org/docs/9.5/static/sql-createsequence.html
const QString createSequenceStmt(QLatin1String("CREATE SEQUENCE IF NOT EXISTS %1 INCREMENT %2 START %3 MINVALUE %4;"));
const int minValue = ci.sequenceStart < 1 ? ci.sequenceStart : 1;
addToCreateSequences(createSequenceStmt.arg(sequenceName).arg(ci.sequenceIncrement).arg(ci.sequenceStart).arg(minValue), ed);
addToSequences(sequenceName, ed);
return sequenceName;
}
void SqlGenerator::generatePrimaryKey(const QString &tableName,
const FullQualifiedColumnInformation &ci,
ExerciseDependency ed)
{
QString pkName = QLatin1String("PK_") + tableName + QLatin1Char('_') + ci.columnName;
// truncate long names to 63
if (pkName.size() > 63) pkName.resize(63);
addToPks(pkName, ed);
// ADD IF NOT EXISTS => Postgresql 9.6
// Using stored procedure 'exists_constraint' for now (14.03.16) (HR)
const QString pkConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 ADD CONSTRAINT %2 PRIMARY KEY (%3);"));
addToCreatePks(pkConstraint.arg(tableName, pkName, ci.columnName), ed);
const QString pkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
addToDropConstraints(pkDropConstraint.arg(tableName, pkName), ed);
}
void SqlGenerator::generateForeignKeys(const QVector<QSharedPointer<ForeignKeyInformation> > &foreignKeys,
ExerciseDependency ed)
{
for (const QSharedPointer<ForeignKeyInformation> &fk : foreignKeys)
generateForeignKey(fk, ed);
}
void SqlGenerator::generateForeignKey(const QSharedPointer<ForeignKeyInformation> &foreignKey,
ExerciseDependency ed)
{
QString fkName = QLatin1String("FK_") + foreignKey->source.tableName +
QLatin1Char('_') + foreignKey->source.columnName +
QLatin1Char('_') + foreignKey->destination.tableName +
QLatin1Char('_') + foreignKey->destination.columnName;
// truncate long names to 64
//if (fkName.size() > 63) fkName.resize(63);
if (fkName.size() > 63) fkName = foreignKey->name;
addToFks(fkName, ed);
QString fkConstraint(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 FOREIGN KEY (%3) REFERENCES %4 (%5)"));
switch (foreignKey->deleteAction)
{
case ForeignKeyInformation::OnDeleteAction::NoAction: // no deletion possible at all
fkConstraint += QStringLiteral(" ON DELETE NO ACTION");
break;
case ForeignKeyInformation::OnDeleteAction::Cascade:
fkConstraint += QStringLiteral(" ON DELETE CASCADE ON UPDATE CASCADE");
break;
case ForeignKeyInformation::OnDeleteAction::Restrict: // no deletion until childs are deleted
fkConstraint += QStringLiteral(" ON DELETE RESTRICT");
break;
case ForeignKeyInformation::OnDeleteAction::SetNull:
fkConstraint += QStringLiteral(" ON DELETE SET NULL");
break;
case ForeignKeyInformation::OnDeleteAction::SetDefault:
fkConstraint += QStringLiteral(" ON DELETE SET DEFAULT");
break;
}
fkConstraint += QLatin1Char(';');
// the work if the fk points to a global or local schema is already done in the EA export script (ExportExternalFk())
// if foreignKey->destination.schema is set, then we've to use it ... :)
QString destination;
if (!foreignKey->destination.schema.isEmpty())
{
const QString &extSchema = foreignKey->destination.schema;
destination = extSchema + QLatin1Char('.') + foreignKey->destination.tableName;
}
else
destination = foreignKey->destination.tableName;
const QString &extSvcAbbr = foreignKey->destination.serviceAbbr;
// depends on another service .. remember depedency
if (!extSvcAbbr.isEmpty() && !m_referencedSevices.contains(extSvcAbbr))
m_referencedSevices += extSvcAbbr;
addToCreateFks(fkConstraint.arg(foreignKey->source.tableName, fkName, foreignKey->source.columnName,
destination, foreignKey->destination.columnName), ed);
const QString fkDropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
addToDropConstraints(fkDropConstraint.arg(foreignKey->source.tableName, fkName), ed);
}
void SqlGenerator::generateConstraints(const QString &tableName,
const QVector<ConstraintInformation> &constraints,
ExerciseDependency ed)
{
for (const ConstraintInformation &constraint : constraints)
generateConstraint(tableName, constraint, ed);
}
void SqlGenerator::generateConstraint(const QString &tableName,
const ConstraintInformation &constraint,
ExerciseDependency ed)
{
const QString dropConstraint(QStringLiteral("ALTER TABLE IF EXISTS %1 DROP CONSTRAINT IF EXISTS %2 CASCADE;"));
switch (constraint.type)
{
case ConstraintInformation::Type::index:
{
QString constraintName = QLatin1String("IDX_") + tableName + QLatin1Char('_') +
constraint.affectedColumns.join(QStringLiteral("_"));
// truncate long names to 63
if (constraintName.size() > 63) constraintName.resize(63);
const QString index(QStringLiteral("CREATE INDEX %1 ON %2 (%3);"));
addToCreateConstraints(index.arg(constraintName, tableName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
addToConstraints(constraintName, ed);
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
break;
}
case ConstraintInformation::Type::unique:
{
QString constraintName = QLatin1String("UQ_") + tableName + QLatin1Char('_') +
constraint.affectedColumns.join(QStringLiteral("_"));
// truncate long names to 63
if (constraintName.size() > 63) constraintName.resize(63);
const QString unique(QStringLiteral("ALTER TABLE %1 ADD CONSTRAINT %2 UNIQUE (%3);"));
addToCreateConstraints(unique.arg(tableName, constraintName, constraint.affectedColumns.join(QStringLiteral(", "))), ed);
addToConstraints(constraintName, ed);
addToDropConstraints(dropConstraint.arg(tableName, constraintName), ed);
break;
}
case ConstraintInformation::Type::unknown:
break;
}
}
bool SqlGenerator::handleEnumerations()
{
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
for (const EnumInformation &ei : enumerations)
{
if (!handleEnumeration(ei))
return false;
}
return true;
}
bool SqlGenerator::handleEnumeration(const EnumInformation &ei)
{
const QStringList sql = handleEnumeration(ei, QString());
if (m_sqlItemsGlobal.tables.contains(ei.tableName))
m_sqlGlobal.enumerationValues += sql;
if (m_sqlItemsExercise.tables.contains(ei.tableName))
m_sqlExercise.enumerationValues += sql;
return true;
}
QStringList SqlGenerator::handleEnumeration(const EnumInformation &ei, const QString &dbSchema) const
{
DEBUG_LOG(m_logger) << "Handle enumeration " << ei.enumName;
if (ei.tableName.isEmpty())
{
INFO_LOG(m_logger) << "No table for enumeration " << ei.enumName << " - not creating inserts for this enum";
return QStringList();
}
const QString insertEnum(QStringLiteral("INSERT INTO %1 (%2) VALUES (%3);"));
const QString columnNames(QStringLiteral("id, name"));
const QString tablename = dbSchema.isEmpty() ? ei.tableName : dbSchema + QLatin1Char('.') + ei.tableName;
// Festlegung: id - name = key - value
QStringList enumerationValues;
for (const EnumKeyValuePair &evp : ei.values)
{
const QString values(QString::number(evp.value) + QLatin1String(", '") +
fixupComment(evp.key) + QLatin1Char('\''));
enumerationValues += insertEnum.arg(tablename, columnNames, values);
}
return enumerationValues;
}
bool SqlGenerator::generateDbNotifications(const QString &tableName,
const QVector<DbNotifcationInformation> &notifications,
ExerciseDependency ed)
{
for (const DbNotifcationInformation &notification : notifications)
{
if (notification.paramNames.isEmpty() ||
notification.paramNames.size() > 1)
{
ERROR_LOG(m_logger) << "Can not handle more than one parameter";
continue;
}
generateDbNotification(notification, ed);
if (!generateDbNotificationTrigger(tableName, notification, ed))
return false;
}
return true;
}
void SqlGenerator::generateDbNotification(const DbNotifcationInformation &notification,
ExerciseDependency ed)
{
const QString func(QStringLiteral("CREATE OR REPLACE FUNCTION public.%1()\n"
"RETURNS TRIGGER AS $$\n"
"DECLARE\n"
" toNotify RECORD;\n"
" toReturn RECORD;\n"
"BEGIN\n"
" IF (TG_OP = 'INSERT') THEN\n"
" toNotify = NEW;\n"
" toReturn = NEW;\n"
" ELSIF (TG_OP = 'UPDATE') THEN\n"
" toNotify = OLD;\n"
" toReturn = NEW;\n"
" ELSIF (TG_OP = 'DELETE') THEN\n"
" toNotify = OLD;\n"
" toReturn = OLD;\n"
" END IF;\n"
" \n"
" PERFORM pg_notify('%2', CAST(toNotify.%3 AS text));\n"
" RETURN toReturn;\n"
"END;\n"
"$$ LANGUAGE plpgsql;"));
const QString triggerFuncName(getNotificationFunctionName(notification));
addToDbFunctions(func.arg(triggerFuncName, notification.methodName, notification.paramNames.first()), ed);
}
bool SqlGenerator::generateDbNotificationTrigger(const QString &tableName,
const DbNotifcationInformation &notification,
ExerciseDependency ed)
{
// CREATE TRIGGER uebung_1ins_tln_zuordn BEFORE INSERT ON tln_zuordnung FOR EACH ROW EXECUTE PROCEDURE public.ins_zuordnung();
const QString drop(QStringLiteral("DROP TRIGGER IF EXISTS %1 ON %2;"));
const QString func(QStringLiteral("CREATE TRIGGER %1\n") +
QStringLiteral("%2 ON %3 FOR EACH ROW EXECUTE PROCEDURE public.%4();"));
for (DbNotifcationInformation::NotificationType notifyType : notification.notificationType)
{
QString when;
QString triggerNameShortcut;
switch (notifyType)
{
case DbNotifcationInformation::NotificationType::BeforeInsert:
when = QStringLiteral("BEFORE INSERT");
triggerNameShortcut = QStringLiteral("bi");
break;
case DbNotifcationInformation::NotificationType::AfterInsert:
when = QStringLiteral("AFTER INSERT");
triggerNameShortcut = QStringLiteral("ai");
break;
case DbNotifcationInformation::NotificationType::BeforeUpdate:
when = QStringLiteral("BEFORE UPDATE");
triggerNameShortcut = QStringLiteral("bu");
break;
case DbNotifcationInformation::NotificationType::AfterUpdate:
when = QStringLiteral("AFTER UPDATE");
triggerNameShortcut = QStringLiteral("au");
break;
case DbNotifcationInformation::NotificationType::BeforeDelete:
when = QStringLiteral("BEFORE DELETE");
triggerNameShortcut = QStringLiteral("bd");
break;
case DbNotifcationInformation::NotificationType::AfterDelete:
when = QStringLiteral("AFTER DELETE");
triggerNameShortcut = QStringLiteral("ad");
break;
}
const QString triggerName(QLatin1String("trg_") + triggerNameShortcut + QLatin1Char('_') + notification.methodName);
const QString triggerFuncName(getNotificationFunctionName(notification));
addToDbFunctionTrigger(drop.arg(triggerName, tableName), ed);
addToDbFunctionTrigger(func.arg(triggerName, when, tableName, triggerFuncName), ed);
}
return true;
}
QString SqlGenerator::getNotificationFunctionName(const DbNotifcationInformation &notification) const
{
return getServiceAbbr() + QLatin1Char('_') + notification.methodName;
}
QString SqlGenerator::fixupComment(const QString &comment)
{
// fixup chars
QString ret = comment;
ret = ret.remove(QLatin1Char('\''));
ret = ret.replace(QLatin1Char(';'), QLatin1Char(','));
return ret;
}
QStringList SqlGenerator::getDbSettings(const QString &dbSchema) const
{
QStringList data;
// see svc_db_impl.cpp.grantlee - DatabaseInterfaceImpl::dbUser()
QString dbUser(QStringLiteral("ets_"));
if (m_exerciseId > 0)
dbUser += QStringLiteral("ex_00"); //getExerciseSchemaName(m_exerciseId);
else
dbUser += m_xmlReader->getServiceAbbr().toLower();
// Grant table privileges
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
QLatin1String(" GRANT SELECT,INSERT,UPDATE,DELETE,TRUNCATE ON TABLES TO ") + dbUser + QLatin1Char(';'));
// Grant select to all users in <product>_ro
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
QLatin1String(" GRANT SELECT, REFERENCES ON TABLES TO ets_ro;"));
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
QLatin1String(" GRANT EXECUTE ON FUNCTIONS TO ") + dbUser + QLatin1Char(';'));
data.append(QLatin1String("ALTER DEFAULT PRIVILEGES IN SCHEMA ") + dbSchema + QLatin1Char('\n') +
QLatin1String(" GRANT ALL ON SEQUENCES TO ") + dbUser + QLatin1Char(';'));
// Set search path
data.append(QLatin1String("SET SEARCH_PATH TO ") + dbSchema + QLatin1String(", PUBLIC;"));
QStringList deps;
deps.reserve(m_referencedSevices.size());
for(const QString &svc : m_referencedSevices)
deps += QStringLiteral("-- DEPENDS: ") + svc;
return deps + data;
}
QStringList SqlGenerator::getAllSqlStmts(const QString &dbSchema, ExerciseDependency ed) const
{
DEBUG_LOG(m_logger) << "Creating statements for service " << getServiceAbbr() << " in exercise " << m_exerciseId << ", this: " << this;
QStringList data;
const SqlStatements &sql = getStatements(ed);
if (sql.isEmpty())
return data;
if (!dbSchema.isEmpty())
data << getDbSettings(dbSchema);
// Write drop-stmts
data.append(QStringLiteral("\n-- Drop tables/views/sequences/constraints"));
data.append(sql.dropViews);
data.append(sql.dropConstraints);
data.append(sql.dropTables);
data.append(sql.dropSequences);
// Write create-stmts (tables)
data.append(QStringLiteral("\n-- Create tables"));
data.append(sql.createTables);
// Create sequence(s)
data.append(QStringLiteral("\n-- Create sequence(s)"));
data.append(sql.createSequences);
// Write primary key(s)
data.append(QStringLiteral("\n-- Create primary keys"));
data.append(sql.createPrimaryKeys);
// Write foreign key(s)
data.append(QStringLiteral("\n-- Create foreign keys"));
data.append(sql.createForeignKeys);
// Write create-stmts (views)
data.append(QStringLiteral("\n-- Create views"));
data.append(sql.createViews);
// Write inserts for enums
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Inserts for static data i.e. enums\n"));
data.append(sql.enumerationValues);
// Write notify functions
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Notify functions for callbacks (signals in db interface)\n"));
data.append(sql.dbFunctions);
// Write notify functions
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Notify function trigger for callbacks (signals in db interface)\n"));
data.append(sql.dbFunctionTrigger);
// Write constraints
data.append(QStringLiteral("\n-- --------------------------------------------------------------------------------"));
data.append(QStringLiteral("\n-- Indexes \n"));
data.append(sql.createConstraints);
return data;
}
QStringList SqlGenerator::getEnumerationInsertValues(const QString &table, const QString &dbSchema) const
{
const QVector<EnumInformation> enumerations = m_xmlReader->getEnumerations();
for (const EnumInformation &ei : enumerations)
{
if (ei.tableName == table)
return handleEnumeration(ei, dbSchema);
}
return QStringList();
}
bool SqlGenerator::writeSqlToFile(const QString &dbSchema, const QString &sqlFile, ExerciseDependency ed) const
{
return writeFile(sqlFile, getAllSqlStmts(dbSchema, ed).join(QLatin1Char('\n')));
}
bool SqlGenerator::writeSql(const QString &filePath, ExerciseDependency ed)
{
Q_ASSERT(ed == Global || ed == Exercise);
// ExerciseDependency == Global -> write exercise independent sql statements -> <svc>.sql
QString sqlFile = filePath + getServiceAbbr();
if (ed == ExerciseDependency::Exercise)
sqlFile += QLatin1Char('_') + getExerciseSchemaName(m_exerciseId);
sqlFile += QLatin1String(".sql");
const SqlStatements &sql = getStatements(ed);
if (sql.isEmpty())
{
DEBUG_LOG(m_logger) << "Sql file is emtpy: " << sqlFile;
return writeFile(sqlFile, QString());
}
// exerciseId > 0 -> write exercise dependent sql statements -> <svc>_ex_<exerciseId>.sql
const QString dbSchema = (ed == ExerciseDependency::Exercise ?
getExerciseSchemaName(m_exerciseId) :
getServiceAbbr());
INFO_LOG(m_logger) << "Creating sql-stmt for schema (" << dbSchema << ")";
return writeSqlToFile(dbSchema, sqlFile, ed);
}
template<typename T>
static void addInternal(QStringList &global,
QStringList &exercise,
const T &stmt,
ExerciseDependency ed)
{
switch (ed)
{
case ExerciseDependency::Global:
global += stmt;
break;
case ExerciseDependency::Exercise:
exercise += stmt;
break;
case ExerciseDependency::GlobalAndExercise:
global += stmt;
exercise += stmt;
break;
case ExerciseDependency::Undefined:
break;
}
}
void SqlGenerator::addToDropSequences(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dropSequences, m_sqlExercise.dropSequences, stmt, ed);
}
void SqlGenerator::addToDropTables(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dropTables, m_sqlExercise.dropTables, stmt, ed);
}
void SqlGenerator::addToDropViews(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dropViews, m_sqlExercise.dropViews, stmt, ed);
}
void SqlGenerator::addToCreateConstraints(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createConstraints, m_sqlExercise.createConstraints, stmt, ed);
}
void SqlGenerator::addToDropConstraints(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dropConstraints, m_sqlExercise.dropConstraints, stmt, ed);
}
void SqlGenerator::addToCreateFks(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createForeignKeys, m_sqlExercise.createForeignKeys, stmt, ed);
}
void SqlGenerator::addToCreatePks(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createPrimaryKeys, m_sqlExercise.createPrimaryKeys, stmt, ed);
}
void SqlGenerator::addToCreateSequences(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createSequences, m_sqlExercise.createSequences, stmt, ed);
}
void SqlGenerator::addToCreateTables(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmt, ed);
}
void SqlGenerator::addToCreateTables(const QStringList &stmts, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createTables, m_sqlExercise.createTables, stmts, ed);
}
void SqlGenerator::addToCreateViews(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.createViews, m_sqlExercise.createViews, stmt, ed);
}
void SqlGenerator::addToDbFunctions(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dbFunctions, m_sqlExercise.dbFunctions, stmt, ed);
}
void SqlGenerator::addToDbFunctionTrigger(const QString &stmt, ExerciseDependency ed)
{
addInternal(m_sqlGlobal.dbFunctionTrigger, m_sqlExercise.dbFunctionTrigger, stmt, ed);
}
void SqlGenerator::addToConstraints(const QString &name, ExerciseDependency ed)
{
addInternal(m_sqlItemsGlobal.constraints, m_sqlItemsExercise.constraints, name, ed);
}
void SqlGenerator::addToFks(const QString &name, ExerciseDependency ed)
{
addInternal(m_sqlItemsGlobal.foreignKeys, m_sqlItemsExercise.foreignKeys, name, ed);
}
void SqlGenerator::addToPks(const QString &name, ExerciseDependency ed)
{
addInternal(m_sqlItemsGlobal.primaryKeys, m_sqlItemsExercise.primaryKeys, name, ed);
}
void SqlGenerator::addToSequences(const QString &name, ExerciseDependency ed)
{
addInternal(m_sqlItemsGlobal.sequences, m_sqlItemsExercise.sequences, name, ed);
}
void SqlGenerator::addToTables(const QString &name, ExerciseDependency ed)
{
addInternal(m_sqlItemsGlobal.tables, m_sqlItemsExercise.tables, name, ed);
}
bool SqlGenerator::writeFile(const QString &fn, const QString &content) const
{
QFile f(fn);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
ERROR_LOG(m_logger) << "Error opening file '" << f.fileName() << "' for writing: "
<< f.errorString();
return false;
}
f.write(content.toUtf8());
if (!content.isEmpty())
f.write("\n");
f.close();
return true;
}
+180
View File
@@ -0,0 +1,180 @@
/**
* @RDE_HEADER@
* Generate SQL statements from Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QStringList>
#include <QSharedPointer>
#include "StructBuilder.h"
#include "codegen_export.h"
namespace NGL
{
class INGLLogger;
}
namespace CodeGen
{
struct ColumnInformation;
struct ConstraintInformation;
struct EnumInformation;
struct ForeignKeyInformation;
struct TableInformation;
class XmlReader;
class CODEGEN_BASE_EXPORT SqlGenerator final
{
public:
struct SqlStatements
{
QStringList dropViews;
QStringList dropTables;
QStringList dropSequences;
QStringList dropConstraints;
QStringList createTables;
QStringList createSequences;
QStringList createPrimaryKeys;
QStringList createForeignKeys;
QStringList createViews;
QStringList createConstraints;
QStringList enumerationValues; // tables are created within createTables
QStringList dbFunctions;
QStringList dbFunctionTrigger;
bool isEmpty() const
{
return dropViews.isEmpty() && dropTables.isEmpty() && dropSequences.isEmpty() && dropConstraints.isEmpty();
}
};
struct SqlItems
{
QStringList tables; // all tables (just names)
QStringList sequences; // all sequences (just names)
QStringList primaryKeys;
QStringList foreignKeys;
QStringList constraints;
};
public:
Q_DISABLE_COPY(SqlGenerator)
explicit SqlGenerator(XmlReader *xmlReader, StructBuilder *structBuilder);
explicit SqlGenerator(const QString &xmlFile);
~SqlGenerator();
bool isValid() const { return m_isValid; }
// exerciseId -> use exerciseId if ExerciseDependent tables are found
bool generateData(int exerciseId);
bool createStatementsForTable(const QString &tableName, const QString &tableNameOut);
SqlStatements getStatements(ExerciseDependency ed) const
{
return (ed == ExerciseDependency::Exercise ? m_sqlExercise : m_sqlGlobal);
}
SqlItems getSqlItems(ExerciseDependency ed) const
{
return (ed == ExerciseDependency::Exercise ? m_sqlItemsExercise : m_sqlItemsGlobal);
}
QStringList getEnumerationInsertValues(const QString &table, const QString &dbSchema) const;
QStringList getReferencedServices() const // returns the abbreviations
{
return m_referencedSevices;
}
QStringList getDbSettings(const QString &dbSchema) const;
// Writes
bool writeSql(const QString &filePath, ExerciseDependency ed);
bool writeSqlToFile(const QString &dbSchema, const QString &sqlFile, ExerciseDependency ed) const;
// Methods from XmlReader
QString getServiceAbbr() const { return m_xmlReader->getServiceAbbr(); }
bool isServiceExerciseIndependent() const { return m_xmlReader->isServiceExerciseIndependent(); }
bool isServiceGlobal() const { return (m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::Global || m_xmlReader->defaultExerciseDependency() == CodeGen::ExerciseDependency::GlobalAndExercise); }
CodeGen::ExerciseDependency getExerciseDependency() const { return m_xmlReader->defaultExerciseDependency();}
QStringList getGroupNames() const { return m_xmlReader->getGroupNames(); }
QStringList getAllSqlStmts(const QString &dbSchema, ExerciseDependency ed) const;
protected:
private:
bool handleTables();
bool handleViews();
bool handleTable(const CompleteTableInformation &cti);
bool handleView(const ViewInformation &vi);
QString getTableColumnCreateStatements(const QString &tableName,
const QVector<MemberInformation> &columns,
QStringList *tableComments, ExerciseDependency ed);
QString generateSequence(const QString &tableName, const FullQualifiedColumnInformation &ci, ExerciseDependency ed);
void generatePrimaryKey(const QString &tableName, const FullQualifiedColumnInformation &ci, ExerciseDependency ed);
void generateForeignKeys(const QVector<QSharedPointer<ForeignKeyInformation> > &foreignKeys, ExerciseDependency ed);
void generateForeignKey(const QSharedPointer<ForeignKeyInformation> &foreignKey, ExerciseDependency ed);
void generateConstraints(const QString &tableName, const QVector<ConstraintInformation> &constraints, ExerciseDependency ed);
void generateConstraint(const QString &tableName, const ConstraintInformation &constraint, ExerciseDependency ed);
bool handleEnumerations();
bool handleEnumeration(const EnumInformation &ei);
QStringList handleEnumeration(const EnumInformation &ei, const QString &dbSchema) const;
bool generateDbNotifications(const QString &tableName, const QVector<DbNotifcationInformation> &notifications, ExerciseDependency ed);
void generateDbNotification(const DbNotifcationInformation &notification, ExerciseDependency ed);
bool generateDbNotificationTrigger(const QString &tableName, const DbNotifcationInformation &notification, ExerciseDependency ed);
QString getNotificationFunctionName(const DbNotifcationInformation &notification) const;
static QString fixupComment(const QString &comment);
private:
XmlReader *m_xmlReader;
StructBuilder *m_structBuilder;
bool m_createdByXmlFile;
void addToDropSequences(const QString &stmt, ExerciseDependency ed);
void addToDropTables(const QString &stmt, ExerciseDependency ed);
void addToDropViews(const QString &stmt, ExerciseDependency ed);
void addToDropConstraints(const QString &stmt, ExerciseDependency ed);
void addToCreateConstraints(const QString &stmt, ExerciseDependency ed);
void addToCreateFks(const QString &stmt, ExerciseDependency ed);
void addToCreatePks(const QString &stmt, ExerciseDependency ed);
void addToCreateSequences(const QString &stmt, ExerciseDependency ed);
void addToCreateTables(const QString &stmt, ExerciseDependency ed);
void addToCreateTables(const QStringList &stmts, ExerciseDependency ed);
void addToCreateViews(const QString &stmt, ExerciseDependency ed);
void addToDbFunctions(const QString &stmt, ExerciseDependency ed);
void addToDbFunctionTrigger(const QString &stmt, ExerciseDependency ed);
void addToConstraints(const QString &name, ExerciseDependency ed);
void addToFks(const QString &name, ExerciseDependency ed);
void addToPks(const QString &name, ExerciseDependency ed);
void addToSequences(const QString &name, ExerciseDependency ed);
void addToTables(const QString &name, ExerciseDependency ed);
bool writeFile(const QString &fn, const QString &content) const;
SqlStatements m_sqlGlobal;
SqlStatements m_sqlExercise;
SqlItems m_sqlItemsGlobal;
SqlItems m_sqlItemsExercise;
QStringList m_referencedSevices;
bool m_isValid;
int m_exerciseId;
NGL::INGLLogger &m_logger;
};
} // namespace CodeGen
+1436
View File
File diff suppressed because it is too large. Load diff
+346
View File
@@ -0,0 +1,346 @@
/**
* @RDE_HEADER@
* Build up struct informations for the generators
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QtCore/QString>
#include "XmlReader.h"
#include "codegen_export.h"
namespace NGL
{
class INGLLogger;
}
namespace CodeGen
{
class StructBuilder;
struct EaData
{
QString table;
QString column;
QString type;
QString toString() const
{
return table +
QLatin1Char(';') + column +
QLatin1Char(';') + type;
}
};
struct FullQualifiedColumnInformation : public ColumnInformation
{
QString table;
bool usedForInsert = false;
bool usedForView = false;
QString columnNameView;
QString getColumnNameView() const { return columnNameView.isEmpty() ? columnName : columnNameView; }
FullQualifiedColumnInformation() = default;
FullQualifiedColumnInformation(const ColumnInformation &ci, const QString &tableName)
: ColumnInformation(ci)
, table(tableName)
{}
QString toString() const
{
return table +
QLatin1Char(';') + columnName +
QLatin1Char(';') + datatype +
// QLatin1Char(';') + comment +
QLatin1Char(';') + (isPrimaryKey ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + (isForeignKey ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + (usedForInsert ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + (usedForView ? QLatin1String("true") : QLatin1String("false"));
}
};
class DataTypeInfo
{
public:
DataTypeInfo(const QString &name, bool isPod)
: m_name(name)
, m_isPod(isPod)
{}
virtual ~DataTypeInfo() = default;
virtual QString name() const { return m_name; }
virtual QString idlType() const = 0; // used in .idl file
virtual QString ddsType() const = 0; // used in generated code from rtiddsgen
virtual QString sqlType() const = 0; // used in sql statements
virtual QString cppType() const = 0; // used in our code
virtual QString cppTypeConstRef() const { return isPod() ? cppType() : QLatin1String("const ") + cppType() + QLatin1String(" &"); }
virtual bool isPod() const { return m_isPod; }
virtual bool isSequence() const { return false; }
virtual bool isStructure() const { return false; }
virtual QVector<QSharedPointer<DataTypeInfo>> member() const { return QVector<QSharedPointer<DataTypeInfo>>(); }
virtual QSharedPointer<DataTypeInfo> innerDataType() const { return QSharedPointer<DataTypeInfo>(); }
static QSharedPointer<DataTypeInfo> fromIdlDataType(const QString &name,
const QString &idlType,
StructBuilder *structBuilder,
const QString &svcName = QString());
protected:
const QString m_name;
const bool m_isPod;
};
struct IdlData : public IdlColumnInformation
{
QSharedPointer<DataTypeInfo> dataTypeInfo;
IdlData() = default;
explicit IdlData(const IdlColumnInformation &ci)
: IdlColumnInformation(ci)
{}
QString toString() const
{
return name +
QLatin1Char(';') + type +
// QLatin1Char(';') + comment +
QLatin1Char(';') + (isKey ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + (isOptional ? QLatin1String("true") : QLatin1String("false"));
}
};
struct CppData
{
QString structName;
QString memberName;
QString defaultValue;
bool usedForCppStruct = false;
bool usedForJson = false;
bool isReadOnly = false;
bool isOptional = false;
QString type;
QString ddsType; // = type, except for e.g. QByteArray
QString toVariantPre; // e.g. static_cast<int>(
QString toVariantPost; // e.g. .x())
QString fromVariantPre1; // before ' = '
QString fromVariantPre2; // after ' = '
QString fromVariantPost;
QString toJsonPre;
QString toJsonPost;
QString fromJsonPre2;
QString fromJsonPost;
QString toString() const
{
return structName +
QLatin1Char(';') + memberName +
QLatin1Char(';') + (usedForCppStruct ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + (isReadOnly ? QLatin1String("true") : QLatin1String("false")) +
QLatin1Char(';') + type +
QLatin1Char(';') + toVariantPre +
QLatin1Char(';') + toVariantPost +
QLatin1Char(';') + fromVariantPre1 +
QLatin1Char(';') + fromVariantPre2 +
QLatin1Char(';') + fromVariantPost;
}
};
struct MemberInformation
{
EaData ea;
FullQualifiedColumnInformation db;
CppData cpp;
bool isDdsKey = false;
QSharedPointer<ForeignKeyInformation> fk;
QSharedPointer<EnumInformation> enumeration;
QString toString() const
{
return ea.toString() + QLatin1Char(';') +
db.toString() + QLatin1Char(';') +
cpp.toString();
}
bool isEmpty() const
{
return ea.column.isEmpty();
}
};
struct CompleteStructInformation
{
QString structName;
QString viewName;
QSet<QSharedPointer<TableInformation> > tables;
QSharedPointer<TableInformation> primaryTable;
QVector<MemberInformation> member;
QVector<DbNotifcationInformation> dbNotifications;
bool hasStaticContent = false;
bool isEmpty() const
{
return primaryTable.isNull();
}
};
struct CompleteTopicTypeInformation final : public TopicTypeInformationBase
{
QString serviceName;
QVector<IdlData> members;
CompleteTopicTypeInformation() = default;
explicit CompleteTopicTypeInformation(const TopicTypeInformationBase &s)
: TopicTypeInformationBase(s)
{}
bool hasBaseValue() const { return !baseValueType.isEmpty(); }
bool isValid() const { return TopicTypeInformationBase::isValid() && !members.isEmpty(); }
};
struct CompleteTableInformation : public TableInformation
{
QVector<MemberInformation> columns;
explicit CompleteTableInformation(const TableInformation &o)
: TableInformation(o)
{}
CompleteTableInformation() = default;
bool isEmpty() const { return columns.isEmpty(); }
};
class CODEGEN_BASE_EXPORT StructBuilder
{
struct ColumnInformationEx : public ColumnInformation
{
QSharedPointer<TableInformation> table;
ColumnInformationEx() = default;
ColumnInformationEx(const ColumnInformation &o,
const QSharedPointer<TableInformation> &ti)
: ColumnInformation(o)
, table(ti)
{}
};
public:
struct ServiceTypeStruct
{
QString service;
QString type;
bool bIsSequence = false;
bool isExternalType() const { return !service.isEmpty(); }
bool isSequence() const { return bIsSequence; }
QString fqType() const { return service + QStringLiteral("::") + type; }
};
explicit StructBuilder(XmlReader *xmlReader);
~StructBuilder() = default;
bool buildStructures();
QVector<CompleteStructInformation> structInformations() const
{
return m_structInfos;
}
QVector<CompleteTopicTypeInformation> topicTypeInformations() const
{
return m_topicTypeInfos;
}
CompleteStructInformation structInformationByTableName(const QString &tableName) const;
EnumInformation enumInformationByType(const QString &type, bool checkExternals = true, bool verbose = true);
CompleteTopicTypeInformation topicTypeInformationByStructType(const QString &type, bool checkExternals = true);
CompleteTopicTypeInformation externalTopicTypeByStructType(const ServiceTypeStruct &sts);
QVector<EnumInformation> enumInformations() const
{
return m_enumInfos;
}
QMap<QString, CompleteTableInformation> getDbTables() const
{
return m_sqlTableInformations;
}
QMap<QString, ViewInformation> getDbViews() const
{
return m_sqlViewInformations;
}
bool needPoint3d() const
{
return m_needPoint3d;
}
bool needPgArrayInclude() const
{
return m_needPgArrayInclude;
}
CompleteTableInformation completeTableInformation(const CompleteTopicTypeInformation &ctti);
CompleteTableInformation getRecordingTopicTable(const QString &structName);
QMap<QString, CompleteTableInformation> getRecordingTopicTables();
QVector<QSharedPointer<DataTypeInfo> > getSerializeFromToJsonTopics() const;
bool fixupQVariantConversions(MemberInformation &mi) const;
Q_REQUIRED_RESULT static QString firstCharLowercase(const QString &txt);
Q_REQUIRED_RESULT static QString camelCaseString(const QString &in, bool bFirstUppercase = false);
Q_REQUIRED_RESULT static QString camelCaseToUnderline(const QString &in);
bool buildJSonStructureInformationFromIdlData(const CompleteTopicTypeInformation &ctti);
QMap<QString, QVector<MemberInformation>> jsonStructureInformation() const { return m_jsonStructureInformation; }
private:
bool handleEnum(const EnumInformation &ei);
bool handleTable(const ServiceStructInformation &ssi);
bool handleIdlStructInfo(const TopicTypeInformation &tti,
QVector<CompleteTopicTypeInformation> &destination,
const QString &svcName = QString());
bool loadExternalTopicType(const ServiceTypeStruct &sts);
bool getColumnsFromTable(const QString &tableName,
QVector<ColumnInformationEx> &columns,
QVector<QString> &tableNames) const;
bool fixupCustomDbDatatypes(MemberInformation &memberInfo, QVector<MemberInformation> &members);
bool fixupDatatypePoint2d(MemberInformation &memberInfo, QVector<MemberInformation> &members);
bool fixupDatatypePoint3d(MemberInformation &memberInfo, QVector<MemberInformation> &members);
bool fixupDatatypeIntegerArray(MemberInformation &memberInfo);
QVector<MemberInformation> buildDbTableInformationFromIdlData(const CompleteTopicTypeInformation &ctti,
const QString &tableName,
const QString &columnNamePrefix,
const QString &cppStructure);
void addToSerializeFromToJsonTopics(const QSharedPointer<DataTypeInfo> &toAdd);
CompleteTableInformation getRecordingTopicTable(const CompleteTopicTypeInformation &ctti,
const MemberInformation &miPrimaryKey);
MemberInformation getRecordPrimaryKey() const;
ServiceTypeStruct extractServiceAndTypeFromTypename(const QString &type);
void addToSqlTableInformation(const MemberInformation &mi);
private:
QVector<CompleteStructInformation> m_structInfos;
QVector<CompleteTopicTypeInformation> m_topicTypeInfos;
mutable QVector<CompleteTopicTypeInformation> m_externalTopicTypes;
QVector<EnumInformation> m_enumInfos;
QMap<QString, CompleteTableInformation> m_sqlTableInformations;
QMap<QString, ViewInformation> m_sqlViewInformations;
QSet<QString> m_sqlTableInformationsAlreadyAvailable;
QVector<QSharedPointer<DataTypeInfo>> m_serializeFromToJsonTopics;
QMap<QString, QVector<MemberInformation>> m_jsonStructureInformation;
bool m_needPoint3d; // need include of 'qpoint3d.h'
bool m_needPgArrayInclude; // need include of 'LtsUtilsCore/pgarray.h'
XmlReader *m_xmlReader;
NGL::INGLLogger &m_logger;
NGL::INGLLogger &m_loggerCSV;
};
} // namespace CodeGen
+941
View File
@@ -0,0 +1,941 @@
/**
* @RDE_HEADER@
* Read Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#include "XmlReader.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QDirIterator>
#include <QFile>
#include <QDomDocument>
#include <QXmlStreamReader>
#include <ngltools/ngltools_filesystem.h>
using namespace CodeGen;
class XmlReaderException : public std::exception
{
public:
explicit XmlReaderException(const QString &msg) noexcept
: m_msg(msg.toUtf8())
{}
~XmlReaderException() = default;
virtual const char *what() const noexcept
{
return m_msg.data();
}
private:
const QByteArray m_msg;
};
XmlReader::XmlReader()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("CodeGen.XmlReader"))
{}
bool XmlReader::readServiceFile(const QString &svcFile,
const QString &idlServiceInfoFile,
const XmlReader *parent)
{
m_externalXmlInformations.clear();
m_idlFiles.clear();
m_xmlFiles.clear();
m_usedServices.clear();
m_fullServiceName2Abbreviation.clear();
// clean up
m_svcNamespace.clear();
m_svcAbbr.clear();
m_svcProcNames.clear();
m_customRecordReplay = false;
m_exerciseDependency = ExerciseDependency::Exercise;
m_dbClassTables.clear();
m_struct2BaseClass.clear();
m_structsToGenerate.clear();
m_idlStructsToGenerate.clear();
m_tables.clear();
m_views.clear();
m_enumerations.clear();
m_functionTopics.clear();
m_externalTopicTypes.clear();
m_customFunctions.clear();
m_prio = 0;
m_svcDevMode = false;
// avoid parsing unneeded files
if (parent)
{
m_idlFiles = parent->m_idlFiles;
m_xmlFiles = parent->m_xmlFiles;
m_fullServiceName2Abbreviation = parent->m_fullServiceName2Abbreviation;
m_externalXmlInformations = parent->m_externalXmlInformations;
}
// load informations about the location of all idl files
loadIdlInfofile(idlServiceInfoFile);
// try to open the service file
QFile f(svcFile);
if (!f.exists())
{
ERROR_LOG(m_logger) << "Service file '" << svcFile << "' not found!";
return false;
}
if (!f.open(QIODevice::ReadOnly))
{
m_errorString = QLatin1String("Can not open service file '") + svcFile +
QLatin1String("' for reading: ") + f.errorString();
ERROR_LOG(m_logger) << m_errorString;
return false;
}
INFO_LOG(m_logger) << "SvcFile: " << svcFile;
// read content
QString errMsg;
int errLine = 0;
int errColumn = 0;
QDomDocument doc;
if (!doc.setContent(&f, &errMsg, &errLine, &errColumn))
{
m_errorString = QLatin1String("Error parsing service file '") + svcFile + QLatin1String("': ") +
errMsg + QLatin1String(" at ") + QString::number(errLine) +
QLatin1Char(':') + QString::number(errColumn);
ERROR_LOG(m_logger) << m_errorString;
return false;
}
// now finally read the data
// what do we expect?
// - a service name ( plus service abbreviation i.e. AES)
// - service classes to generate (dbServiceStructs)
// - database tables to generate (tables)
// - static enumerations to generate (enumerations)
// - idl structures for topicTypes (idlStructs)
// - a devMode attribute which is optional
QDomElement domRoot = doc.documentElement();
if (!domRoot.hasAttribute(QStringLiteral("namespace")))
{
m_errorString = QLatin1String("Xml file '") + svcFile +
QLatin1String("' is not a valid service file: service name/namespace is missing");
ERROR_LOG(m_logger) << m_errorString;
return false;
}
m_svcNamespace = getAttribute(domRoot, QStringLiteral("namespace"));
m_svcAbbr = getAttribute(domRoot, QStringLiteral("abbreviation")).toLower();
m_svcProcNames = getAttribute(domRoot, QStringLiteral("procNames")).split(QLatin1Char(','));
m_customRecordReplay = getAttributeBool(domRoot, QStringLiteral("customRecordReplay"), true, false);
QString devModeStr = getAttribute(domRoot, QStringLiteral("devMode"), true);
m_svcDevMode = devModeStr == QStringLiteral("1") ||
devModeStr == QStringLiteral("true");
INFO_LOG(m_logger) << "devMode: " << m_svcDevMode;
const QString groupNamesString = getAttribute(domRoot, QStringLiteral("groups"), true);
if (!groupNamesString.isEmpty())
{
m_groupNames = groupNamesString.split(QLatin1Char(','), QString::SkipEmptyParts);
INFO_LOG(m_logger) << "groups: " << groupNamesString;
}
else
m_groupNames.clear();
m_exerciseDependency = string2ExerciseDependency(getAttribute(domRoot, QStringLiteral("exerciseDependency"), true));
// exerciseIndepenent only if in core/master group AND no other groups
// core + other groups => one instance of service is svc_0
// other instances are svc_<exerciseId>
if (m_groupNames.contains(QStringLiteral("Core")) ||
m_groupNames.contains(QStringLiteral("Master")))
{
if (m_exerciseDependency != ExerciseDependency::Global &&
m_exerciseDependency != ExerciseDependency::GlobalAndExercise)
{
WARNING_LOG(m_logger) << "Service " << getServiceNamespace()
<< " is not marked as exercise independent but should be started in group Core/Master";
}
}
INFO_LOG(m_logger) << "exerciseDependency: " << (int)m_exerciseDependency;
if ( domRoot.hasAttribute(QStringLiteral("prio")))
{
m_prio = getAttribute(domRoot, QStringLiteral("prio"), 0).toInt();
}
try
{
parseDBSserviceStructs(domRoot.firstChildElement(QStringLiteral("dbServiceStructs")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbServiceStructs: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseDBTables(domRoot.firstChildElement(QStringLiteral("tables")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbtables: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseDBViews(domRoot.firstChildElement(QStringLiteral("views")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing dbviews: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseEnumerations(domRoot.firstChildElement(QStringLiteral("enumerations")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing enumerations: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseCustomDtcFunctions(domRoot.firstChildElement(QStringLiteral("customDtcFunctions")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing custom dtc functions: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseTopicTypes(domRoot.firstChildElement(QStringLiteral("topicTypes")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing idl strucs: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
try
{
parseFunctionTopics(domRoot.firstChildElement(QStringLiteral("functionTopics")));
}
catch (const XmlReaderException &ex)
{
m_errorString = QLatin1String("Error parsing function topics: ") + QLatin1String(ex.what());
ERROR_LOG(m_logger) << m_errorString;
return false;
}
return true;
}
QString XmlReader::getAttribute(const QDomElement &element,
const QString &attributeName,
bool bOptional)
{
if (!element.hasAttribute(attributeName))
{
if (bOptional)
return QString();
throw XmlReaderException(QLatin1String("Element ") + element.tagName() +
QLatin1String(" has no attribute ") + attributeName);
}
return element.attribute(attributeName);
}
bool XmlReader::getAttributeBool(const QDomElement &element,
const QString &attributeName,
bool bOptional, bool defaultValue)
{
QString str = getAttribute(element, attributeName, bOptional);
if (str.isEmpty())
return defaultValue;
bool bOk;
bool ret = (str.toInt(&bOk) != 0);
if (!bOk)
throw XmlReaderException(QLatin1String("Can not parse boolean value: ") + str);
return ret;
}
int XmlReader::getAttributeInt(const QDomElement &element,
const QString &attributeName,
bool bOptional, int defaultValue)
{
QString str = getAttribute(element, attributeName, bOptional);
if (str.isEmpty())
return defaultValue;
bool bOk;
int ret = str.toInt(&bOk);
if (!bOk)
{
//try from base16
ret = str.toUInt(&bOk,16);
if(!bOk)
throw XmlReaderException(QLatin1String("Can not parse int value: ") + str);
}
return ret;
}
void XmlReader::parseDBSserviceStructs(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// service struct -> table relations
QDomElement domDbStruct = rootElement.firstChildElement(QStringLiteral("serviceStruct"));
while (!domDbStruct.isNull())
{
ServiceStructInformation ssi;
ssi.name = getAttribute(domDbStruct, QStringLiteral("name"));
ssi.primaryTable = getAttribute(domDbStruct, QStringLiteral("table"));
ssi.hasStaticContent = getAttributeBool(domDbStruct, QStringLiteral("static"), true, false);
QDomElement domFunc = domDbStruct.firstChildElement(QStringLiteral("function"));
while (!domFunc.isNull())
{
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Db);
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
}
QDomElement domNotify = domDbStruct.firstChildElement(QStringLiteral("notification"));
while (!domNotify.isNull())
{
ssi.dbNotifications.append(parseDbNotification(domNotify));
domNotify = domNotify.nextSiblingElement(domNotify.tagName());
}
m_structsToGenerate.push_back(ssi);
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
}
std::sort(m_structsToGenerate.begin(), m_structsToGenerate.end());
// table -> parent table relations
domDbStruct = rootElement.firstChildElement(QStringLiteral("table"));
while (!domDbStruct.isNull())
{
const QString table = getAttribute(domDbStruct, QStringLiteral("name"));
const QString parentTable = getAttribute(domDbStruct, QStringLiteral("base"), true);
m_struct2BaseClass[table] = parentTable;
domDbStruct = domDbStruct.nextSiblingElement(domDbStruct.tagName());
}
INFO_LOG(m_logger) << "Read " << m_structsToGenerate.size() << " database service struct(s)";
}
void XmlReader::parseDBTables(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domDbTable = rootElement.firstChildElement(QStringLiteral("table"));
while (!domDbTable.isNull())
{
QSharedPointer<TableInformation> ti(new TableInformation);
ti->name = getAttribute(domDbTable, QStringLiteral("name"));
ti->structName = QLatin1String("t_") + ti->name;
ti->comment = getAttribute(domDbTable, QStringLiteral("comment"), true);
ti->exerciseDependency = string2ExerciseDependency(getAttribute(domDbTable, QStringLiteral("exerciseDependency"), true));
QDomElement domColumn = domDbTable.firstChildElement(QStringLiteral("column"));
while (!domColumn.isNull())
{
ti->plainColumns.append(parseTableColumn(domColumn, ti->name));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
QDomElement domForeignKey = domDbTable.firstChildElement(QStringLiteral("foreignKey"));
while (!domForeignKey.isNull())
{
ti->foreignKeys.append(parseForeignKey(domForeignKey));
domForeignKey = domForeignKey.nextSiblingElement(domForeignKey.tagName());
}
QDomElement domConstraint = domDbTable.firstChildElement(QStringLiteral("constraint"));
while (!domConstraint.isNull())
{
ti->constraints.append(parseConstraint(domConstraint));
domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName());
}
m_tables.insert(ti->name, ti);
domDbTable = domDbTable.nextSiblingElement(domDbTable.tagName());
}
INFO_LOG(m_logger) << "Read " << m_tables.size() << " database table(s)";
}
void XmlReader::parseDBViews(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domDbView = rootElement.firstChildElement(QStringLiteral("view"));
while (!domDbView.isNull())
{
QSharedPointer<ViewInformation> vi(new ViewInformation);
vi->name = getAttribute(domDbView, QStringLiteral("name"));
vi->structName = QLatin1String("v_") + vi->name;
vi->baseSchema = m_svcAbbr;
vi->baseTable = getAttribute(domDbView, QStringLiteral("base"));
vi->comment = getAttribute(domDbView, QStringLiteral("comment"), true);
vi->filter = getAttribute(domDbView, QStringLiteral("filter"));
vi->exerciseDependency = string2ExerciseDependency(getAttribute(domDbView, QStringLiteral("exerciseDependency"), true));
DEBUG_LOG(m_logger) << "Base table = " << vi->baseTable;
/*
QDomElement domConstraint = domDbView.firstChildElement(QStringLiteral("constraint"));
while (!domConstraint.isNull())
{
vi->constraints.append(parseConstraint(domConstraint));
domConstraint = domConstraint.nextSiblingElement(domConstraint.tagName());
}
*/
m_views.insert(vi->name, vi);
domDbView = domDbView.nextSiblingElement(domDbView.tagName());
}
INFO_LOG(m_logger) << "Read " << m_views.size() << " database view(s)";
}
void XmlReader::parseEnumerations(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domEnum = rootElement.firstChildElement(QStringLiteral("enumeration"));
while (!domEnum.isNull())
{
EnumInformation enumeration;
enumeration.serviceName = getServiceNamespace();
enumeration.enumName = getAttribute(domEnum, QStringLiteral("name"));
enumeration.tableName = getAttribute(domEnum, QStringLiteral("table"), true);
QDomElement enumVal = domEnum.firstChildElement(QStringLiteral("attribute"));
while (!enumVal.isNull())
{
EnumKeyValuePair evp;
evp.key = getAttribute(enumVal, QStringLiteral("key"));
evp.value = getAttributeInt(enumVal, QStringLiteral("value"));
enumeration.values.append(evp);
enumVal = enumVal.nextSiblingElement(enumVal.tagName());
}
m_enumerations.push_back(enumeration);
domEnum = domEnum.nextSiblingElement(domEnum.tagName());
}
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " enumeration(s)";
}
void XmlReader::parseCustomDtcFunctions(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
QDomElement domElement = rootElement.firstChildElement(QStringLiteral("customDtcFunction"));
while (!domElement.isNull())
{
QDomElement domFunc = domElement.firstChildElement(QStringLiteral("function"));
while (!domFunc.isNull())
{
m_customFunctions += parseCustomFunction(domFunc, CustomServiceFunctionInformation::Type::Dtc);
domFunc = domFunc.nextSiblingElement(domFunc.tagName());
}
domElement = domElement.nextSiblingElement(domElement.tagName());
}
INFO_LOG(m_logger) << "Read " << m_enumerations.size() << " customDtcFunction(s)";
}
/**
* @brief Parse idl structure (TopicType Information)
* (IDL = Interface Definition Language)
* @param rootElement
*/
void XmlReader::parseTopicTypes(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// idl struct
QDomElement domIdlStruct = rootElement.firstChildElement(QStringLiteral("topicType"));
while (!domIdlStruct.isNull())
{
TopicTypeInformation isi;
isi.name = getAttribute(domIdlStruct, QStringLiteral("name"));
isi.baseValueType = getAttribute(domIdlStruct, QStringLiteral("baseValueType"), true);
// preload some external topics to be able to know which external services we're using
const QVector<QStringRef> sl = isi.baseValueType.splitRef(QStringLiteral("::"));
if (sl.size() == 2)
loadExternalTopicType(sl.at(0).toString(), sl.at(1).toString());
const QString tmp = getAttribute(domIdlStruct, QStringLiteral("extensibility"), true);
if (tmp.compare(QLatin1String("FINAL"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::FINAL;
if (tmp.compare(QLatin1String("MUTABLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::MUTABLE;
if (tmp.compare(QLatin1String("EXTENSIBLE"), Qt::CaseInsensitive) == 0) isi.extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE;
isi.comment = getAttribute(domIdlStruct, QStringLiteral("comment"), true);
isi.isDataObject = getAttributeBool(domIdlStruct, QStringLiteral("isDataObject"), true, false);
isi.dtsAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTS"), true, true);
isi.dtcAutogen = getAttributeBool(domIdlStruct, QStringLiteral("autogenDTC"), true, true);
isi.recordTopic = getAttributeBool(domIdlStruct, QStringLiteral("recordTopic"), true, false);
isi.routeToClient = getAttributeBool(domIdlStruct, QStringLiteral("routeToClient"), true, true);
isi.routeToServer = getAttributeBool(domIdlStruct, QStringLiteral("routeToServer"), true, false);
isi.routeInReplay = getAttributeBool(domIdlStruct, QStringLiteral("routeInReplay"), true, false);
isi.autosubscribe = getAttributeBool(domIdlStruct, QStringLiteral("autosubscribe"), true, true);
QDomElement domColumn = domIdlStruct.firstChildElement(QStringLiteral("column"));
while (!domColumn.isNull())
{
isi.columns.append(parseIdlStructColumn(domColumn));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
//INFO_LOG(m_logger) << "# TopicType: " << isi.name;
m_idlStructsToGenerate.insert(isi.name, isi);
domIdlStruct = domIdlStruct.nextSiblingElement(domIdlStruct.tagName());
}
INFO_LOG(m_logger) << "Read " << m_idlStructsToGenerate.size() << " idl struct(s)";
}
void XmlReader::parseFunctionTopics(const QDomElement &rootElement)
{
if (rootElement.isNull())
return;
// idl struct
QDomElement domFunctionTopic = rootElement.firstChildElement(QStringLiteral("functionTopic"));
while (!domFunctionTopic.isNull())
{
FunctionTopic ft;
ft.name = getAttribute(domFunctionTopic, QStringLiteral("name"));
ft.requestTopicType = getAttribute(domFunctionTopic, QStringLiteral("requestTopicType"));
ft.replyTopicType = getAttribute(domFunctionTopic, QStringLiteral("replyTopicType"));
ft.requestTopicName = getAttribute(domFunctionTopic, QStringLiteral("requestTopicName"));
ft.replyTopicName = getAttribute(domFunctionTopic, QStringLiteral("replyTopicName"));
ft.routeInReplay = getAttributeBool(domFunctionTopic, QStringLiteral("routeInReplay"), true, false);
m_functionTopics.append(ft);
domFunctionTopic = domFunctionTopic.nextSiblingElement(domFunctionTopic.tagName());
}
for (const FunctionTopic &ft : qAsConst(m_functionTopics))
{
const QVector<QStringRef> sl1 = ft.requestTopicType.splitRef(QStringLiteral("::"));
if (sl1.size() == 2)
loadExternalTopicType(sl1.at(0).toString(), sl1.at(1).toString());
const QVector<QStringRef> sl2 = ft.replyTopicType.splitRef(QStringLiteral("::"));
if (sl2.size() == 2)
loadExternalTopicType(sl2.at(0).toString(), sl2.at(1).toString());
}
}
ColumnInformation XmlReader::parseTableColumn(const QDomElement &column, const QString &tableName) const
{
ColumnInformation ci;
ci.columnName = getAttribute(column, QStringLiteral("name"));
ci.datatype = getAttribute(column, QStringLiteral("datatype"));
ci.defaultValue = getAttribute(column, QStringLiteral("default"), true);
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
ci.isPrimaryKey = getAttributeBool(column, QStringLiteral("isPrimaryKey"), true, false);
ci.isForeignKey = getAttributeBool(column, QStringLiteral("isForeignKey"), true, false);
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
const QDomElement seq = column.firstChildElement(QStringLiteral("sequence"));
if (!seq.isNull())
{
ci.sequenceStart = getAttributeInt(seq, QStringLiteral("start"), true, -1);
ci.sequenceIncrement = getAttributeInt(seq, QStringLiteral("increment"), true, -1);
ci.hasSequence = ci.sequenceStart >= 0 && ci.sequenceIncrement > 0;
}
if (ci.columnName.toLower() != ci.columnName)
WARNING_LOG(m_logger) << "Column " << ci.columnName << " in Table "
<< tableName << " has has upper-case chars which is not allowed!";
return ci;
}
IdlColumnInformation XmlReader::parseIdlStructColumn(const QDomElement &column) const
{
IdlColumnInformation ci;
ci.name = getAttribute(column, QStringLiteral("name"));
ci.type = getAttribute(column, QStringLiteral("idlType"));
ci.comment = getAttribute(column, QStringLiteral("comment"), true);
ci.isKey = getAttributeBool(column, QStringLiteral("isKey"), true, false);
ci.isOptional = getAttributeBool(column, QStringLiteral("isOptional"), true, false);
ci.isTranslatable = getAttributeBool(column, QStringLiteral("isTranslatable"), true, false);
return ci;
}
QSharedPointer<ForeignKeyInformation> XmlReader::parseForeignKey(const QDomElement &foreignKey) const
{
QSharedPointer<ForeignKeyInformation> fk(new ForeignKeyInformation);
fk->name = getAttribute(foreignKey, QStringLiteral("name"));
// Default-Value of isOnDeleteCascade => true
const QString deleteAction = getAttribute(foreignKey, QStringLiteral("referential_integrity"), true);
if (deleteAction == QLatin1String("DeleteNoAction"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::NoAction;
else if (deleteAction == QLatin1String("DeleteCascade"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade;
else if (deleteAction == QLatin1String("DeleteRestrict"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Restrict;
else if (deleteAction == QLatin1String("DeleteSetNull"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetNull;
else if (deleteAction == QLatin1String("DeleteSetDefault"))
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::SetDefault;
else
fk->deleteAction = ForeignKeyInformation::OnDeleteAction::Cascade; // default
fk->source = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("source")));
fk->destination = parseForeignKeyEndpoint(fk->name, foreignKey.firstChildElement(QStringLiteral("destination")));
return fk;
}
ForeignKeyInformation::Endpoint XmlReader::parseForeignKeyEndpoint(const QString &foreignKeyName,
const QDomElement &endpoint) const
{
if (endpoint.isNull())
throw XmlReaderException(QLatin1String("No endpoint for foreign key ") + foreignKeyName);
ForeignKeyInformation::Endpoint fkEndpoint;
fkEndpoint.service = getAttribute(endpoint, QStringLiteral("service"), true);
fkEndpoint.serviceAbbr= getAttribute(endpoint, QStringLiteral("serviceAbbr"), true);
fkEndpoint.schema = getAttribute(endpoint, QStringLiteral("schema"), true);
fkEndpoint.tableName = getAttribute(endpoint, QStringLiteral("table"));
fkEndpoint.columnName = getAttribute(endpoint, QStringLiteral("column"));
return fkEndpoint;
}
ConstraintInformation XmlReader::parseConstraint(const QDomElement &constraint) const
{
ConstraintInformation ret;
ret.name = getAttribute(constraint, QStringLiteral("name"), true);
const QString type = getAttribute(constraint, QStringLiteral("type")).toLower();
if (type == QLatin1String("unique"))
ret.type = ConstraintInformation::Type::unique;
else if (type == QLatin1String("index"))
ret.type = ConstraintInformation::Type::index;
QDomElement domColumn = constraint.firstChildElement(QStringLiteral("column"));
if (domColumn.isNull())
throw XmlReaderException(QLatin1String("No column for constraint") + ret.name);
while (!domColumn.isNull())
{
ret.affectedColumns += getAttribute(domColumn, QStringLiteral("name"));
domColumn = domColumn.nextSiblingElement(domColumn.tagName());
}
return ret;
}
CustomServiceFunctionInformation XmlReader::parseCustomFunction(const QDomElement &customFunc,
CustomServiceFunctionInformation::Type funcType) const
{
CustomServiceFunctionInformation ret;
ret.methodName = getAttribute(customFunc, QStringLiteral("name"));
ret.returnValue = getAttribute(customFunc, QStringLiteral("returns"), true);
ret.isConst = getAttributeBool(customFunc, QStringLiteral("isConst"), true, false);
if (ret.returnValue.isEmpty())
ret.returnValue = QStringLiteral("bool");
QMap<int, QPair<QString, QString>> paramHelper;
QDomElement domParam = customFunc.firstChildElement(QStringLiteral("parameter"));
while (!domParam.isNull())
{
int pos = getAttributeInt(domParam, QStringLiteral("pos"));
if (paramHelper.contains(pos))
throw XmlReaderException(QLatin1String("Doubled position entry for custom function ") + ret.methodName);
QString paramName = getAttribute(domParam, QStringLiteral("name"));
const QString paramType = getAttribute(domParam, QStringLiteral("type"));
const QString paramDefault = getAttribute(domParam, QStringLiteral("default"), true);
if (!paramDefault.isEmpty())
paramName.append(QLatin1String(" = ") + paramDefault);
paramHelper[pos] = qMakePair(paramName, paramType);
domParam = domParam.nextSiblingElement(domParam.tagName());
}
ret.paramNames.reserve(paramHelper.size());
ret.paramTypes.reserve(paramHelper.size());
ret.type = funcType;
QStringList signature;
for (auto it = paramHelper.constBegin(); it != paramHelper.constEnd(); ++it)
{
ret.paramNames += it.value().first;
ret.paramTypes += it.value().second;
QString tmp = it.value().second;
if (!it.value().second.endsWith(QLatin1Char('&')))
tmp += QLatin1Char(' ');
tmp += it.value().first;
signature += tmp;
}
ret.signature += signature.join(QStringLiteral(", "));
return ret;
}
DbNotifcationInformation XmlReader::parseDbNotification(const QDomElement &customFunc) const
{
DbNotifcationInformation ret(parseCustomFunction(customFunc, CustomServiceFunctionInformation::Type::DbNotify));
const QString attribute(getAttribute(customFunc, QStringLiteral("notificationTypes")).toLower());
const QStringList nfTypes = attribute.split(QLatin1Char(','));
static const QMap<QString, DbNotifcationInformation::NotificationType> s_nfTypeMap =
{
{ QLatin1String("beforeinsert"), DbNotifcationInformation::NotificationType::BeforeInsert },
{ QLatin1String("afterinsert"), DbNotifcationInformation::NotificationType::AfterInsert },
{ QLatin1String("beforeupdate"), DbNotifcationInformation::NotificationType::BeforeUpdate },
{ QLatin1String("afterupdate"), DbNotifcationInformation::NotificationType::AfterUpdate },
{ QLatin1String("beforedelete"), DbNotifcationInformation::NotificationType::BeforeDelete },
{ QLatin1String("afterdelete"), DbNotifcationInformation::NotificationType::AfterDelete }
};
for (const QString &nfType : nfTypes)
{
const auto it = s_nfTypeMap.find(nfType);
if (it == s_nfTypeMap.end())
throw XmlReaderException(QStringLiteral("Can not find notification type %2 in type map for function %1").arg(ret.methodName, nfType));
ret.notificationType += it.value();
}
if (ret.notificationType.isEmpty())
throw XmlReaderException(QStringLiteral("Notification function '%1' without a valid notification type").arg(ret.methodName));
if (ret.paramTypes.size() != 1)
throw XmlReaderException(QStringLiteral("Notification function '%1' must have exactly one parameter").arg(ret.methodName));
return ret;
}
TopicTypeInformation XmlReader::loadExternalTopicType(const QString &externalServiceName, const QString &topicTypeName)
{
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << topicTypeName;
if (!loadExternalService(externalServiceName))
{
WARNING_LOG(m_logger) << "Could not find service " << externalServiceName
<< " referenced by external topic " << topicTypeName;
return TopicTypeInformation();
}
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
if (!extXmlReader)
{
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
return TopicTypeInformation();
}
TopicTypeInformation tti = extXmlReader->getIdlStructInformation().value(topicTypeName);
if (tti.isValid())
{
tti.name = externalServiceName + QLatin1String("::") + tti.name;
m_externalTopicTypes += tti;
m_usedServices[externalServiceName] = true;
return tti;
}
WARNING_LOG(m_logger) << "Could not find topic " << topicTypeName
<< " in external service definition for service "
<< externalServiceName;
return TopicTypeInformation();
}
EnumInformation XmlReader::getExternalEnumeration(const QString &externalServiceName, const QString &enumName, bool verbose)
{
DEBUG_LOG(m_logger) << "externalServiceName: " << externalServiceName << ", topicTypeName: " << enumName;
if (!loadExternalService(externalServiceName))
{
WARNING_LOG(m_logger) << "Could not find service '" << externalServiceName << "'"
<< " referenced by external enumeration " << enumName;
return EnumInformation();
}
auto extXmlReader = m_externalXmlInformations.value(externalServiceName);
if (!extXmlReader)
{
ERROR_LOG(m_logger) << "Could not find service xml file for service '" << externalServiceName << "'";
return EnumInformation();
}
const QVector<EnumInformation> enums = extXmlReader->getEnumerations();
for (const EnumInformation &ei : enums)
{
if (ei.enumName == enumName)
{
m_usedServices[externalServiceName] = true;
return ei;
}
}
if (verbose)
{
WARNING_LOG(m_logger) << "Could not find enumeration " << enumName
<< " in external service definition for '"
<< externalServiceName << "'";
}
return EnumInformation();
}
bool XmlReader::loadExternalService(const QString &serviceName)
{
if (serviceName == getServiceNamespace())
return false;
if (m_externalXmlInformations.contains(serviceName))
return true;
QString fileName = xmlFileForService(serviceName);
if (fileName.isEmpty())
{
const QVector<QString> dirs = NGL::directories(NGL::FileType::ConfigFile);
for (const QString &dirname : dirs)
{
QDirIterator dirIt(dirname + QStringLiteral("/xml"), QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
while (dirIt.hasNext())
{
dirIt.next();
if (!dirIt.fileName().endsWith(QLatin1String(".xml")))
continue;
fileName = dirIt.filePath();
const QString svcNamespace = serviceNamespaceFromFile(fileName);
if (svcNamespace == serviceName)
break;
}
}
}
if (fileName.isEmpty())
return false;
DEBUG_LOG(m_logger) << "xml file for " << serviceName << ": " << fileName;
auto extXmlReader = QSharedPointer<XmlReader>(new XmlReader);
if (!extXmlReader->readServiceFile(fileName, QString(), this))
{
WARNING_LOG(m_logger) << "Error reading service file " << fileName
<< " for service " << serviceName;
return false;
}
m_externalXmlInformations[serviceName] = extXmlReader;
if (!m_usedServices.contains(serviceName))
m_usedServices[serviceName] = false;
return true;
}
void XmlReader::loadIdlInfofile(const QString &idlServiceInfoFile)
{
if (idlServiceInfoFile.isEmpty())
return;
QFile f(idlServiceInfoFile);
if (!f.open(QIODevice::ReadOnly))
{
WARNING_LOG(m_logger) << "Could not read idl service info file " << idlServiceInfoFile;
return;
}
const QList<QByteArray> files = f.readAll().split('\n');
for (const QByteArray &filePath : files)
{
// comment
if (filePath.startsWith("--") || filePath.trimmed().isEmpty())
continue;
const QString fileName = QString::fromLocal8Bit(filePath.trimmed());
const QVector<QStringRef> parts = fileName.splitRef(QLatin1Char('/'));
getServiceNameInfoFromFilePath(parts); // what/ever/ServiceFooBar/sfb_service.xml -> ServiceFooBar <-> sfb
const auto fn = parts.last().trimmed();
if (fn.endsWith(QLatin1String(".xml")))
{
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).split(QLatin1Char('_')).at(0);
m_xmlFiles[svcAbbreveation.toString()] = fileName;
DEBUG_LOG(m_logger) << "xml: " << svcAbbreveation.toString() << " = " << fileName;
}
else
{
const auto svcAbbreveation = fn.split(QLatin1Char('.')).at(0).toString().toLower();
if (svcAbbreveation.endsWith(QLatin1String("_enums")))
continue;
m_idlFiles[svcAbbreveation] = fileName;
DEBUG_LOG(m_logger) << "idl: " << svcAbbreveation << " = " << fileName;
}
}
}
QString XmlReader::getIdlFileForService(const QMap<QString, QString> &data,
const QString &serviceName) const
{
const QString serviceAbbr = serviceNameAbbreviation(serviceName);
return data.value(serviceAbbr);
}
QString XmlReader::serviceNameAbbreviation(const QString &serviceName) const
{
return m_fullServiceName2Abbreviation.value(serviceName);
}
void XmlReader::getServiceNameInfoFromFilePath(const QVector<QStringRef> &parts)
{
if (parts.size() < 2)
return;
// foo_service.xml -> foo
const QString abbreviation = parts.last().split(QLatin1Char('.')).first().split(QLatin1Char('_')).first().toString();
const QString serviceName = parts.at(parts.size() - 2).toString();
DEBUG_LOG(m_logger) << "serviceName: " << serviceName << " = " << abbreviation;
m_fullServiceName2Abbreviation[serviceName] = abbreviation;
}
ExerciseDependency XmlReader::string2ExerciseDependency(const QString &str)
{
if (str.isEmpty())
return ExerciseDependency::Undefined;
const QStringList sd = str.toLower().split(QLatin1Char(','));
int ret = ExerciseDependency::Undefined;
for (const QString &s : sd)
{
if (s == QLatin1String("global"))
ret |= ExerciseDependency::Global;
else if (s == QLatin1String("exercise"))
ret |= ExerciseDependency::Exercise;
else if (s == QLatin1String("globalandexercise"))
ret |= ExerciseDependency::GlobalAndExercise;
}
return static_cast<ExerciseDependency>(ret);
}
QString XmlReader::serviceNamespaceFromFile(const QString &fn)
{
QFile f(fn);
if (!f.open(QIODevice::ReadOnly))
return QString();
QXmlStreamReader sr(&f);
while (!sr.atEnd())
{
sr.readNext();
if (sr.isStartElement() && sr.name() == QLatin1String("service"))
return (sr.attributes().value(QLatin1String("namespace"))).toString();
}
return QString();
}
+432
View File
@@ -0,0 +1,432 @@
/**
* @RDE_HEADER@
* Read Xml service files
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QtCore/QMap>
#include <QtCore/QSet>
#include <QtCore/QSharedPointer>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QVector>
#include <dds/core/xtypes/EnumType.hpp>
#include "codegen_export.h"
namespace NGL
{
class INGLLogger;
}
class QDomElement;
namespace CodeGen
{
enum ExerciseDependency // bitfield
{
Undefined = 0,
Global = 0x1,
Exercise = 0x2,
GlobalAndExercise = 0x3
};
struct EnumKeyValuePair
{
QString key;
int value = 0;
};
struct EnumInformation
{
QString enumName;
QString tableName;
QString serviceName;
QVector<EnumKeyValuePair> values;
bool isValid() const { return !enumName.isEmpty(); }
};
struct ColumnInformation
{
QString columnName;
QString datatype;
QString comment;
QString defaultValue;
bool isPrimaryKey = false;
bool isAssignmentForPrimaryTablePk = false;
bool isForeignKey = false;
bool generateSelectRemove = false; // generate db functions 'selectByFoo()' and 'removeByFoo()'
bool canBeNull = true; // currently not passed through from EA / xml
bool hasSequence = false;
bool isOptional = false; // when optional, than the type is a QVariant (and QVariant::null is NULL)
int sequenceStart = 0;
int sequenceIncrement = 0;
};
struct IdlColumnInformation
{
QString name;
QString type; //!< the idl data type
QString comment;
bool isKey = false;
bool isOptional = false;
bool isTranslatable = false;
};
struct ForeignKeyInformation
{
struct Endpoint
{
QString service;
QString serviceAbbr;
QString schema;
QString tableName;
QString columnName;
};
QString name;
Endpoint source;
Endpoint destination;
enum class OnDeleteAction
{
NoAction,
Cascade,
Restrict,
SetNull,
SetDefault
} deleteAction = OnDeleteAction::Cascade;
};
struct CustomServiceFunctionInformation
{
QString methodName;
QString returnValue;
QString signature;
bool isConst = false;
QVector<QString> paramTypes;
QVector<QString> paramNames;
enum class Type
{
Db,
DbNotify,
Dtc
} type = Type::Db;
};
struct DbNotifcationInformation : public CustomServiceFunctionInformation
{
DbNotifcationInformation() = default;
explicit DbNotifcationInformation(const CustomServiceFunctionInformation &d)
: CustomServiceFunctionInformation(d)
{}
enum class NotificationType
{
BeforeInsert,
AfterInsert,
BeforeUpdate,
AfterUpdate,
BeforeDelete,
AfterDelete
};
QVector<NotificationType> notificationType;
};
struct ConstraintInformation
{
enum struct Type
{
unknown = 0x00,
unique = 0x01,
index = 0x02
} type = Type::unknown;
QString name;
QStringList affectedColumns;
};
struct TableInformation
{
QString name;
QString structName;
QString comment;
QVector<ColumnInformation> plainColumns; // not yet processed - e.g. with datatype 'point_2d'
QVector<QSharedPointer<ForeignKeyInformation> > foreignKeys;
QVector<ConstraintInformation> constraints;
ExerciseDependency exerciseDependency = ExerciseDependency::Undefined;
QSharedPointer<ForeignKeyInformation> fkInformationForColumn(const QString &tableName,
const QString &columnName) const
{
for (const QSharedPointer<ForeignKeyInformation> &fki : foreignKeys)
{
if (fki->source.columnName == columnName &&
fki->source.tableName == tableName)
return fki;
}
return QSharedPointer<ForeignKeyInformation>();
}
};
struct ViewInformation
{
QString name;
QString comment;
QString structName;
QString baseTable;
QString baseSchema;
QString filter;
ExerciseDependency exerciseDependency = ExerciseDependency::Undefined;
};
struct ServiceStructInformation
{
QString name;
QString primaryTable;
bool hasStaticContent = false;
bool operator<(const ServiceStructInformation &o) const
{
if (hasStaticContent != o.hasStaticContent)
return hasStaticContent < o.hasStaticContent;
if (name != o.name)
return name < o.name;
return primaryTable < o.primaryTable;
}
QVector<DbNotifcationInformation> dbNotifications;
};
struct TopicTypeInformationBase
{
TopicTypeInformationBase() = default;
virtual ~TopicTypeInformationBase() = default;
QString name;
QString baseValueType; // parent class
QString comment;
bool isDataObject = false; // can be used as DWO/DRO
bool dtsAutogen = true; // automatically generate DTS Impl
bool dtcAutogen = true; // automatically generate DTC Impl
bool recordTopic = false; // record topic for replay?
bool routeToClient = true; // route Topic from server to client?
bool routeToServer = false; // route Topic from client to server?
bool routeInReplay = true; // route Topic in replay from exercise to replay (or vice-versa)?
bool autosubscribe = true; // automatically call enableFoo() in DTC::enableAll()
dds::core::xtypes::ExtensibilityKind extensibility = dds::core::xtypes::ExtensibilityKind::EXTENSIBLE;
virtual bool isValid() const { return !name.isEmpty(); }
bool operator<(const TopicTypeInformationBase &o) const
{
if (name != o.name)
return name < o.name;
return baseValueType < o.baseValueType;
}
};
struct TopicTypeInformation : public TopicTypeInformationBase
{
QVector<IdlColumnInformation> columns;
};
struct FunctionTopic
{
QString name;
QString requestTopicType;
QString replyTopicType;
QString requestTopicName;
QString replyTopicName;
bool routeInReplay = false;
};
class CODEGEN_BASE_EXPORT XmlReader final
{
public:
XmlReader();
bool readServiceFile(const QString &svcFile,
const QString &idlServiceInfoFile = QString(),
const XmlReader *parent = nullptr);
QString errorString() const
{
return m_errorString;
}
QString getServiceNamespace() const { return m_svcNamespace; }
QString getServiceAbbr() const { return m_svcAbbr; }
QStringList getGroupNames() const { return m_groupNames; }
int getPrio() const { return m_prio;}
QStringList getProcNames() const { return m_svcProcNames; }
bool hasCustomRecordReplayPlugin() const { return m_customRecordReplay; }
bool getDevMode() const { return m_svcDevMode; }
bool isServiceExerciseIndependent() const { return m_exerciseDependency == ExerciseDependency::Global; }
ExerciseDependency defaultExerciseDependency() const { return m_exerciseDependency; }
QMap<QString, QVector<QString> > getDbClass2TableMapping() const { return m_dbClassTables; }
// service structs
QMap<QString, QString> getStruct2BaseClassRelations() const { return m_struct2BaseClass; }
QVector<ServiceStructInformation> getStructToGenerate() const { return m_structsToGenerate; }
// idl structs
QMap<QString, TopicTypeInformation > getIdlStructInformation() const { return m_idlStructsToGenerate; }
QVector<TopicTypeInformation> getIdlStructToGenerate() const
{
QVector<TopicTypeInformation> res;
res.reserve(m_idlStructsToGenerate.size());
auto it = m_idlStructsToGenerate.constBegin();
while (it != m_idlStructsToGenerate.constEnd())
{
res.append(it.value());
++it;
}
return res;
}
// tables
QMap<QString, QSharedPointer<TableInformation> > getTables() const
{
return m_tables;
}
QMap<QString, QSharedPointer<ViewInformation> > getViews() const
{
return m_views;
}
QSharedPointer<TableInformation> getTable(const QString &table) const
{
return m_tables.value(table);
}
QVector<EnumInformation> getEnumerations() const
{
return m_enumerations;
}
QVector<IdlColumnInformation> getIdlColumns(const QString &structName) const
{
return m_idlStructsToGenerate.value(structName).columns;
}
QVector<FunctionTopic> getFunctionTopics() const
{
return m_functionTopics;
}
QVector<TopicTypeInformation> getExternalTopicTypes() const
{
return m_externalTopicTypes;
}
QString idlFileForService(const QString &serviceName) const
{
return getIdlFileForService(m_idlFiles, serviceName);
}
QString xmlFileForService(const QString &serviceName) const
{
return getIdlFileForService(m_xmlFiles, serviceName);
}
QMap<QString, bool> usedServices() const
{
return m_usedServices;
}
QVector<CustomServiceFunctionInformation> getCustomFunctions() const
{
return m_customFunctions;
}
TopicTypeInformation loadExternalTopicType(const QString &externalServiceName, const QString &topicTypeName);
EnumInformation getExternalEnumeration(const QString &externalServiceName, const QString &enumName, bool verbose);
static ExerciseDependency string2ExerciseDependency(const QString &str);
XmlReader(const XmlReader &) = delete;
XmlReader &operator=(const XmlReader &) = delete;
private:
void parseDBSserviceStructs(const QDomElement &rootElement);
void parseDBTables(const QDomElement &rootElement);
void parseDBViews(const QDomElement &rootElement);
void parseEnumerations(const QDomElement &rootElement);
void parseTopicTypes(const QDomElement &rootElement);
void parseFunctionTopics(const QDomElement &rootElement);
void parseCustomDtcFunctions(const QDomElement &rootElement);
static QString getAttribute(const QDomElement &element,
const QString &attributeName,
bool bOptional = false);
static bool getAttributeBool(const QDomElement &element,
const QString &attributeName,
bool bOptional, bool defaultValue);
static int getAttributeInt(const QDomElement &element,
const QString &attributeName,
bool bOptional = false, int defaultValue = 0);
ColumnInformation parseTableColumn(const QDomElement &column, const QString &tableName) const;
IdlColumnInformation parseIdlStructColumn(const QDomElement &column) const;
QSharedPointer<ForeignKeyInformation> parseForeignKey(const QDomElement &foreignKey) const;
ForeignKeyInformation::Endpoint parseForeignKeyEndpoint(const QString &foreignKeyName,
const QDomElement &endpoint) const;
ConstraintInformation parseConstraint(const QDomElement &constraint) const;
CustomServiceFunctionInformation parseCustomFunction(const QDomElement &customFunc, CustomServiceFunctionInformation::Type funcType) const;
DbNotifcationInformation parseDbNotification(const QDomElement &customFunc) const;
bool loadExternalService(const QString &serviceName);
void loadIdlInfofile(const QString &idlServiceInfoFile);
QString getIdlFileForService(const QMap<QString, QString> &data, const QString &serviceName) const;
QString serviceNameAbbreviation(const QString &serviceName) const;
void getServiceNameInfoFromFilePath(const QVector<QStringRef> &parts);
static QString serviceNamespaceFromFile(const QString &fn);
// The service name from the parsed xml file;
QString m_svcNamespace;
// Abbreviation of service name (inside xml file)
QString m_svcAbbr;
// Names for associated processes with this service (a single name in most cases)
QStringList m_svcProcNames;
// Extent the default Record/Replay plugin with own functions
bool m_customRecordReplay;
// depends this service on an exercise or is it a global on (or both)
ExerciseDependency m_exerciseDependency{ExerciseDependency::Exercise};
// Mapping DBClass -> used tables
QMap<QString, QVector<QString> > m_dbClassTables;
// structs to generate struct -> base class relation
QMap<QString, QString> m_struct2BaseClass;
QVector<ServiceStructInformation> m_structsToGenerate;
QMap<QString, TopicTypeInformation> m_idlStructsToGenerate;
QVector<FunctionTopic> m_functionTopics;
QVector<TopicTypeInformation> m_externalTopicTypes;
QVector<CustomServiceFunctionInformation> m_customFunctions;
QMap<QString, QSharedPointer<TableInformation> > m_tables;
QMap<QString, QSharedPointer<ViewInformation> > m_views;
QVector<EnumInformation> m_enumerations;
QString m_errorString;
QMap<QString, QSharedPointer<XmlReader> > m_externalXmlInformations;
QMap<QString, QString> m_idlFiles; // svcShortName, filename
QMap<QString, QString> m_xmlFiles; // svcShortName, filename
QMap<QString, bool> m_usedServices;
QMap<QString, QString> m_fullServiceName2Abbreviation;
QStringList m_groupNames;
int m_prio;
bool m_svcDevMode{false};
NGL::INGLLogger &m_logger;
};
} // namespace CodeGen
+21
View File
@@ -0,0 +1,21 @@
/**
* Export definitions
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QtCore/qglobal.h>
#ifndef CODEGEN_BASE_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define CODEGEN_BASE_EXPORT
# else
# if defined(MAKE_CODEGEN_BASE_LIB)
# define CODEGEN_BASE_EXPORT Q_DECL_EXPORT
# else
# define CODEGEN_BASE_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
+101
View File
@@ -0,0 +1,101 @@
log4j.reset=true
log4j.rootLogger=WARN, stdout
# CodeGeneration Tool
###########################################################
log4j.logger.CodeGen.main=WARN
log4j.logger.CodeGen=INFO
log4j.logger.CodeGen.Settings=INFO
log4j.logger.CodeGen.FtsImpl=INFO
log4j.logger.CodeGen.FtcImpl=INFO
# DTS/DTC, FTS/FTC
log4j.logger.Generic.DTS=INFO
log4j.logger.Generic.DTC=INFO
log4j.logger.Generic.FTS.Function=INFO
log4j.logger.Generic.FTC=INFO
log4j.logger.DTC.Subscriber=INFO
log4j.logger.DTC.SubscriberListener=INFO
log4j.logger.FTS=INFO
log4j.logger.FTC=WARN
# connext (request/reply feature)
log4j.logger.connext.requester=WARN
log4j.logger.connext.replier=WARN
# CodeGen
log4j.logger.CodeGen.XmlReader=WARN
# SqlGenerator
log4j.logger.CodeGen.SqlGenerator=WARN
log4j.logger.CodeGen.StructBuilder=INFO
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=9
log4j.appender.file.file=${LOG4QT_LOGDIR}/${LOG4QT_USER}_codeGen.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d %-5p %c |#| %m (%.50F:%L) %n
# Appnd_Console
###########################################################
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= %d %p %c %c{1} - %m (%-.30F:%L) %n
# Appnd_Syslog
###########################################################
log4j.appender.syslog=org.apache.log4j.SyslogAppender
log4j.appender.syslog.Threshold=ERROR
log4j.appender.syslog.SysLogHost=localhost
log4j.appender.syslog.Facility=LOCAL7
log4j.appender.syslog.layout=org.apache.log4j.PatternLayout
log4j.appender.syslog.layout.ConversionPattern= %-4r %-5p %c %x - %m
log4j.appender.syslog.LevelRangeFilter.AcceptOnMatch=true
log4j.appender.syslog.LevelRangeFilter.LevelMin=INFO
log4j.appender.syslog.LevelRangeFilter.LevelMax=FATAL
###########################################################
###########################################################
###########################################################
# 1) Available Levels in LOG4CXX Version 0.10.0:
# TRACE,
# DEBUG,
# INFO,
# WARN,
# ERROR
# FATAL
#
# d = date/time
# p = priority (Level)
# c = category/channel
# m = message
# F = file
# L = line
# n = platform dependent line separator character or characters.
# ....
#
# Examples for console:
# a)
# %d %p %c #%m# (%.50F:%L)%n
# date/time
# priority
# category/channel
# #message#
# (Maximal 50Character des Filename von rechts:Line)
# line separator
#
# b)
# %d (%.15F:%L) %-5p %c |#| %m%n
# date/time
# (Maximal 15Character des Filename von rechts:Line)
# priority, Right pad with spaces if the priority name is less than 5 characters long.
# category/channel
# message
# line separator
#
# Examples for files:
+477
View File
@@ -0,0 +1,477 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# $Id$
# $HeadURL$
# $Author$
#
from __future__ import print_function
import xml.etree.ElementTree as ET
import os
import re
import string
import sys
import time
class FtData(object):
def __init__( self ):
self.name = ''
self.namespace = ''
self.requestTopicType = ''
self.requestTopicName = ''
self.replyTopicType = ''
self.replyTopicName = ''
def fqnVar( self, name ):
if name.find('::') < 0:
name = self.namespace + '::' + name
return name
def stripNamespace( self, str ):
return str.split('::')[-1]
def getReplyTopicName( self ):
return 'LTS_' + self.stripNamespace( self.replyTopicName ).replace( '::', '_' )
def getReplyTopicNameType( self, withNamespace = True ):
var = self.replyTopicName
return self.fqnVar( var ) if withNamespace else self.stripNamespace( var )
def getRequestTopicName( self ):
return 'LTS_' + self.stripNamespace( self.requestTopicName ).replace( '::', '_' )
def getRequestTopicNameType( self, withNamespace = True ):
var = self.requestTopicName
return self.fqnVar( var ) if withNamespace else self.stripNamespace( var )
def getReplyTopicType( self ):
return self.fqnVar( self.replyTopicType )
def getRequestTopicType( self ):
return self.fqnVar( self.requestTopicType )
def needReplyTopicTypedef( self ):
return self.getReplyTopicNameType() != self.getReplyTopicType()
def needRequestTopicTypedef( self ):
return self.getRequestTopicNameType() != self.getRequestTopicType()
class GuiPluginRights(object):
def __init__( self ):
self.name = ''
self.offset = 0
self.groups = []
class GuiPluginRightsGroup(object):
def __init__( self ):
self.name = ''
self.desc = ''
self.values = []
class FileParser(object):
def __init__( self ):
self.filename = ''
self.service_abbr = ''
self.module_name = ''
self.struct_names = []
self.ft_enums = []
self.ft_replies = []
self.ft_data = dict()
self.op_right_offset = 0
self.op_rights = []
def build(self, path):
""" Parse the whole stuff ... """
self.filename = path
self.read_file_xml(self.filename)
def read_file_xml(self, filename):
""" Read the given xml service file"""
"""print >> sys.stdout, 'Reading xml file %s' % filename"""
xmlTree = ET.parse(filename)
root = xmlTree.getroot()
self.module_name = root.attrib['namespace']
self.service_abbr = root.attrib['abbreviation'].lower()
for child in root.iter('topicType'):
struct_name = child.attrib['name']
if 'isDataObject' in child.attrib and int(child.attrib['isDataObject']) != 0:
self.struct_names.append(struct_name)
for child in root.iter('functionTopic'):
ft_name = child.attrib['name']
self.ft_enums.append(ft_name) # solange es noch entityModel.idl gibt ...
ft_data = FtData()
ft_data.name = ft_name
ft_data.namespace = self.module_name
ft_data.requestTopicType = child.attrib['requestTopicType']
ft_data.requestTopicName = child.attrib['requestTopicName']
ft_data.replyTopicType = child.attrib['replyTopicType']
ft_data.replyTopicName = child.attrib['replyTopicName']
self.ft_data[ft_name] = ft_data
for child in root.iter('operatorRights'):
self.op_right_offset = int(child.attrib['offset'])
for pluginChild in child.iter('guiPlugin'):
r = GuiPluginRights()
r.name = pluginChild.attrib['name']
r.offset = int(pluginChild.attrib['offset'])
r.groups = [];
for groups in pluginChild.iter('operatorRightGroup'):
g = GuiPluginRightsGroup();
g.name = groups.attrib['name']
g.desc = groups.attrib['description'] if 'description' in groups.attrib else ''
for right in groups.iter('operatorRight'):
desc = right.attrib['description'] if 'description' in right.attrib else ''
g.values.append((right.attrib['right'], int(right.attrib['value']), desc))
r.groups.append(g)
self.op_rights.append(r)
def outputHeader( outfile, parser, description, with_dds_include = True, with_pragma = True ):
outfile.write('\n')
outfile.write('/**\n')
outfile.write('* %s definitions\n' % description)
outfile.write('*/\n')
outfile.write('\n')
outfile.write('// warning: this file is generated with command\n')
outfile.write('// %s\n' % ( " ".join(sys.argv) ))
outfile.write('// at %s\n' % time.strftime( '%c' ))
outfile.write('// it will be overwritten by next run - do not modify!\n')
outfile.write('\n')
if with_pragma:
outfile.write('#pragma once\n')
outfile.write('\n')
if with_dds_include:
outfile.write('#include "%s.hpp"\n' % ( parser.service_abbr ))
class DtsGenerator(object):
def generate( self, outfile, parser ):
outputHeader( outfile, parser, 'DTS' )
outfile.write('\n')
outfile.write('/**\n')
outfile.write(' * Type definitions \n')
outfile.write(' * => DataWriterObjects (DWOs)\n')
outfile.write(' * => DataTopicServers (DTSs)\n')
outfile.write(' */\n')
outfile.write('\n')
outfile.write('namespace DDSGeneric\n')
outfile.write('{\n')
outfile.write('template <typename T>\n')
outfile.write('class GenericDataWriter;\n')
outfile.write('}\n')
outfile.write('\n')
outfile.write('namespace DTS\n')
outfile.write('{\n')
outfile.write(' template<typename T> class GenericDts;\n')
outfile.write(' template<typename T> class GenericDtsImpl;\n')
outfile.write('\n')
for struct_name in sorted(parser.struct_names):
# Add ProjectName (from idl file)
topic_name = "%s::%s" % ( parser.module_name, struct_name )
dwo_name = "Dwo%s" % ( struct_name )
outfile.write(' // Topic %s\n' % ( topic_name ))
outfile.write(' // Interface\n')
outfile.write(' using %s = GenericDts<%s>;\n' % ( struct_name, topic_name ))
outfile.write(' // Interface impl\n')
outfile.write(' using %s = DDSGeneric::GenericDataWriter<%s>;\n' % ( dwo_name, topic_name ))
outfile.write(' using %sImpl = GenericDtsImpl<%s>;\n' % ( struct_name, dwo_name ))
outfile.write('\n')
outfile.write('} // namespace DTS\n')
outfile.write('\n')
class DtcGenerator(object):
def generate( self, outfile, parser ):
outputHeader( outfile, parser, 'DTC' )
outfile.write('\n')
outfile.write('/**\n')
outfile.write(' * Type definitions \n')
outfile.write(' * => DataTopicClients (DTCs)\n')
outfile.write(' */\n')
outfile.write('\n')
outfile.write('namespace DDSGeneric\n')
outfile.write('{\n')
outfile.write('template <typename T>\n')
outfile.write('class GenericDataReader;\n')
outfile.write('template <typename T>\n')
outfile.write('class GenericDataReaderContentFilter;\n')
outfile.write('class DynamicDataReader;\n')
outfile.write('}\n')
outfile.write('\n')
outfile.write('namespace DTC\n')
outfile.write('{\n')
outfile.write(' template<typename T> class GenericDtc;\n')
outfile.write(' template<typename T> class GenericDtcImpl;\n')
outfile.write(' template<typename T> class GenericDtcSampleInfo;\n')
outfile.write(' template<typename T> class GenericDtcImplSampleInfo;\n')
outfile.write('\n')
for struct_name in sorted(parser.struct_names):
# Add ProjectName (from idl file)
topic_name = "%s::%s" % ( parser.module_name, struct_name )
dro_name = "Dro%s" % ( struct_name )
dro_name_cf = "Dro%sCF" % ( struct_name )
outfile.write(' // Topic %s\n' % ( topic_name ))
outfile.write(' using %s = DDSGeneric::GenericDataReader<%s>;\n' % ( dro_name, topic_name ))
outfile.write(' using %s = GenericDtc<%s>;\n' % ( struct_name, dro_name ))
outfile.write(' using %sImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name ))
outfile.write(' using %s = DDSGeneric::GenericDataReaderContentFilter<%s>;\n' % ( dro_name_cf, topic_name ))
outfile.write(' using %sCF = GenericDtc<%s>;\n' % ( struct_name, dro_name_cf ))
outfile.write(' using %sCFImpl = GenericDtcImpl<%s>;\n' % ( struct_name, dro_name_cf ))
outfile.write(' namespace SampleInfo {\n')
outfile.write(' using %s = GenericDtcSampleInfo<%s>;\n' % ( struct_name, dro_name ))
outfile.write(' using %sImpl = GenericDtcImplSampleInfo<%s>;\n' % ( struct_name, dro_name ))
outfile.write(' }\n')
outfile.write('\n')
outfile.write('} // namespace DTC\n')
outfile.write('\n')
def printFtTopicTypeNames( outfile, ft_data ):
outfile.write(' template<> struct TopicNames<%s>' % ft_data.name)
outfile.write(' {\n')
outfile.write(' static constexpr const char *functionTopicName() { return "%s"; }\n' % ft_data.name)
outfile.write(' static constexpr const char *replyTopicName() { return "%s"; }\n' % ft_data.getReplyTopicName())
outfile.write(' static constexpr const char *replyTopicTypeName() { return "%s"; }\n' % ft_data.getReplyTopicNameType( True ))
outfile.write(' static constexpr const char *requestTopicName() { return "%s"; }\n' % ft_data.getRequestTopicName())
outfile.write(' static constexpr const char *requestTopicTypeName() { return "%s"; }\n' % ft_data.getRequestTopicNameType( True ))
outfile.write(' };\n')
def printFtTypedefs( outfile, ft_data ):
if ft_data.needRequestTopicTypedef():
outfile.write(' using %s = %s;\n' % ( ft_data.getRequestTopicNameType( False ), ft_data.getRequestTopicType() ))
if ft_data.needReplyTopicTypedef():
outfile.write(' using %s = %s;\n' % ( ft_data.getReplyTopicNameType( False ), ft_data.getReplyTopicType() ))
class FtsGenerator(object):
def generate( self, outfile, parser):
outputHeader( outfile, parser, 'FTS' )
outfile.write('\n')
outfile.write('namespace FTS\n')
outfile.write('{\n')
outfile.write('template <typename REQ, typename REP, typename DUMMY> class GenericFtsImpl;\n')
outfile.write('template<typename GENERIC_FTS> struct TopicNames;\n')
outfile.write('}\n')
outfile.write('namespace %s\n' % ( parser.module_name ))
outfile.write('{\n')
outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n')
for ft_name in sorted(parser.ft_enums):
printFtTypedefs( outfile, parser.ft_data[ft_name] )
outfile.write('} // namespace %s\n' % ( parser.module_name ))
outfile.write('\n')
outfile.write('\n')
outfile.write('namespace FTS\n')
outfile.write('{\n')
outfile.write(' // TypeDefinitions for GenericFtsImpl\n')
for ft_name in sorted(parser.ft_enums):
ft_data = parser.ft_data[ft_name]
dummy = '%sTemplateHelperDummy' % ft_data.name;
outfile.write(' class %s;\n' % dummy)
outfile.write(' using %s = GenericFtsImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy))
printFtTopicTypeNames( outfile, ft_data )
outfile.write('\n')
outfile.write('} // namespace FTS\n')
outfile.write('\n')
class FtcGenerator(object):
def generate( self, outfile, parser ):
outputHeader( outfile, parser, 'FTC' )
outfile.write('\n')
outfile.write('namespace FTC\n')
outfile.write('{\n')
outfile.write('template <typename REQ, typename REP, typename DUMMY> class GenericFtcImpl;\n')
outfile.write('template<typename GENERIC_FTC> struct TopicNames;\n')
outfile.write('}\n')
outfile.write('namespace %s\n' % ( parser.module_name ))
outfile.write('{\n')
outfile.write(' // TypeDefinitions for Replies that are not implemented => using Core::FunctionReply\n')
for ft_name in sorted(parser.ft_enums):
printFtTypedefs( outfile, parser.ft_data[ft_name] )
outfile.write('} // namespace %s\n' % ( parser.module_name ))
outfile.write('\n')
outfile.write('\n')
outfile.write('namespace FTC\n')
outfile.write('{\n')
outfile.write(' // TypeDefinitions for GenericFtcImpl\n')
for ft_name in sorted(parser.ft_enums):
ft_data = parser.ft_data[ft_name]
dummy = '%sTemplateHelperDummy' % ft_data.name;
outfile.write(' class %s;\n' % dummy)
outfile.write(' using %s = GenericFtcImpl<%s, %s, %s>;\n' % (ft_data.name, ft_data.getRequestTopicType(), ft_data.getReplyTopicNameType(), dummy))
printFtTopicTypeNames( outfile, ft_data )
outfile.write('\n')
outfile.write('} // namespace FTC\n')
outfile.write('\n')
class OperatorRights(object):
def generate( self, outfile, parser ):
outputHeader( outfile, parser, 'Operator Rights', False )
outfile.write('namespace %s \n' % parser.module_name)
outfile.write('{\n')
outfile.write(' static const int Offset = %s; \n' % parser.op_right_offset)
outfile.write('}\n\n')
outfile.write('namespace OperatorRights\n')
outfile.write('{\n')
globalOffset = parser.op_right_offset << 24
for r in parser.op_rights:
outfile.write('enum %s\n' % r.name)
outfile.write('{\n')
offsetPlugin = globalOffset + (r.offset << 16)
groupCount = -1
for group in r.groups:
groupCount = groupCount + 1
groupOffset = offsetPlugin + (groupCount << 8)
for k,v,d in group.values:
v += groupOffset
outfile.write(' %s = %s,\n' % (k, hex(v)))
outfile.write('};\n')
outfile.write('\n')
outfile.write('enum Groups_%s\n' % parser.module_name)
outfile.write('{\n')
pattern = re.compile('[\W]+')
for r in parser.op_rights:
offsetPlugin = globalOffset + (r.offset << 16)
for group in r.groups:
groupOffset = offsetPlugin + (groupCount << 8)
name = pattern.sub('', group.name)
outfile.write(' Group%s = %s,\n' % (name, hex(groupOffset)))
outfile.write('};\n')
outfile.write('\n')
outfile.write('enum Plugins_%s\n' % parser.module_name)
outfile.write('{\n')
for r in parser.op_rights:
offsetPlugin = globalOffset + (r.offset << 16)
name = pattern.sub('', r.name)
outfile.write(' Plugin%s = %s,\n' % (name, hex(offsetPlugin)))
outfile.write('};\n')
outfile.write('} // namespace OperatorRights\n')
class OperatorRightsCpp(object):
def generate( self, outfile, parser ):
outputHeader( outfile, parser, 'Operator Rights (source)', False, False )
outfile.write('#include "%s_operator_rights.h"\n' % parser.service_abbr)
outfile.write('#include <QCoreApplication>\n\n\n')
if len(parser.op_rights) == 0:
return
outfile.write('\nvoid doNothing()\n{\n const char * txt[] {\n')
disamb = 'OperatorRights'
globalOffset = parser.op_right_offset << 24
for r in parser.op_rights:
offsetPlugin = globalOffset + (r.offset << 16)
groupCount = -1
for group in r.groups:
groupCount = groupCount + 1
groupOffset = offsetPlugin + (groupCount << 8)
if group.name != '':
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.name))
if group.desc != '':
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, group.desc))
for aclName, aclId, aclDescr in group.values:
outfile.write(' QT_TRANSLATE_NOOP("%s", "%s"),\n' % (disamb, aclDescr))
outfile.write(' };\n Q_UNUSED(txt);\n}\n')
class OperatorRightsSql(object):
def generate( self, outfile, parser ):
outfile.write('BEGIN;\n')
globalOffset = parser.op_right_offset << 24
if globalOffset > 0:
outfile.write('--- Offset: %s = %s\n' % (globalOffset, hex(globalOffset)))
groupId = globalOffset;
for r in parser.op_rights:
offsetPlugin = globalOffset + (r.offset << 16)
groupCount = -1
for group in r.groups:
groupCount = groupCount + 1
groupOffset = offsetPlugin + (groupCount << 8)
outfile.write('\n--- %s - %s = %s\n' % (group.name, hex(groupOffset), groupOffset))
str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 4, false);\n"
outfile.write(str % (groupOffset, group.name, group.desc))
for aclName, aclId, aclDescr in group.values:
aclId += groupOffset
outfile.write('--- Base: %s, %s - %s = %s\n' % (groupOffset, aclName, aclId, hex(aclId)))
str = "INSERT INTO opm.opm_access_control_level (id, name, description) VALUES (%s, '%s', '%s');\n";
outfile.write(str % (aclId, aclName, aclDescr));
str = "INSERT INTO opm.opm_group (id, name, description, type_id, is_hidden) VALUES (%s, '%s', '%s', 1, true);\n";
outfile.write(str % (groupId, aclName, aclDescr));
str = "INSERT INTO opm.opm_acl_allow (group_id, acl_id) VALUES (%s, %s); -- group_id is: %s = %s\n";
outfile.write(str % (groupId, aclId, groupId, hex(groupId)));
str = "INSERT INTO opm.opm_group_group (group_id, is_member_of_group_id) VALUES (%s, %s); -- %s\n"
outfile.write(str % (groupOffset, groupId, group.name))
groupId = groupId + 1
outfile.write('\n')
outfile.write('END;\n')
def generate( parser, generator, out_filename ):
try:
outfile = open(out_filename, 'w')
except:
print("Could not open %s for writing" % out_filename, file=sys.stderr)
sys.exit(4)
generator.generate(outfile, parser)
outfile.close()
def usage( error ):
print(error, file=sys.stderr)
print("Usage: %s DTS|DTC|FTS|FTC|SQL <service_file.xml> <outfile> " % sys.argv[0], file=sys.stderr)
print(" %s ALL <service_file.xml> <outdir> " % sys.argv[0], file=sys.stderr)
print("i.e.: %s DTS exm_service.xml DtsTypeDefinitions.h" % sys.argv[0], file=sys.stderr)
print("call: '%s'" % " ".join(sys.argv), file=sys.stderr)
sys.exit(1)
def main():
if len(sys.argv) != 4:
usage("Invalid argument count")
option_str = sys.argv[1]
xml_filename = sys.argv[2]
parser = FileParser()
parser.build(xml_filename)
if option_str == "DTS":
generate(parser, DtsGenerator(), sys.argv[3])
elif option_str == "DTC":
generate(parser, DtcGenerator(), sys.argv[3])
elif option_str == "FTS":
generate(parser, FtsGenerator(), sys.argv[3])
elif option_str == "FTC":
generate(parser, FtcGenerator(), sys.argv[3])
elif option_str == "SQL":
out_dir = sys.argv[3]
generate(parser, OperatorRightsSql(), os.path.join(out_dir, parser.service_abbr + '_operator_rights.sql'))
elif option_str == "ALL":
out_dir = sys.argv[3]
fn = parser.service_abbr + '_%s_types.h'
generate(parser, DtsGenerator(), fn % 'dts')
generate(parser, DtcGenerator(), fn % 'dtc')
generate(parser, FtsGenerator(), fn % 'fts')
generate(parser, FtcGenerator(), fn % 'ftc')
generate(parser, OperatorRights(), parser.service_abbr + '_operator_rights.h')
generate(parser, OperatorRightsCpp(), parser.service_abbr + '_operator_rights.cpp')
else:
usage("invalid option '%s'" % option_str)
sys.exit(0)
if __name__ == "__main__":
main()
File diff suppressed because it is too large. Load diff
+177
View File
@@ -0,0 +1,177 @@
/**
* @RDE_HEADER@
* main for ExerciseManagement
* $Id$
* $HeadURL$
* $Author$
*/
#include "StructBuilder.h"
#include "XmlReader.h"
#include "DaoGeneratorGrantlee.h"
#include "DatabaseGeneratorGrantlee.h"
#include "CppTopicGeneratorGrantlee.h"
#include "SqlGenerator.h"
#include <nglapplication/nglcoreapplication.h>
#include <QDir>
#include <NGLLogger/INGLLogger.h>
#include <LtsCommandLineParser/LtsCommandLineParser.h>
// ------------------------------------------------------------------
class CmdLineParser : public LtsCommandLineParser::LtsCommandLineParser
{
public:
CmdLineParser()
: LtsCommandLineParser(LtsCommandLineParser::LtsCommandLineParser::None)
{
addOption({ QStringList() << QStringLiteral("s") << QStringLiteral("servicefile"),
QStringLiteral("full path to the service xml-file"),
QStringLiteral("service xml-file"),
QStringLiteral("/home")
});
addOption({ QStringList() << QStringLiteral("a") << QStringLiteral("action"),
QStringLiteral("the action to execute"),
QStringLiteral("DATABASE|DATABASE_SERVER|IDL|DDS_TOPICS"),
QStringLiteral("/0")
});
addOption({ QStringList() << QStringLiteral("o") << QStringLiteral("outdir"),
QStringLiteral("the output directory where to put the generated files"),
QStringLiteral("output directory"),
QStringLiteral("/home")
});
addOption({ QStringList() << QStringLiteral("i") << QStringLiteral("idlinfo"),
QStringLiteral("the info file with all available idl service files"),
QStringLiteral("info file for idl"),
QStringLiteral("/0")
});
}
bool init()
{
if (!LtsCommandLineParser::init())
return false;
if (serviceFile().isEmpty() || action().isEmpty() || outDir().isEmpty())
{
showHelp();
return false;
}
return true;
}
QString serviceFile() const { return value(QStringLiteral("s")); }
QString action() const { return value(QStringLiteral("a")); }
QString outDir() const { return value(QStringLiteral("o")); }
QString idlInfoFile() const { return value(QStringLiteral("i")); }
};
int main(int argc, char **argv)
{
NGLCoreApplication app(argc, argv);
app.createLogger("CodeGen", "codegen_log4cxx.props", true /* don't spam while compilation of the project */);
NGL::INGLLogger &m_logger = app.baseLogger();
CmdLineParser options;
if (!options.init())
return -1;
QScopedPointer<CodeGen::XmlReader> reader(new CodeGen::XmlReader);
DEBUG_LOG(m_logger) << "ServiceFile: " << options.serviceFile();
DEBUG_LOG(m_logger) << "IdlInfoFile: " << options.idlInfoFile();
if (!reader->readServiceFile(options.serviceFile(), options.idlInfoFile()))
{
ERROR_LOG(m_logger) << "Error parsing service file '" << options.serviceFile()
<< ": " << reader->errorString();
return 2;
}
QDir dir(options.outDir());
DEBUG_LOG(m_logger) << "Directory: " << options.outDir();
if (!dir.exists() && !dir.mkpath(options.outDir()))
{
ERROR_LOG(m_logger) << "Directory '" << options.outDir()
<< "' does not exist and can not be created!";
return 3;
}
QScopedPointer<CodeGen::StructBuilder> structBuilder(new CodeGen::StructBuilder(reader.data()));
if (!structBuilder->buildStructures())
{
ERROR_LOG(m_logger) << "Error building up structures";
return 4;
}
const QString action = options.action().toLower();
//INFO_LOG(m_logger) << "action = " << action;
QScopedPointer<CodeGen::GeneratorGrantleeBase> generator;
if (action == QStringLiteral("dao"))
generator.reset(new CodeGen::DaoGeneratorGrantlee);
else if (action == QStringLiteral("database"))
generator.reset(new CodeGen::DatabaseGeneratorGrantlee(false));
else if (action == QStringLiteral("database_server"))
generator.reset(new CodeGen::DatabaseGeneratorGrantlee(true));
else if (action == QStringLiteral("dds_topics"))
generator.reset(new CodeGen::CppTopicGeneratorGrantlee);
else if (action == QStringLiteral("sql"))
{
CodeGen::SqlGenerator sql(reader.data(), structBuilder.data());
if (!sql.generateData(35))
return 8;
// .sql will be appended in method
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise))
{
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
return 10;
}
if (!sql.generateData(42))
return 8;
// .sql will be appended in method
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Exercise))
{
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
return 10;
}
// recreate data for exercise independent statements
sql.generateData(0);
if (!sql.writeSql(dir.absolutePath() + QLatin1Char('/'), CodeGen::ExerciseDependency::Global))
{
ERROR_LOG(m_logger) << "Error while writing sql information for service " << sql.getServiceAbbr();
return 10;
}
return 0;
}
else
{
ERROR_LOG(m_logger) << "Invalid action '" << options.action() << "' given!";
return 5;
}
if (!generator->init(reader.data(), structBuilder.data()))
{
ERROR_LOG(m_logger) << "Error initializing generator!";
return 6;
}
//INFO_LOG(m_logger) << "init generator ";
//INFO_LOG(m_logger) << "########## options.arguments.size(): " << options.arguments().size();
const auto posArgs = options.positionalArguments();
for (const QString &file : posArgs)
{
//INFO_LOG(m_logger) << "GenerateData for " << file;
if (!generator->generateData(file, dir.absolutePath()))
return 7;
}
return 0;
}
+163
View File
@@ -0,0 +1,163 @@
/**
* @RDE_HEADER@
* Gui to edit grantlee templates
* $Id$
* $HeadURL$
* $Author$
*/
#include "main_grantlee_gui.h"
#include "ui_main_grantlee_gui.h"
#include "StructBuilder.h"
#include "XmlReader.h"
#include "CppTopicGeneratorGrantlee.h"
#include "DatabaseGeneratorGrantlee.h"
#include <nglapplication/nglapplication.h>
#include <NGLLogger/INGLLogger.h>
#include <QFileDialog>
#include <QMessageBox>
#include <grantlee/engine.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_xmlReader(nullptr)
, m_structBuilder(nullptr)
, m_generator(new CodeGen::CppTopicGeneratorGrantlee)
, m_engine(new Grantlee::Engine)
, m_ui(new Ui_MainGrantleeGui)
{
m_idlServiceInfoFile = QStringLiteral("/home/chehrlic/TDS/TDS-build-product-clang/service_idl_files.txt");
m_ui->setupUi(this);
connect(m_ui->actionExit, &QAction::triggered,
this, &MainWindow::close);
connect(m_ui->actionSelect_Data_Source, &QAction::triggered,
this, &MainWindow::onSelectDataSource);
connect(m_ui->actionOpen_Template, &QAction::triggered,
this, &MainWindow::onOpenTemplate);
connect(m_ui->actionSave_Template, &QAction::triggered,
this, &MainWindow::onSaveTemplate);
connect(m_ui->pbRefresh, &QPushButton::clicked,
this, &MainWindow::onRefreshOutput);
connect(m_ui->actionDatabaseGenerator, &QAction::triggered,
this, &MainWindow::onGeneratorDb);
connect(m_ui->actionDDS_Generator, &QAction::triggered,
this, &MainWindow::onGeneratorDds);
}
MainWindow::~MainWindow()
{
delete m_xmlReader;
delete m_structBuilder;
delete m_generator;
delete m_engine;
delete m_ui;
}
static QLatin1String s_startDir("/home/chehrlic/TDS/TDS-source-product/Lts/");
void MainWindow::onSelectDataSource()
{
const QString dataSource = QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Service Files (*.xml)"));
if (dataSource.isEmpty())
return;
delete m_xmlReader;
m_xmlReader = new CodeGen::XmlReader;
if (!m_xmlReader->readServiceFile(dataSource, m_idlServiceInfoFile))
{
QMessageBox::warning(this, tr("Invalid data source"), tr("The selected data source is not valid: %1").arg(m_xmlReader->errorString()));
return;
}
delete m_structBuilder;
m_structBuilder = new CodeGen::StructBuilder(m_xmlReader);
if (!m_structBuilder->buildStructures())
{
QMessageBox::warning(this, tr("Invalid data source"), tr("Error building structures from data"));
return;
}
m_ui->teOuput->clear();
m_generator->init(m_xmlReader, m_structBuilder);
QMessageBox::information(this, tr("Data source"), tr("Successfully loaded data source"));
}
void MainWindow::onOpenTemplate()
{
const QString templateFile = QFileDialog::getOpenFileName(this, tr("Select data source"), s_startDir, tr("Templates (*.grantlee)"));
if (templateFile.isEmpty())
return;
QFile f(templateFile);
if (!f.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, tr("Error reading template file"), tr("Can not read file: %1").arg(f.errorString()));
return;
}
const QString content = QString::fromUtf8(f.readAll());
m_ui->teInput->setText(content);
m_templateFile = templateFile;
}
void MainWindow::onSaveTemplate()
{
QFile f(m_templateFile);
if (!f.open(QIODevice::WriteOnly))
{
QMessageBox::warning(this, tr("Error saving template file"), tr("Can not save file: %1").arg(f.errorString()));
return;
}
f.write(m_ui->teInput->toPlainText().toUtf8());
f.close();
QMessageBox::information(this, tr("Saving template file"), tr("Successfully saved template to %1").arg(m_templateFile));
}
void MainWindow::onRefreshOutput()
{
const QString in = m_ui->teInput->toPlainText();
if (!m_generator->setTemplate(QString(), in))
{
m_ui->teOuput->setText(m_generator->errorString());
return;
}
m_ui->teOuput->setText(m_generator->render());
}
void MainWindow::onGeneratorDb()
{
if (dynamic_cast<CodeGen::DatabaseGeneratorGrantlee *>(m_generator))
return;
delete m_generator;
m_generator = new CodeGen::DatabaseGeneratorGrantlee(false);
if (m_xmlReader && m_structBuilder)
m_generator->init(m_xmlReader, m_structBuilder);
}
void MainWindow::onGeneratorDds()
{
if (dynamic_cast<CodeGen::CppTopicGeneratorGrantlee *>(m_generator))
return;
delete m_generator;
m_generator = new CodeGen::CppTopicGeneratorGrantlee;
if (m_xmlReader && m_structBuilder)
m_generator->init(m_xmlReader, m_structBuilder);
}
int main(int argc, char **argv)
{
NGLApplication app(argc, argv);
app.createLogger("CodeGen", "codegen_log4cxx.props");
NGL::INGLLogger &m_logger = app.baseLogger();
STARTUP_LOG(m_logger);
int iRet = 0;
{
QScopedPointer<MainWindow> mw(new MainWindow);
mw->show();
iRet = app.exec();
}
SHUTDOWN_LOG(m_logger);
return iRet;
}
+44
View File
@@ -0,0 +1,44 @@
/**
* @RDE_HEADER@
* Gui to edit grantlee templates
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QMainWindow>
namespace CodeGen
{
class XmlReader;
class StructBuilder;
class GeneratorGrantleeBase;
}
namespace Grantlee
{
class Engine;
}
class Ui_MainGrantleeGui;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private Q_SLOTS:
void onSelectDataSource();
void onOpenTemplate();
void onSaveTemplate();
void onRefreshOutput();
void onGeneratorDb();
void onGeneratorDds();
private:
CodeGen::XmlReader *m_xmlReader;
CodeGen::StructBuilder *m_structBuilder;
CodeGen::GeneratorGrantleeBase *m_generator;
Grantlee::Engine *m_engine;
QString m_idlServiceInfoFile;
QString m_templateFile;
Ui_MainGrantleeGui *m_ui;
};
+140
View File
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainGrantleeGui</class>
<widget class="QMainWindow" name="MainGrantleeGui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>838</width>
<height>532</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Input</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="teInput"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Output</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pbRefresh">
<property name="text">
<string>Refresh Output</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QTextBrowser" name="teOuput"/>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>838</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>Fi&amp;le</string>
</property>
<addaction name="actionSelect_Data_Source"/>
<addaction name="actionOpen_Template"/>
<addaction name="actionSave_Template"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuGenerator">
<property name="title">
<string>&amp;Generator</string>
</property>
<addaction name="actionDatabaseGenerator"/>
<addaction name="actionDDS_Generator"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuGenerator"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionExit">
<property name="text">
<string>&amp;Exit</string>
</property>
</action>
<action name="actionOpen_Template">
<property name="text">
<string>&amp;Open Template</string>
</property>
</action>
<action name="actionSelect_Data_Source">
<property name="text">
<string>&amp;Select Data Source</string>
</property>
</action>
<action name="actionSave_Template">
<property name="text">
<string>Sa&amp;ve Template</string>
</property>
</action>
<action name="actionDatabaseGenerator">
<property name="text">
<string>&amp;DatabaseGenerator</string>
</property>
</action>
<action name="actionDDS_Generator">
<property name="text">
<string>DDS &amp;Generator</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+39
View File
@@ -0,0 +1,39 @@
/**
* @RDE_HEADER@
* Template for idl struct definitions
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-08-05
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}.idl
#ifndef {{ namespace_abbr }}_idl_included
#define {{ namespace_abbr }}_idl_included
//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux
//@copy #include "{{ namespace_abbr }}_enums.hpp"
#include "{{ namespace_abbr }}_enums.idl"
{% for ext_service in ext_services.member %}
//@copy #include <{{ ext_service.baseFilePath }}.hpp>
#include <{{ ext_service.baseFilePath }}.idl>
{% endfor %}
module {{ namespace }}
{
{% for idl_struct in idl_structs %}
{% if idl_struct.comment %} // {{ idl_struct.comment }} {% endif %}
struct {{ idl_struct.name }} {% if idl_struct.baseValueType %}: {{ idl_struct.baseValueType }} {% endif %}
{ {% for idl_member in idl_struct.member %}
{{ idl_member.type }} {{ idl_member.name }}; {{ idl_member.isKey }} {{ idl_member.isOptional }} {% endfor %}
}; //@Extensibility {{ idl_struct.extensibility }}
{% endfor %}
}; // namespace {{ namespace }}
#endif
{% endautoescape %}
+204
View File
@@ -0,0 +1,204 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id: svc_db_impl.cpp.grantlee 56901 2022-04-22 04:21:30Z karwat $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.cpp.grantlee $
* $Author: karwat $
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dao.cpp
#include "{{ namespace_abbr }}_dao.h"
{% if custom_db_procs %}
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h>
{% endif %}
#include <ngldb/dbtransaction.h>
#include <ngldb/nsqlquery.h>
#include <ngldb/dbconnection.h>
#include <ngldb/sql_query_janitor.h>
#include <QtSql/QSqlError>
#include <QVariant>
#include <NGLLogger/INGLLogger.h>
namespace {{ namespace }}
{
Dao::Dao(int exerciseId)
: m_query(nullptr)
, m_exerciseId(exerciseId)
, m_bEmitMessageOnError(true)
, m_bCanHandleSchemas(true)
, m_ownDatabaseConnection(true)
, m_logger(getLogger("Db"))
{}
Dao::~Dao()
{
m_query = nullptr;
{% if has_db_notifications %}
QSqlDriver *driver = m_dbConnection.driver();
if (driver && m_dbConnection.isOpen())
{
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}"));
{% endfor %}{% endfor %} }
{% endif %}
if (m_ownDatabaseConnection && m_dbConnection.isOpen())
m_dbConnection.close();
}
bool Dao::init(const QString &dbUser,
const QString &dbPasswd,
const QString &dbDriver,
const QString &dbHost,
const QString &dbName)
{
m_ownDatabaseConnection = true;
m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName());
return initInternal();
}
bool Dao::initInternal()
{
QSqlDriver *driver = m_dbConnection.driver();
if (!driver)
{
WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!";
return false;
}
const QString driverName = m_dbConnection.driverName();
DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName()
<< ", schema: " << schemaName() << ", used driver: " << driverName;
if (!m_dbConnection.isOpen())
{
WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface";
return false;
}
if (driverName == QLatin1String("QSQLITE"))
m_bCanHandleSchemas = false;
{% if has_db_notifications %}
if (!driver->hasFeature(QSqlDriver::EventNotifications))
{
WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!";
}
else
{
connect(driver, QOverload<const QString&,QSqlDriver::NotificationSource,const QVariant&>::of(&QSqlDriver::notification),
this, &Dao::onNotification);
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
}
{% endif %}
m_query = std::unique_ptr<NSqlQuery>(new NSqlQuery(m_dbConnection, m_logger));
return true;
}
QString Dao::schemaName() const
{
if (!m_bCanHandleSchemas)
return QString();
if (m_exerciseId <= 0)
return QStringLiteral("{{ namespace_abbr }}");
return DBCommon::getExerciseSchemaName(m_exerciseId);
}
QString Dao::dbUser() const
{
if (m_exerciseId > 0)
return QStringLiteral("ets_ex_00");
else
return QStringLiteral("ets_") + schemaName();
}
void Dao::setEmitMessageOnError(bool bEmit)
{
m_bEmitMessageOnError = bEmit;
}
QString Dao::getTableName(const QString &tableName) const
{
if (!m_bCanHandleSchemas)
return tableName;
if (m_exerciseId > 0)
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
return QStringLiteral("{{ namespace_abbr }}.") + tableName;
}
QSqlError Dao::getLastError() const
{
return m_lastError;
}
{% if has_db_notifications %}
void Dao::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
{
DEBUG_LOG(m_logger) << "Notification source = " << source;
if (source == QSqlDriver::SelfSource)
return;
// search coresponding signal
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
if (name == QStringLiteral("{{ db_notify }}"))
{
emit signal{{ db_notify }}(payload.toInt());
return;
}
{% endfor %}{% endfor %}
}
{% endif %}
{% for cpp_struct in cpp_structs %}
//
// --> Struct: Database::{{ cpp_struct.name }}
//
bool Dao::select(QVector<Database::{{ cpp_struct.name }}> &dataVec, const QString &where)
{
SqlQueryJanitor janitor(m_query);
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
{
dataVec.push_back(data);
};
return selectPrepareInternal{{ cpp_struct.name }}(where) && selectFetchInternal{{ cpp_struct.name }}(cb);
}
bool Dao::selectPrepareInternal{{ cpp_struct.name }}(const QString &where)
{
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}")));
const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" FROM ") + tableName + where);
return m_query->prepare(stmt);
}
bool Dao::selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
{
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
Database::{{ cpp_struct.name }} data;
while (m_query->next())
{
int i = 0;
{% for row in cpp_struct.viewMember %}
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
cb(data);
}
return true;
}
// <-- Database::{{ cpp_struct.name }} end
{% endfor %}
/*static*/ QString Dao::qVariantToJsonString(const QVariant &value)
{
return value.isNull() ? QStringLiteral("{}") : value.toString();
}
} // namespace {{ namespace }}
{% endautoescape %}
+82
View File
@@ -0,0 +1,82 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id: svc_db_impl.h.grantlee 53676 2021-06-10 10:21:06Z diers $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/25_02_refactoring/Lts/Common/CodeGen/svc_db_impl.h.grantlee $
* $Author: diers $
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dao.h
#pragma once
#include <QPointF>
#include <QString>
#include <QVector>
#include <QSqlDriver>
#include <QSqlDatabase>
#include <QtSql/QSqlError>
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h>
#include <{{ include_prefix }}/logger.h>
class NSqlQuery;
namespace {{ namespace }}
{
class {{ namespace_abbr_upper }}_COMMON_EXPORT Dao {% if not custom_db_procs %}final {%endif %}: public QObject
{
Q_OBJECT
public:
explicit Dao(int exerciseId);
~Dao();
bool init(const QString &dbUser,
const QString &dbPasswd,
const QString &dbDriver,
const QString &dbHost,
const QString &dbName);
QString schemaName() const;
QString dbUser() const;
const QSqlDatabase &database() const { return m_dbConnection; }
void setEmitMessageOnError(bool bEmit);
QSqlError getLastError() const;
{% for cpp_struct in cpp_structs %}
// --> Database::{{ cpp_struct.name }}
public:
bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec, const QString &where = QString());
private:
bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where);
bool selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb);
// <-- Database::{{ cpp_struct.name }}
{% endfor %}
static QString qVariantToJsonString(const QVariant &value);
protected:
QString getTableName(const QString &tableName) const;
{% if has_db_notifications %}
private Q_SLOTS:
void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload);
Q_SIGNALS:
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %} void signal{{ db_notify }}(int id);
{% endfor %}{% endfor %}{% endif %}
protected:
QSqlDatabase m_dbConnection;
std::unique_ptr<NSqlQuery> m_query;
QSqlError m_lastError;
int m_exerciseId;
bool m_bEmitMessageOnError;
bool m_bCanHandleSchemas;
bool m_ownDatabaseConnection;
LOGGER_T m_logger;
private:
bool initInternal();
};// class Dao
} // namespace {{ namespace }}
{% endautoescape %}
+145
View File
@@ -0,0 +1,145 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_db.h
#pragma once
#include <QObject>
#include <functional>
#include <QtSql/QSqlError>
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_structs.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
{% for ext_service in ext_services.member %}
#include <{{ ext_service.baseFilePath }}.hpp>
{% endfor %}
namespace NGL
{
class INGLLogger;
}
class QSqlDatabase;
namespace {{ namespace }}
{
class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterface : public QObject
{
Q_OBJECT
public:
virtual ~DatabaseInterface() = default;
/**
* Create a new instance for {{ namespace }}::DatabaseInterface.
* @param exerciseId if <= 0 -> global service
*/
static std::shared_ptr<DatabaseInterface> create(int exerciseId);
/**
* @brief initialize the database interface (must be called after the db connection was opened!)
* @return true on succcess
*/
virtual bool init(const QSqlDatabase &dbConnection) = 0;
virtual bool init(const QString &dbUser,
const QString &dbPasswd,
const QString &dbDriver,
const QString &dbHost,
const QString &dbName) = 0;
/**
* @brief return the current schema name depending if exercise id > 0 or not
*/
virtual QString schemaName() const = 0;
/**
* @brief return the current database user dependening if exercise id > 0 or not
*/
virtual QString dbUser() const = 0;
/**
* @brief return the database connection used in this DatabaseInterface
*/
virtual const QSqlDatabase &database() const = 0;
/**
* emit an error message (ERROR_LOG) when a statement fails to execute (default: on)
*/
virtual void setEmitMessageOnError(bool bEmit) = 0;
virtual QSqlError getLastError() const = 0;
{% for cpp_struct in cpp_structs %}
//
// Database::{{ cpp_struct.name }}
//
public:
virtual bool insert(Database::{{ cpp_struct.name }} &data) = 0;
virtual bool upsert(Database::{{ cpp_struct.name }} &data) = 0;
virtual bool update(const Database::{{ cpp_struct.name }} &data) = 0;
{% if cpp_struct.insertStructures|length == 1 %}
virtual bool insert(std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
virtual bool upsert(std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
virtual bool update(const std::vector<Database::{{ cpp_struct.name }}> &cont) = 0;
{% endif %}
virtual bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec) = 0;
virtual bool select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb) = 0;
{% for fk in cpp_struct.selectRemoveMember %}
virtual bool selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0;
virtual bool selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) = 0;
{% endfor %}
virtual bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) = 0;
virtual bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) = 0;
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
virtual bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) = 0;
{% endfor %} {% endif %}
virtual bool fixUpSequence{{ cpp_struct.name }}() = 0;
virtual bool truncate{{ cpp_struct.name }}() = 0;
{% if cpp_struct.dbNotifications %}
Q_SIGNALS:
{% for dbnotify in cpp_struct.dbNotifications %}
// database notification on insert/update/delete
void {{ dbnotify }}(const QVariant &payload);
{% endfor %}
{% endif %}
//
// Database::{{ cpp_struct.name }} end
//
{% endfor %}
{% if data_structs|length > 0 %}
public:{% for data_struct in data_structs %}
// read only struct Database::{{ data_struct.name }}
virtual bool select(QVector<Database::{{ data_struct.name }}> &dataVec) = 0;
{% endfor %} {% endif %}
{% if custom_db_procs|length > 0 %}
public:{% for db_proc in custom_db_procs %}
// custom proc, body must be implemented by user
virtual {{ db_proc.signature }} = 0;
{% endfor %} {% endif %}
public:
// vector of database struct to/from JSON-array
// helper for BaseDownloader
template<typename T>
static QJsonArray toJson(const QVector<T> &dataVec)
{
return Database::toJson<T>(dataVec);
}
template<typename T>
static std::vector<T> fromJson(const QJsonArray &arr)
{
return Database::fromJson<T>(arr);
}
};// class DatabaseInterface
} // namespace {{ namespace }}
{% endautoescape %}
+732
View File
@@ -0,0 +1,732 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_db_impl.cpp
#include "{{ namespace_abbr }}_db_impl.h"
{% if custom_db_procs %}
#include <{{ include_prefix }}/{{ namespace_abbr }}_db_impl_custom.h>
{% endif %}
#include <ngldb/dbtransaction.h>
#include <ngldb/nsqlquery.h>
#include <ngldb/dbconnection.h>
#include <ngldb/sql_query_janitor.h>
#include <QtSql/QSqlError>
#include <QVariant>
#include <NGLLogger/INGLLogger.h>
namespace {{ namespace }}
{
QString qVariantToJsonString(const QVariant &value)
{
return value.isNull() ? QStringLiteral("{}") : value.toString();
}
QString fixupEmptyJsonString(const QString &str)
{
return str.isEmpty() ? QStringLiteral("{}") : str;
}
// DatabaseInterface
std::shared_ptr<DatabaseInterface> DatabaseInterface::create(int exerciseId)
{
{% if custom_db_procs %}
return std::make_shared<DatabaseInterfaceImplCustom>(exerciseId);
{% else %}
return std::make_shared<DatabaseInterfaceImpl>(exerciseId);
{% endif %}
}
DatabaseInterfaceImpl::DatabaseInterfaceImpl(int exerciseId)
: m_query(nullptr)
, m_exerciseId(exerciseId)
, m_bEmitMessageOnError(true)
, m_bCanHandleSchemas(true)
, m_ownDatabaseConnection(true)
, m_logger(getLogger("Db"))
{}
DatabaseInterfaceImpl::~DatabaseInterfaceImpl()
{
m_query = nullptr;
{% if has_db_notifications %}
QSqlDriver *driver = m_dbConnection.driver();
if (driver && m_dbConnection.isOpen())
{
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
driver->unsubscribeFromNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
}
{% endif %}
if (m_ownDatabaseConnection && m_dbConnection.isOpen())
m_dbConnection.close();
}
bool DatabaseInterfaceImpl::init(const QSqlDatabase &dbConnection)
{
m_dbConnection = dbConnection;
m_ownDatabaseConnection = false;
return initInternal();
}
bool DatabaseInterfaceImpl::init(const QString &dbUser,
const QString &dbPasswd,
const QString &dbDriver,
const QString &dbHost,
const QString &dbName)
{
m_ownDatabaseConnection = true;
m_dbConnection = DBConnection::open(m_logger, dbUser, dbPasswd, dbDriver, dbHost, dbName, schemaName());
return initInternal();
}
bool DatabaseInterfaceImpl::initInternal()
{
QSqlDriver *driver = m_dbConnection.driver();
if (!driver)
{
WARNING_LOG(m_logger) << "No SQL driver loaded - no database available!";
return false;
}
const QString driverName = m_dbConnection.driverName();
DEBUG_LOG(m_logger) << "Connected to database " << m_dbConnection.databaseName() << "@" << m_dbConnection.hostName()
<< ", schema: " << schemaName() << ", used driver: " << driverName;
if (!m_dbConnection.isOpen())
{
WARNING_LOG(m_logger) << "Database connection is no open - can not initialize database interface";
return false;
}
if (driverName == QLatin1String("QSQLITE"))
m_bCanHandleSchemas = false;
{% if has_db_notifications %}
if (!driver->hasFeature(QSqlDriver::EventNotifications))
{
WARNING_LOG(m_logger) << "Can not subscribe to notifications - sqldriver does not support this feature!";
}
else
{
connect(driver, QOverload<const QString&,QSqlDriver::NotificationSource,const QVariant&>::of(&QSqlDriver::notification),
this, &DatabaseInterfaceImpl::onNotification);
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
driver->subscribeToNotification(QStringLiteral("{{ db_notify }}"));{% endfor %}{% endfor %}
}
{% endif %}
m_query = std::unique_ptr<NSqlQuery>(new NSqlQuery(m_dbConnection, m_logger));
return true;
}
QString DatabaseInterfaceImpl::schemaName() const
{
if (!m_bCanHandleSchemas)
return QString();
if (m_exerciseId <= 0)
return QStringLiteral("{{ namespace_abbr }}");
return DBCommon::getExerciseSchemaName(m_exerciseId);
}
QString DatabaseInterfaceImpl::dbUser() const
{
if (m_exerciseId > 0)
return QStringLiteral("ets_ex_00");
else
return QStringLiteral("ets_") + schemaName();
}
void DatabaseInterfaceImpl::setEmitMessageOnError(bool bEmit)
{
m_bEmitMessageOnError = bEmit;
}
QString DatabaseInterfaceImpl::getTableName(const QString &tableName) const
{
if (!m_bCanHandleSchemas)
return tableName;
if (m_exerciseId > 0)
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
return QStringLiteral("{{ namespace_abbr }}.") + tableName;
}
QSqlError DatabaseInterfaceImpl::getLastError() const
{
return m_lastError;
}
{% if has_db_notifications %}
void DatabaseInterfaceImpl::onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload)
{
DEBUG_LOG(m_logger) << "Notification source = " << source;
// no need to emit notification signals
if (source == QSqlDriver::SelfSource)
return;
// search coresponding signal
{% for cpp_struct in cpp_structs %}{% for db_notify in cpp_struct.dbNotifications %}
if (name == QLatin1String("{{ db_notify }}"))
{
emit {{ db_notify }}(payload);
}{% endfor %}{% endfor %}
}
{% endif %}
{% for cpp_struct in cpp_structs %}
//
// Struct: Database::{{ cpp_struct.name }}
//
bool DatabaseInterfaceImpl::insert(Database::{{ cpp_struct.name }} &data)
{
SqlQueryJanitor janitor(m_query);
{% if cpp_struct.insertStructures|length > 1 %}
DBTransaction transaction(m_dbConnection);
{% endif %}
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
QString keyColumnInsertKey;
QString keyColumnInsertVal;
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
const bool bInsertWithKey = data.{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}};
if (bInsertWithKey)
{
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
}
{% endif %}
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
QStringLiteral(" (") + keyColumnInsertKey +
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" ) VALUES (") + keyColumnInsertVal +
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )"));
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
{% if insertStruct.serialMember.cppMemberName %}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% else %}
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% endif %}
{% if insertStruct.baseId.cppMemberName %}
data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }};
{% endif %}
{% endif %}
}
{% endfor %}
{% if cpp_struct.insertStructures|length > 1 %}
transaction.commit();
{% endif %}
return true;
}
bool DatabaseInterfaceImpl::upsert(Database::{{ cpp_struct.name }} &data)
{
SqlQueryJanitor janitor(m_query);
{% if cpp_struct.insertStructures|length > 1 %}
DBTransaction transaction(m_dbConnection);
{% endif %}
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
QString keyColumnInsertKey;
QString keyColumnInsertKeyConflict;
QString keyColumnInsertVal;
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
const bool bInsertWithKey = true;
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
{% endif %}
keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} ");
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
QStringLiteral(" (") + keyColumnInsertKey +
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" ) VALUES (") + keyColumnInsertVal +
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") +
QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") +
QStringLiteral(" SET"
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
{% if insertStruct.serialMember.cppMemberName %}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% else %}
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% endif %}
{% if insertStruct.baseId.cppMemberName %}
data.{{ insertStruct.baseId.cppMemberName }} = data.{{ insertStruct.serialMember.cppMemberName }};
{% endif %}
{% endif %}
}
{% endfor %}
{% if cpp_struct.insertStructures|length > 1 %}
transaction.commit();
{% endif %}
return true;
}
bool DatabaseInterfaceImpl::update(const Database::{{ cpp_struct.name }} &data)
{
SqlQueryJanitor janitor(m_query);
{% if cpp_struct.insertStructures|length > 1 %}
DBTransaction transaction(m_dbConnection);
{% endif %}
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
const QString stmt(QStringLiteral("UPDATE ") + tableName +
QStringLiteral(" SET"
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
{% if insertStruct.serialMember.cppMemberName %}
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
}
{% endfor %}
{% if cpp_struct.insertStructures|length > 1 %}
transaction.commit();
{% endif %}
return true;
}
{% if cpp_struct.insertStructures|length == 1 %}
// current only implemented for 'plain' structures with only one affected table
bool DatabaseInterfaceImpl::insert(std::vector<Database::{{ cpp_struct.name }}> &cont)
{
if (cont.empty())
return true;
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
QString keyColumnInsertKey;
QString keyColumnInsertVal;
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
const bool bInsertWithKey = cont[0].{{cpp_struct.primaryKey.cppMemberName}} != {{cpp_struct.primaryKey.cppDefault}};
// the first element determines the stmt to be used
if (bInsertWithKey)
{
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
}
{% endif %}
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
QStringLiteral(" (") + keyColumnInsertKey +
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" ) VALUES (") + keyColumnInsertVal +
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )"));
{
SqlQueryJanitor janitor(m_query);
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
for (auto &data : cont)
{
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
{% if insertStruct.serialMember.cppMemberName %}
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% endif %}
}
}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
fixUpSequence{{ cpp_struct.nameCc }}();
{% endif %}
}
{% endfor %}
return true;
}
bool DatabaseInterfaceImpl::upsert(std::vector<Database::{{ cpp_struct.name }}> &cont)
{
if (cont.empty())
return true;
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
QString keyColumnInsertKey;
QString keyColumnInsertKeyConflict;
QString keyColumnInsertVal;
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
const bool bInsertWithKey = true;
keyColumnInsertVal = QStringLiteral(" :{{ insertStruct.primaryKey.dbColumnName }}, ");
keyColumnInsertKey = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }}, ");
{% endif %}
// the first element determines the stmt to be used
keyColumnInsertKeyConflict = QStringLiteral("{{ insertStruct.primaryKey.dbColumnName }} ");
const QString stmt(QStringLiteral("INSERT INTO ") + tableName +
QStringLiteral(" (") + keyColumnInsertKey +
QStringLiteral("{% for row in insertStruct.member %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" ) VALUES (") + keyColumnInsertVal +
QStringLiteral("{% for row in insertStruct.member %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} )") +
QStringLiteral(" ON CONFLICT (") + keyColumnInsertKeyConflict + QStringLiteral(") DO UPDATE ") +
QStringLiteral(" SET"
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" WHERE excluded.{{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
{
SqlQueryJanitor janitor(m_query);
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
for (auto &data : cont)
{
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
{% if insertStruct.serialMember.cppMemberName %}
if (data.{{cpp_struct.primaryKey.cppMemberName}} == {{cpp_struct.primaryKey.cppDefault}})
data.{{ insertStruct.serialMember.cppMemberName }} = m_query->lastInsertId(){{ insertStruct.serialMember.cppFromQvPost }};
{% endif %}
}
}
{% if forloop.first and cpp_struct.primaryKey.dbHasSequence %}
if (bInsertWithKey)
fixUpSequence{{ cpp_struct.nameCc }}();
{% endif %}
}
{% endfor %}
return true;
}
// current only implemented for 'plain' structures with only one affected table
bool DatabaseInterfaceImpl::update(const std::vector<Database::{{ cpp_struct.name }}> &cont)
{
if (cont.empty())
return true;
{% for insertStruct in cpp_struct.insertStructures %}
{
const QString tableName(getTableName(QStringLiteral("{{ insertStruct.name }}")));
const QString stmt(QStringLiteral("UPDATE ") + tableName +
QStringLiteral(" SET"
" {% for row in insertStruct.member %}{{ row.dbColumnName }} = :{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" WHERE {{ insertStruct.primaryKey.dbColumnName }} = :{{ insertStruct.primaryKey.dbColumnName }}"));
{
SqlQueryJanitor janitor(m_query);
if (!m_query->prepare(stmt))
{
m_lastError = m_query->lastError();
return false;
}
for (const auto &data : cont)
{
{% for row in insertStruct.member %}
m_query->bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.cppToQvPre }}data.{{ row.cppMemberName }}{{ row.cppToQvPost }});
{% endfor %}
{% if insertStruct.serialMember.cppMemberName %}
m_query->bindValue(QStringLiteral(":{{ insertStruct.primaryKey.dbColumnName }}"), data.{{ insertStruct.serialMember.cppMemberName }});
{% endif %}
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
}
}
}
{% endfor %}
return true;
}
{% endif %}
bool DatabaseInterfaceImpl::select(QVector<Database::{{ cpp_struct.name }}> &dataVec)
{
SqlQueryJanitor janitor(m_query);
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
{
dataVec.push_back(data);
};
return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb);
}
bool DatabaseInterfaceImpl::select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
{
SqlQueryJanitor janitor(m_query);
return selectPrepareInternal{{ cpp_struct.name }}(QString()) && selectFetchInternal{{ cpp_struct.name }}(cb);
}
{% for fk in cpp_struct.selectRemoveMember %}
bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy)
{
SqlQueryJanitor janitor(m_query);
if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy))
{
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
m_lastError = m_query->lastError();
return false;
}
m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
const auto cb =[&dataVec] (const Database::{{ cpp_struct.name }} &data)
{
dataVec.push_back(data);
};
return selectFetchInternal{{ cpp_struct.name }}(cb);
}
bool DatabaseInterfaceImpl::selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy)
{
SqlQueryJanitor janitor(m_query);
if (!selectPrepareInternal{{ cpp_struct.name }}(QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.dbColumnName }}") + orderBy))
{
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
m_lastError = m_query->lastError();
return false;
}
m_query->bindValue(QStringLiteral(":{{ fk.dbColumnName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
return selectFetchInternal{{ cpp_struct.name }}(cb);
}
{% endfor %}
bool DatabaseInterfaceImpl::selectPrepareInternal{{ cpp_struct.name }}(const QString &where)
{
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.viewTableName }}")));
const QString stmt(QStringLiteral("SELECT {% for row in cpp_struct.viewMember %}{{ row.dbColumnNameView }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" FROM ") + tableName + where);
return m_query->prepare(stmt);
}
bool DatabaseInterfaceImpl::selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb)
{
if (!m_query->exec(m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
Database::{{ cpp_struct.name }} data;
while (m_query->next())
{
int i = 0;
{% for row in cpp_struct.viewMember %}
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
cb(data);
}
return true;
}
bool DatabaseInterfaceImpl::select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data)
{
SqlQueryJanitor janitor(m_query);
int iCnt = 0;
const auto cb =[&data, &iCnt] (const Database::{{ cpp_struct.name }} &content)
{
data = content;
++iCnt;
};
const QString where = QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = %1").arg({{ cpp_struct.primaryKey.cppMemberName }});
if (!selectPrepareInternal{{ cpp_struct.name }}(where) || !selectFetchInternal{{ cpp_struct.name }}(cb))
return false;
return (iCnt == 1);
}
bool DatabaseInterfaceImpl::remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }})
{
SqlQueryJanitor janitor(m_query);
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
const QString stmt(QStringLiteral("DELETE FROM ") + tableName +
QStringLiteral(" WHERE {{ cpp_struct.primaryKey.dbColumnName }} = ") +
QString::number({{ cpp_struct.primaryKey.cppMemberName }}));
if (!m_query->exec(stmt, m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
return true;
}
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
bool DatabaseInterfaceImpl::remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }})
{
SqlQueryJanitor janitor(m_query);
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
const QString stmt(QStringLiteral("DELETE FROM ") + tableName +
QStringLiteral(" WHERE {{ fk.dbColumnName }} = :{{ fk.cppMemberName }}"));
if (!m_query->prepare(stmt))
{
ERROR_LOG(m_logger) << "Error preparing query: " << m_query->lastError().text();
m_lastError = m_query->lastError();
return false;
}
m_query->bindValue(QStringLiteral(":{{ fk.cppMemberName }}"), {{ fk.cppToQvPre }}{{ fk.cppMemberName }}{{ fk.cppToQvPost }});
return m_query->exec(m_bEmitMessageOnError);
}
{% endfor %} {% endif %}
bool DatabaseInterfaceImpl::fixUpSequence{{ cpp_struct.nameCc }}()
{
SqlQueryJanitor janitor(m_query);
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
const QString stmt(QStringLiteral("SELECT setval('") + tableName + QStringLiteral("_{{ cpp_struct.primaryKey.dbColumnName }}_seq', COALESCE((SELECT MAX({{ cpp_struct.primaryKey.dbColumnName }})+1 FROM ") +
tableName + QStringLiteral("), 1),false)"));
if (!m_query->exec(stmt, m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
return true;
}
bool DatabaseInterfaceImpl::truncate{{ cpp_struct.nameCc }}()
{
SqlQueryJanitor janitor(m_query);
const QString tableName(getTableName(QStringLiteral("{{ cpp_struct.primaryKey.dbTableName }}")));
const QString stmt(QStringLiteral("TRUNCATE TABLE ") + tableName + QStringLiteral(" CASCADE"));
if (!m_query->exec(stmt, m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
return true;
}
// Database::{{ cpp_struct.name }} end
{% endfor %}
{% for ds in data_structs %}
bool DatabaseInterfaceImpl::select(QVector<Database::{{ ds.name }}> &dataVec)
{
SqlQueryJanitor janitor(m_query);
const QString tableName(getTableName(QStringLiteral("{{ ds.viewTableName }}")));
const QString stmt(QStringLiteral("SELECT {% for row in ds.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}"
" FROM ") + tableName);
if (!m_query->exec(stmt, m_bEmitMessageOnError))
{
m_lastError = m_query->lastError();
return false;
}
Database::{{ ds.name }} data;
while (m_query->next())
{
int i = 0;
{% for row in ds.member %}
data.{{ row.cppMemberName }}{{ row.cppFromQvPre1 }} = {{ row.cppFromQvPre2 }}m_query->value(i++){{ row.cppFromQvPost }};{% endfor %}
dataVec.append(data);
}
return true;
}
{% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
+102
View File
@@ -0,0 +1,102 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_db_impl.h
#pragma once
#include <QPointF>
#include <QString>
#include <QVector>
#include <QSqlDriver>
#include <QSqlDatabase>
#include <QtSql/QSqlError>
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
#include <{{ include_prefix }}/{{ namespace_abbr }}_db.h>
#include <{{ include_prefix }}/logger.h>
class NSqlQuery;
namespace {{ namespace }}
{
class {{ namespace_abbr_upper }}_COMMON_EXPORT DatabaseInterfaceImpl {% if not custom_db_procs %}final {%endif %}: public DatabaseInterface
{
Q_OBJECT
public:
explicit DatabaseInterfaceImpl(int exerciseId);
~DatabaseInterfaceImpl();
bool init(const QSqlDatabase &dbConnection) override final;
bool init(const QString &dbUser,
const QString &dbPasswd,
const QString &dbDriver,
const QString &dbHost,
const QString &dbName) override final;
QString schemaName() const override final;
QString dbUser() const override final;
const QSqlDatabase &database() const override final { return m_dbConnection; }
void setEmitMessageOnError(bool bEmit) override final;
QSqlError getLastError() const override final;
{% for cpp_struct in cpp_structs %}
// Database::{{ cpp_struct.name }}
bool insert(Database::{{ cpp_struct.name }} &data) override final;
bool upsert(Database::{{ cpp_struct.name }} &data) override final;
bool update(const Database::{{ cpp_struct.name }} &data) override final;
{% if cpp_struct.insertStructures|length == 1 %}
bool insert(std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
bool upsert(std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
bool update(const std::vector<Database::{{ cpp_struct.name }}> &cont) override final;
{% endif %}
bool select(QVector<Database::{{ cpp_struct.name }}> &dataVec) override final;
bool select(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb) override final;
{% for fk in cpp_struct.selectRemoveMember %}
bool selectBy{{ fk.cppMemberNameCc }}(QVector<Database::{{ cpp_struct.name }}> &dataVec, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final;
bool selectBy{{ fk.cppMemberNameCc }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb, const {{ fk.cppType }} &{{ fk.cppMemberName }}, const QString &orderBy = QString()) override final;
{% endfor %}
protected:
bool selectPrepareInternal{{ cpp_struct.name }}(const QString &where);
bool selectFetchInternal{{ cpp_struct.name }}(const std::function<void(const Database::{{ cpp_struct.name }} &data)> &cb);
public:
bool select{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}, Database::{{ cpp_struct.name }} &data) override final;
bool remove{{ cpp_struct.name }}({{ cpp_struct.primaryKey.cppType }} {{ cpp_struct.primaryKey.cppMemberName }}) override final;
{% if cpp_struct.deleteWithoutPkAllowed %} {% for fk in cpp_struct.selectRemoveMember %}
bool remove{{ cpp_struct.nameCc }}By{{ fk.cppMemberNameCc }}(const {{ fk.cppType }} &{{ fk.cppMemberName }}) override final;
{% endfor %} {% endif %}
bool fixUpSequence{{ cpp_struct.name }}() override final;
bool truncate{{ cpp_struct.name }}() override final;
// Database::{{ cpp_struct.name }}
{% endfor %}
{% for data_struct in data_structs %}
// read only struct{{ data_struct.name }}
bool select(QVector<Database::{{ data_struct.name }}> &dataVec) override final;
{% endfor %}
protected:
QString getTableName(const QString &tableName) const;
{% if has_db_notifications %}
private Q_SLOTS:
void onNotification(const QString &name, QSqlDriver::NotificationSource source, const QVariant &payload);
{% endif %}
protected:
QSqlDatabase m_dbConnection;
std::unique_ptr<NSqlQuery> m_query;
QSqlError m_lastError;
int m_exerciseId;
bool m_bEmitMessageOnError;
bool m_bCanHandleSchemas;
bool m_ownDatabaseConnection;
LOGGER_T m_logger;
private:
bool initInternal();
};// class DatabaseInterfaceImpl
} // namespace {{ namespace }}
{% endautoescape %}
+95
View File
@@ -0,0 +1,95 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_db_structs.cpp
#include "{{ namespace_abbr }}_db_structs.h"
#include "{{ namespace_abbr }}_export.h"
namespace {{ namespace }}
{
namespace Database
{
QString jsonValueToJsonString(const QJsonValue &value)
{
return value.isNull() ? QStringLiteral("{}") : value.toString();
}
QString qVariantToJsonString(const QVariant &value)
{
return value.isNull() ? QStringLiteral("{}") : value.toString();
}
QString fixupEmptyJsonString(const QString &str)
{
return str.isEmpty() ? QStringLiteral("{}") : str;
}
// QPointF
QJsonValue toJson(const QPointF &element)
{
QJsonObject obj;
obj.insert(QStringLiteral("x"), QJsonValue(element.x()));
obj.insert(QStringLiteral("y"), QJsonValue(element.y()));
return QJsonValue(obj);
}
template<>
QPointF fromJson(const QJsonValue &val)
{
QJsonObject obj = val.toObject();
return QPointF(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble());
}
{% if need_point3d %}
// QPoint3D
QJsonValue toJson(const LtsUtils::QPoint3D &element)
{
QJsonObject obj;
obj.insert(QStringLiteral("x"), QJsonValue(element.x()));
obj.insert(QStringLiteral("y"), QJsonValue(element.y()));
obj.insert(QStringLiteral("z"), QJsonValue(element.z()));
return QJsonValue(obj);
}
template<>
LtsUtils::QPoint3D fromJson(const QJsonValue &val)
{
QJsonObject obj = val.toObject();
return LtsUtils::QPoint3D(obj.value(QStringLiteral("x")).toDouble(), obj.value(QStringLiteral("y")).toDouble(), obj.value(QStringLiteral("z")).toDouble());
}
{% endif %}
{% for cpp_struct in cpp_structs %}
//
// Struct: {{ cpp_struct.name }}
//
QJsonObject toJson(const {{ cpp_struct.name }} &element)
{
QJsonObject obj;
{% for row in cpp_struct.jsonMember %}
{{ row.dbToJsonElement }};
{% endfor %}
return obj;
}
template<>
{{ cpp_struct.name }} fromJson(const QJsonValue &value)
{
const QJsonObject objElement(value.toObject());
{{ cpp_struct.name }} element;
{% for row in cpp_struct.jsonMember %}
{ {{ row.jsonElementToDb }}; } {% endfor %}
return element;
}
// {{ cpp_struct.name }} end
{% endfor %}
} // namespace Database
} // namespace {{ namespace }}
{% endautoescape %}
+87
View File
@@ -0,0 +1,87 @@
/**
* @RDE_HEADER@
* Template for 'real' structures including insert/update/delete functions for db
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_db_structs.h
#pragma once
#include <QDateTime>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QPointF>
#include <QString>
#include <QVariant>
#include <QVector>
{% if need_point3d %}
#include <LtsUtilsCore/qpoint3d.h>
{% endif %}
{% if need_pgarray %}
#include <LtsUtilsCore/pgarray.h>
{% endif %}
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_enums.hpp>
{% for ext_service in ext_services.member %}
#include <{{ ext_service.baseFilePath }}.hpp>
{% endfor %}
namespace {{ namespace }}
{
namespace Database
{
{% for cpp_struct in cpp_structs %}
// {{ cpp_struct.name }}
struct {{ cpp_struct.name }}
{
{% for m in cpp_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %};
{% endfor %}
};{% endfor %}
{% for data_struct in data_structs %}
// read only struct {{ data_struct.name }}
struct {{ data_struct.name }}
{
{% for m in data_struct.cppMember %}{{ m.cppType }} {{ m.cppMemberName }}{% if m.cppDefault %} = {{ m.cppDefault }}{% endif %};
{% endfor %}
};{% endfor %}
// converting DbStructs to/from JSON
template<typename T>
T fromJson(const QJsonValue &value);
{% for cpp_struct in cpp_structs %}
// {{ cpp_struct.name }}
template <> {{ namespace_abbr_upper }}_{{ library_target }}_EXPORT {{ cpp_struct.name }} fromJson(const QJsonValue &value);
{{ namespace_abbr_upper }}_{{ library_target }}_EXPORT QJsonObject toJson(const {{ cpp_struct.name }} &element);{% endfor %}
// vector of database struct to/from JSON-array
template<typename T>
QJsonArray toJson(const QVector<T> &dataVec)
{
QJsonArray arr;
for (const T &data : dataVec)
arr.append(QJsonValue(toJson(data)));
return arr;
}
template<typename T>
std::vector<T> fromJson(const QJsonArray &arr)
{
std::vector<T> dataVec;
dataVec.reserve(arr.size());
for (const QJsonValue &val : arr)
dataVec.push_back(fromJson<T>(val));
return dataVec;
}
};// namespace Database
} // namespace {{ namespace }}
{% endautoescape %}
+88
View File
@@ -0,0 +1,88 @@
/**
* {{ namespace_abbr_upper }} Data Topic Client Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2016-01-04
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dtc.h
#pragma once
#include <QMap>
#include <QObject>
#include <functional>
#include <LtsUtilsCore/data_modification_enum.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
namespace DDSConnector
{
class DdsConnector;
}
namespace {{ namespace }}
{
namespace Interface
{
class {{ namespace_abbr_upper }}_COMMON_EXPORT Dtc : public QObject
{
Q_OBJECT
protected:
Dtc() = default;
public:
virtual ~Dtc() = default;
/**
* Create a new instance for {{ namespace }}::Interface::Dtc.
*/
static std::shared_ptr<Dtc> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
/**
* A unique identifier for this DTC
*/
static inline const char *identifier() { return "{{ namespace }}.Interface.Dtc"; }
/**
* initialize this Dts instance with all data topic readers
*/
virtual bool enableAll(bool enable = true) = 0;
/**
* only initialize the specific reader (can be called instead enableAll())
*/
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
virtual bool enable{{ dt.name }}(bool enable = true) = 0;
virtual bool is{{ dt.name }}Enabled() const = 0;
virtual bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) = 0;
{% endif %} {% endfor %}
{% for cp in custom_dtc_procs %}
// custom procs, body must be implemented by user
virtual {{ cp.signature }} = 0;
{% endfor %}
{% for dt in dataTopics.member %}
public:
//
// Data Topic '{{ dt.name }}'
// {% if dt.key.name %}
virtual QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() = 0;
virtual bool {{ dt.nameFlc }}s(const std::function<void(const {{ namespace }}::{{ dt.type }} &data)> &cb) = 0;
// when the cb returns true, the loop is stopped (useful when searching for a specific topic)
virtual bool {{ dt.nameFlc }}s(const std::function<bool(const {{ namespace }}::{{ dt.type }} &data)> &cb) = 0;
virtual {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) = 0;
{% else %}
virtual {{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() = 0;
{% endif %}
Q_SIGNALS:
void {{ dt.nameFlc }}Changed(const {{ namespace }}::{{ dt.type }} &data, DataModification modType);
{% endfor %}
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+304
View File
@@ -0,0 +1,304 @@
/**
* {{ namespace_abbr_upper }} Data Topic Client Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-07
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dtc_impl.cpp
#include "{{ namespace_abbr }}_dtc_impl.h"
{% if custom_dtc_procs %}
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_impl_custom.h>
{% endif %}
#include <DDS/DTC/generic_dtc_impl.h>
#include <dds/core/cond/StatusCondition.hpp>
#include <dds/core/cond/WaitSet.hpp>
#include <unordered_map>
#include <QCoreApplication>
#include <QThread>
namespace {{ namespace }}
{
namespace Interface
{
// initializer function
std::shared_ptr<Dtc> Dtc::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
{
// share only for main thread
if (qApp->thread() != QThread::currentThread())
{% if custom_dtc_procs %}
return std::make_shared<DtcImplCustom>(ddsConnector);
{% else %}
return std::make_shared<DtcImpl>(ddsConnector);
{% endif %}
static std::unordered_map<DDSConnector::DdsConnector*, std::weak_ptr<Dtc>> s_availableConnectors;
auto it = s_availableConnectors.find(ddsConnector.get());
if (it != s_availableConnectors.end())
{
auto ret = it->second.lock();
if (ret)
return ret;
s_availableConnectors.erase(it);
}
{% if custom_dtc_procs %}
auto ret = std::make_shared<DtcImplCustom>(ddsConnector);
{% else %}
auto ret = std::make_shared<DtcImpl>(ddsConnector);
{% endif %}
s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret));
return std::move(ret); // c++11 defect
}
// DtcImpl
DtcImpl::DtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: Dtc()
, m_ddsConnector(ddsConnector)
, m_logger(getLogger("Dtc"))
{}
DtcImpl::~DtcImpl()
{
enableAll(false);
}
bool DtcImpl::enableAll(bool enable)
{ {% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.autosubscribe %}
if (!enable{{ dt.name }}(enable))
{
ERROR_LOG(m_logger) << "Error initializing data topic {{ dt.name }}";
return false;
}
{% endif %} {% endif %} {% endfor %}
return true;
}
{% for dt in dataTopics.member %} {% if dt.dtcImpl %} {% if dt.key.name %}
template <>
struct TopicConstraints<{{ dt.type }}>
{
using KEY = {{ dt.key.cppType }};
};
{% endif %} {% endif %} {% endfor %}
template <typename DTC>
bool DtcImpl::checkEnabled(const DTC &dtc) const
{
if (!dtc)
{
using TOPIC = typename DtcConstraints<DTC>::TOPIC ;
WARNING_LOG(m_logger) << "Error retrieving data topic "
<< DDS::topic_type_name<TOPIC>::value() << " - not enabled";
Q_ASSERT(false);
return false;
}
return true;
}
template <typename DTC>
bool DtcImpl::waitForHistoricalData(DTC &dtc, int timeoutMSec)
{
if (!checkEnabled(dtc))
return false;
return dtc->waitForHistoricalData(timeoutMSec);
}
template <typename T, typename K = typename TopicConstraints<T>::KEY>
K getKey(const T &val);
template <typename DTC,
typename T,
typename K>
QMap<K, T> DtcImpl::getAll(const DTC &dtc) const
{
if (!checkEnabled(dtc))
return {};
QMap<K, T> retMap;
for (const auto &sample : dtc->readAlive())
retMap.insert(getKey(sample.data()), sample);
return retMap;
}
template <typename DTC, typename C, typename T, typename K>
T DtcImpl::getOne(const DTC &dtc, const C &cont, const K &key) const
{
if (!checkEnabled(dtc))
return {};
const auto it = cont.find(key);
return it == cont.end() ? T() : it.value();
}
template <typename DTC, typename C, typename T>
bool DtcImpl::iterateAll(const DTC &dtc, const C &cont, const std::function<void(const T &data)> &cb) const
{
if (!checkEnabled(dtc))
return false;
for (const auto &sample : cont)
cb(sample);
return true;
}
template <typename DTC, typename C, typename T>
bool DtcImpl::iterateUntil(const DTC &dtc, const C &cont, const std::function<bool(const T &data)> &cb) const
{
if (!checkEnabled(dtc))
return false;
for (const auto &sample : cont)
{
if (cb(sample))
return true;
}
return false;
}
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
//
// Data Topic '{{ dt.name }}'
//
bool DtcImpl::enable{{ dt.name }}(bool enable)
{
if (enable)
{
if (m_dtc{{ dt.name }})
{
INFO_LOG(m_logger) << "{{ dt.name }} already enabled!";
return true;
}
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
assert(m_dtc{{ dt.name }}.get());
m_dtc{{ dt.name }}->setSubscriberCallback([this](const {{ namespace }}::{{ dt.name }} &data, DataModification modType)
{
{{ dt.nameFlc }}ChangedInternal(data, modType);
});
}
else
{
m_dtc{{ dt.name }} = nullptr;
}
return true;
}
bool DtcImpl::waitForHistoricalData{{ dt.name }}(int timeoutMSec)
{
if (!is{{ dt.name }}Enabled())
{
enable{{ dt.name }}();
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
}
if (!waitForHistoricalData(m_dtc{{ dt.name }}, timeoutMSec))
return false;
{% if dt.key.name %}
m_{{ dt.nameFlc }}Cont = getAll(m_dtc{{ dt.name }});
{% else %}
auto samples = m_dtc{{ dt.name }}->readAlive();
if (samples.length() == 0)
return false;
m_{{ dt.nameFlc }} = samples[samples.length() - 1].data();
{% endif %}
return true;
}
{% if dt.key.name %}
template <>
{{ dt.key.cppType }} getKey(const {{ dt.type }} &val)
{
return val.{{ dt.key.name }}();
}
QMap<{{ dt.key.cppType }},{{ namespace }}::{{ dt.type }}> DtcImpl::{{ dt.nameFlc }}s()
{
if (!is{{ dt.name }}Enabled())
{
enable{{ dt.name }}();
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
waitForHistoricalData{{ dt.name }}(10);
}
return m_{{ dt.nameFlc }}Cont;
}
bool DtcImpl::{{ dt.nameFlc }}s(const std::function<void(const {{ namespace }}::{{ dt.type }} &data)> &cb)
{
if (!is{{ dt.name }}Enabled())
{
enable{{ dt.name }}();
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
waitForHistoricalData{{ dt.name }}(10);
}
return iterateAll(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb);
}
bool DtcImpl::{{ dt.nameFlc }}s(const std::function<bool(const {{ namespace }}::{{ dt.type }} &data)> &cb)
{
if (!is{{ dt.name }}Enabled())
{
enable{{ dt.name }}();
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
waitForHistoricalData{{ dt.name }}(10);
}
return iterateUntil(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, cb);
}
{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }})
{
if (!is{{ dt.name }}Enabled())
{
enable{{ dt.name }}();
DEBUG_LOG(m_logger) << "NEED TO ENABLE ! {{ dt.name }}" ;
waitForHistoricalData{{ dt.name }}(10);
}
return getOne(m_dtc{{ dt.name }}, m_{{ dt.nameFlc }}Cont, {{ dt.key.nameFlc }});
}
void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType)
{
switch (modType)
{
case DataModification::eAdded:
case DataModification::eModified:
m_{{ dt.nameFlc }}Cont[getKey(data)] = data;
break;
case DataModification::eRemoved:
m_{{ dt.nameFlc }}Cont.remove(getKey(data));
break;
}
emit {{ dt.nameFlc }}Changed(data, modType);
}
{% else %}
{{ namespace }}::{{ dt.type }} DtcImpl::{{ dt.nameFlc }}()
{
return m_{{ dt.nameFlc }};
}
void DtcImpl::{{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType)
{
switch (modType)
{
case DataModification::eAdded:
case DataModification::eModified:
m_{{ dt.nameFlc }} = data;
break;
case DataModification::eRemoved:
m_{{ dt.nameFlc }} = {{ namespace }}::{{ dt.type }}();
break;
}
emit {{ dt.nameFlc }}Changed(data, modType);
}
{% endif %} {% endif %} {% endfor %}
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+99
View File
@@ -0,0 +1,99 @@
/**
* {{ namespace_abbr_upper }} Data Topic Client Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-07
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dtc_impl.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h>
#include <{{ include_prefix }}/logger.h>
#include <DDS/DDSConnector/dds_connector.h>
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB)
#error "You are not allowed to include this file in your code!"
#endif
namespace {{ namespace }}
{
namespace Interface
{
template <typename UQ_DTC>
struct DtcConstraints
{
using DTC = typename UQ_DTC::element_type;
using TOPIC = typename DTC::TopicType;
};
template <typename TOPIC>
struct TopicConstraints
{};
class DtcImpl {% if not custom_dtc_procs %}final {%endif %}: public Dtc
{
Q_OBJECT
public:
explicit DtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
~DtcImpl();
bool enableAll(bool enable = true) override;
{% for dt in dataTopics.member %} {% if dt.dtcImpl %}
//
// Data Topic '{{ dt.name }}'
//
public:
bool enable{{ dt.name }}(bool enable = true) override final;
bool is{{ dt.name }}Enabled() const override final { return m_dtc{{ dt.name }} != nullptr; }
bool waitForHistoricalData{{ dt.name }}(int timeoutMSec = -1) override final;
{% if dt.key.name %}
QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> {{ dt.nameFlc }}s() override final;
bool {{ dt.nameFlc }}s(const std::function<void(const {{ namespace }}::{{ dt.type }} &data)> &cb) override final;
bool {{ dt.nameFlc }}s(const std::function<bool(const {{ namespace }}::{{ dt.type }} &data)> &cb) override final;
{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}) override final;
{% else %}
{{ namespace }}::{{ dt.type }} {{ dt.nameFlc }}() override final;
{% endif %} {% endif %} {% endfor %}
private:
template <typename DTC>
bool checkEnabled(const DTC &dtc) const;
template <typename DTC>
bool waitForHistoricalData(DTC &dtc, int timeoutMSec);
template <typename DTC,
typename T = typename DtcConstraints<DTC>::TOPIC,
typename K = typename TopicConstraints<T>::KEY>
QMap<K, T> getAll(const DTC &dtc) const;
template <typename DTC, typename C,
typename T = typename DtcConstraints<DTC>::TOPIC,
typename K = typename TopicConstraints<T>::KEY>
T getOne(const DTC &dtc, const C &cont, const K &key) const;
template <typename DTC, typename C,
typename T = typename DtcConstraints<DTC>::TOPIC>
bool iterateAll(const DTC &dtc, const C &cont, const std::function<void(const T &data)> &cb) const;
template <typename DTC, typename C,
typename T = typename DtcConstraints<DTC>::TOPIC>
bool iterateUntil(const DTC &dtc, const C &cont, const std::function<bool(const T &data)> &cb) const;
protected:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
{% for dt in dataTopics.member %}
{% if dt.dtcImpl %}
void {{ dt.nameFlc }}ChangedInternal(const {{ namespace }}::{{ dt.type }} &data, DataModification modType);
std::unique_ptr<DTC::{{ dt.name }}> m_dtc{{ dt.name }}; {% if dt.key.name %}
QMap<{{ dt.key.cppType }}, {{ namespace }}::{{ dt.type }}> m_{{ dt.nameFlc }}Cont; {% else %}
{{ namespace }}::{{ dt.type }} m_{{ dt.nameFlc }};
{% endif %} {% endif %} {% endfor %}
LOGGER_T m_logger;
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+69
View File
@@ -0,0 +1,69 @@
/**
* {{ namespace_abbr_upper }} Data Topic Server Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2016-01-05
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dts.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
namespace DDSConnector
{
class DdsConnector;
}
namespace {{ namespace }}
{
namespace Interface
{
class Dts
{
protected:
Dts() = default;
public:
virtual ~Dts() = default;
/**
* Create a new instance for {{ namespace }}::Interface::Dts.
* The caller takes ownership of the returned instance
*/
{{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr<Dts> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
/**
* A unique identifier for this DTS
*/
{{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Dts"; }
/**
* initialize this Dts instance with all data topic writers
*/
virtual bool enableAll(bool enable = true) = 0;
/**
* only initialize the specific writer (can be called instead enableAll())
*/
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
virtual bool enable{{ dt.name }}(bool enable = true) = 0;
virtual bool is{{ dt.name }}Enabled() const = 0;
{% endif %} {% endfor %}
{% for dt in dataTopics.member %}
//
// Data Topic '{{ dt.name }}'
//
virtual bool publish(const {{ namespace }}::{{ dt.type }} &data) = 0;
{% if dt.key.name %}
virtual bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) = 0;
{% endif %}
{% endfor %}
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+100
View File
@@ -0,0 +1,100 @@
/**
* {{ namespace_abbr_upper }} Data Topic Server Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-07
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dts_impl.cpp
#include "{{ namespace_abbr }}_dts_impl.h"
#include <DDS/DTS/generic_dts_impl.h>
namespace {{ namespace }}
{
namespace Interface
{
// initializer function
std::shared_ptr<Dts> Dts::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
{
return std::make_shared<DtsImpl>(ddsConnector);
}
// DtsImpl
DtsImpl::DtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: Dts()
, m_ddsConnector(ddsConnector)
, m_logger(getLogger("Dts"))
{}
DtsImpl::~DtsImpl()
{
enableAll(false);
}
bool DtsImpl::enableAll(bool enable)
{
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
if (!enable{{ dt.name }}(enable))
{
WARNING_LOG(m_logger) << "Error initializing data topic {{ dt.name }}";
return false;
}
{% endif %} {% endfor %}
return true;
}
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
//
// Data Topic '{{ dt.name }}'
//
bool DtsImpl::enable{{ dt.name }}(bool enable)
{
if (enable)
{
if (m_dts{{ dt.name }})
{
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
return true;
}
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
assert(m_dts{{ dt.name }}.get());
}
else
{
m_dts{{ dt.name }} = nullptr;
}
return true;
}
bool DtsImpl::publish(const {{ namespace }}::{{ dt.type }} &data)
{
if (!m_dts{{ dt.name }})
{
WARNING_LOG(m_logger) << "Error publishing data topic {{ dt.name }} - not enabled";
Q_ASSERT(false);
return false;
}
return m_dts{{ dt.name }}->write(data);
}
{% if dt.key.name %}
bool DtsImpl::remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists)
{
if (!m_dts{{ dt.name }})
{
WARNING_LOG(m_logger) << "Error removing data topic {{ dt.name }} - not enabled";
Q_ASSERT(false);
return false;
}
{{ namespace }}::{{ dt.name }} data;
data.{{ dt.key.name }}({{ dt.key.name }});
return m_dts{{ dt.name }}->remove(data, errorMsgIfSampleDoesNotExists);
}
{% endif %}
{% endif %} {% endfor %}
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+58
View File
@@ -0,0 +1,58 @@
/**
* {{ namespace_abbr_upper }} Data Topic Server Interface
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-04-07
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_dts_impl.h
#pragma once
#include <QString>
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h>
#include <{{ include_prefix }}/logger.h>
#include <DDS/DDSConnector/dds_connector.h>
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB)
#error "You are not allowed to include this file in your code!"
#endif
namespace {{ namespace }}
{
namespace Interface
{
class DtsImpl final : public Dts
{
public:
explicit DtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
~DtsImpl();
bool enableAll(bool enable = true) override;
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
//
// Data Topic '{{ dt.name }}'
//
bool enable{{ dt.name }}(bool enable = true) override;
bool is{{ dt.name }}Enabled() const override final { return m_dts{{ dt.name }} != nullptr; }
bool publish(const {{ namespace }}::{{ dt.type }} &data) override;
{% if dt.key.name %}
bool remove{{ dt.name }}ByKey({{ dt.key.cppTypeCr }} {{ dt.key.nameFlc }}, bool errorMsgIfSampleDoesNotExists = true) override;
{% endif %} {% endif %} {% endfor %}
protected:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
{% for dt in dataTopics.member %} {% if dt.dtsImpl %}
std::unique_ptr<DTS::{{ dt.name }}> m_dts{{ dt.name }};
{% endif %} {% endfor %}
LOGGER_T m_logger;
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+32
View File
@@ -0,0 +1,32 @@
/**
* @RDE_HEADER@
* Template for idl struct definitions
* $Id$
* $HeadURL$
* $Author$
* Created by Hanna E. Rathje, SLSE, 2015-08-19
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_enums.idl
#ifndef {{ namespace_abbr }}_enums_idl_included
#define {{ namespace_abbr }}_enums_idl_included
//@copy #include <{{ include_prefix }}/{{ namespace_abbr }}_export.h> // #define NDDSUSERDllExport for linux
module {{ namespace }}
{
{% for idl_enum in idl_enums %}
enum {{ idl_enum.name }}
{
{% for idl_member in idl_enum.member %}
{{ idl_member.key }} = {{ idl_member.value }}{% if not forloop.last %},{% endif %} {% endfor %}
};
{% endfor %}
}; // module {{ namespace }}
#endif
{% endautoescape %}
+78
View File
@@ -0,0 +1,78 @@
/**
* {{ namespace }} Function Topic Client interface
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_ftc.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
#include <DDS/Core/function_reply_object.h>
namespace DDSConnector
{
class DdsConnector;
}
namespace {{ namespace }}
{
namespace Interface
{
class Ftc
{
protected:
Ftc() = default;
public:
virtual ~Ftc() = default;
/**
* Create a new instance for {{ namespace }}::Interface::Ftc.
* The caller takes ownership of the returned instance!
*/
{{ namespace_abbr_upper }}_CLIENT_EXPORT static std::shared_ptr<Ftc> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector, bool forceCreate = false);
/**
* A unique identifier for this FTC
*/
{{ namespace_abbr_upper }}_CLIENT_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Ftc"; }
/**
* initialize this Ftc instance with all function topic clients
*/
virtual bool enableAll(bool enable = true) = 0;
/**
* only initialize the specific function topic client (can be called instead enableAll())
*/
{% for ft in functionTopics.member %}
virtual bool enable{{ ft.name }}(bool enable = true) = 0;
virtual bool is{{ ft.name }}Enabled() const = 0;
{% endfor %}
{% for ft in functionTopics.member %}
//
// Function Topic '{{ ft.name }}'
//
class {{ ft.replyHandler }}
{
public:
{{ ft.replyHandler }}() = default;
virtual ~{{ ft.replyHandler }}() = default;
virtual void {{ ft.onReply }}(const {{ ft.replyTypeFro }} &reply) = 0;
};
virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) = 0;
virtual int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb) = 0;
virtual {% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) = 0; // await
virtual int64_t last{{ ft.request }}UniqueId() const = 0;
{% endfor %}
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+254
View File
@@ -0,0 +1,254 @@
/**
* {{ namespace }} Function Topic Client implementation
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_ftc_impl.cpp
#include "{{ namespace_abbr }}_ftc_impl.h"
#include <DDS/FTC/generic_ftc_impl.h>
#include <LtsUtilsCore/await.h>
#include <QCoreApplication>
#include <QThread>
#include <unordered_map>
#include <chrono>
//using namespace std::literals::chrono_literals;
namespace {{ namespace }}
{
namespace Interface
{
// initializer function
Q_DECL_EXPORT std::shared_ptr<Ftc> Ftc::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector, bool forceCreate)
{
// share only for main thread
if (qApp->thread() != QThread::currentThread() || forceCreate)
return std::make_shared<FtcImpl>(ddsConnector);
static std::unordered_map<DDSConnector::DdsConnector*, std::weak_ptr<Ftc>> s_availableConnectors;
auto it = s_availableConnectors.find(ddsConnector.get());
if (it != s_availableConnectors.end())
{
auto ret = it->second.lock();
if (ret)
return ret;
s_availableConnectors.erase(it);
}
auto ret = std::make_shared<FtcImpl>(ddsConnector);
s_availableConnectors.insert(std::make_pair(ddsConnector.get(), ret));
return std::move(ret); // c++11 defect
}
// FtcImpl
FtcImpl::FtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: Ftc()
, m_ddsConnector(ddsConnector)
, m_logger(getLogger("Ftc"))
{}
bool FtcImpl::enableAll(bool enable)
{ {% for ft in functionTopics.member %}
if (!enable{{ ft.name }}(enable))
{
ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}";
return false;
}
{% endfor %}
return true;
}
FtcImpl::~FtcImpl()
{
enableAll(false);
}
{% for ft in functionTopics.member %}
// Function Topic '{{ ft.name }}'
bool FtcImpl::enable{{ ft.name }}(bool enable)
{
if (enable)
{
if (m_ftc{{ ft.name }})
{
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
return true;
}
m_ftc{{ ft.name }}.reset(
m_ddsConnector->createFTC<FTC::{{ ft.name }}>(this));
assert(m_ftc{{ ft.name }}.get());
}
else
{
m_ftc{{ ft.name }} = nullptr;
}
return true;
}
{% endfor %}
template<typename T>
static T buildErrorReason(int reason, int requestId)
{
std::string errString;
std::vector<std::string> errArgs;
switch (reason)
{
case 1:
errString = tr_lts("dds", "Service could not handle request");
break;
case 3:
errString = tr_lts("dds", "Timeout while waiting for answer from server");
break;
default:
errString = tr_lts("dds", "Unknown error reason: %1");
errArgs.push_back(std::to_string(reason));
break;
}
T reply;
reply.id(requestId);
reply.errorString(errString);
reply.errorParams(errArgs);
return reply;
}
{% for ft in functionTopics.member %}
//
// {{ ft.name }}
//
int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler)
{
return {{ ft.requestFlc }}(request, [ handler ](const {{ ft.replyTypeFro }} &reply) { if (handler) handler->{{ ft.onReply }}(reply); });
}
int64_t FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb)
{
if (!m_ftc{{ ft.name }})
{
WARNING_LOG(m_logger) << "Error requesting function topic {{ ft.name }} - not enabled";
Q_ASSERT(false);
return 0;
}
static const char * const slotReply = SLOT({{ ft.onReply }}({{ namespace }}::{{ ft.reply }},int,bool));
static const char * const slotError = SLOT({{ ft.onError }}(int,int,int));
int uniqueId = 0;
if (!m_ftc{{ ft.name }}->requestDataClassic(request, slotReply, slotError, uniqueId))
return 0;
m_{{ ft.replyHandlerFlc }}[uniqueId] = cb;
// first 16 hex bytes from Sha3_512 hash for '{{ ft.name }}' converted to an int64
m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }};
return m_last{{ ft.request }}UniqueId;
}
// NOTE: do not use if you call an FTC which returns an id while also containing an error as "error doesn't mean that the service might not reply with an id - so always take the id"
{% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} FtcImpl::{{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false)
{
if (!m_ftc{{ ft.name }})
{
Q_ASSERT(false);
throw std::runtime_error("Error requesting function topic {{ ft.name }} - not enabled");
}
auto *requestWatcher = new Request2();
auto future = m_ftc{{ ft.name }}->requestDataModern(request, requestWatcher);
m_currentlyOpen_{{ ft.requestFlc }}Requests++;
//fprintf(stderr, "{{ ft.requestFlc }}: waiting for reply... (currently open requests: %d)\n", m_currentlyOpen_{{ ft.requestFlc }}Requests);
LtsUtils::await(requestWatcher, &Request2::finished, true); // blocks until request has reply or failed
m_currentlyOpen_{{ ft.requestFlc }}Requests--;
//fprintf(stderr, "{{ ft.requestFlc }}: request done!\n");
// the generic ftc impl's 'requestDataModern' + 'exec()' guarantees that calling 'future.get()' will not result in a 'wait()' call by 'get()' - but we should still check!
switch (future.wait_for(std::chrono::seconds(1)))
{
case std::future_status::deferred:
{
Q_ASSERT(false);
// fprintf(stderr, "{{ ft.requestFlc }}: deferred...\n");
break;
}
case std::future_status::timeout:
{
Q_ASSERT(false);
// fprintf(stderr, "{{ ft.requestFlc }}: future timeout!\n");
throw std::runtime_error("Future timeout!");
}
case std::future_status::ready:
{
// fprintf(stderr, "{{ ft.requestFlc }}: ready\n");
break;
}
}
{% if ft.isCoreFt %}
const auto _reply = future.get(); // throws if exception set
Core::FunctionReplyObject fro(
{% for rp in ft.replyParams %} _reply.{{ rp.nameGetter }}(){% if not forloop.last %},{% endif %} {% endfor %}
);
fro.setUniqueId(m_last{{ ft.request }}UniqueId);
if (fro.error())
{
ERROR_LOG(m_logger) << "FTC call failed: " << fro.translated();
throw std::runtime_error(fro.translated().toStdString().c_str());
}
const auto reply = fro.id();
{% else %}
const auto reply = future.get(); // throws if exception set
{% endif %}
return reply;
}
void FtcImpl::{{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished)
{
auto it = m_{{ ft.replyHandlerFlc }}.find(uniqueId);
if (it == m_{{ ft.replyHandlerFlc }}.end())
{
WARNING_LOG(m_logger) << "Error replying function topic {{ ft.name }} - no replyHandler";
Q_ASSERT(false);
return;
}
auto cb = it.value();
if (bRequestFinished)
m_{{ ft.replyHandlerFlc }}.erase(it);
if (!cb)
return;
m_last{{ ft.request }}UniqueId = uniqueId + {{ ft.replyHandlerHash }};
{% if ft.isCoreFt %}
Core::FunctionReplyObject fro(
{% for rp in ft.replyParams %}
reply.{{ rp.nameGetter }}(){% if not forloop.last %},{% endif %} {% endfor %}
);
fro.setUniqueId(m_last{{ ft.request }}UniqueId);
cb(fro);
{% else %}
cb(reply);
{% endif %}
}
void FtcImpl::{{ ft.onError }}(int reason, int uniqueId, int requestId)
{
{{ ft.onReply }}(buildErrorReason<{{ namespace }}::{{ ft.reply }}>(reason, requestId), uniqueId, true);
}
{% endfor %}
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+81
View File
@@ -0,0 +1,81 @@
/**
* {{ namespace }} Function Topic Client implementation
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_ftc_impl.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_ftc_types.h>
#include <{{ include_prefix }}/logger.h>
#include <DDS/FTC/abstract_requester.h>
#include <DDS/DDSConnector/dds_connector.h>
#include <QObject>
#include <QHash>
#include <QDebug>
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}CLIENT_LIB)
#error "You are not allowed to include this file in your code!"
#endif
namespace {{ namespace }}
{
namespace Interface
{
class FtcImpl final : public QObject, public Ftc
{
Q_OBJECT
public:
explicit FtcImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
~FtcImpl();
bool enableAll(bool enable = true) override;
{% for ft in functionTopics.member %}
bool enable{{ ft.name }}(bool enable = true) override;
bool is{{ ft.name }}Enabled() const override { return m_ftc{{ ft.name }} != nullptr; }
{% endfor %}
{% for ft in functionTopics.member %}
//
// Function Topic '{{ ft.name }}'
//
/// Classic request:
public:
int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, {{ ft.replyHandler }} *handler) override; // returns the uniqueId used for this request
int64_t {{ ft.requestFlc }}(const {{ ft.requestType }} &request, const std::function<void(const {{ ft.replyTypeFro }}&)> &cb) override; // returns the uniqueId used for this request
int64_t last{{ ft.request }}UniqueId() const override { return m_last{{ ft.request }}UniqueId; }
private Q_SLOTS:
void {{ ft.onReply }}(const {{ namespace }}::{{ ft.reply }} &reply, int uniqueId, bool bRequestFinished);
void {{ ft.onError }}(int reason, int uniqueId, int requestId);
private:
QHash<int, std::function<void(const {{ ft.replyTypeFro }}&)>> m_{{ ft.replyHandlerFlc }};
int64_t m_last{{ ft.request }}UniqueId = -1;
/// Modern request:
public:
/* Executes the request and blocks (enters Qt's event loop) until the reply has arrived.
* NOTE: If reply has a FunctionReplyWithState, this function only returns the first reply and returns.
{% if ft.isCoreFt %} * Returns the id of the FunctionReply.{% endif %}*/
{% if ft.isCoreFt %}int32_t{% else %}{{ ft.replyTypeFro }}{% endif %} {{ ft.requestFlc }}(const {{ ft.requestType }} &request) noexcept(false) override;
private:
int m_currentlyOpen_{{ ft.requestFlc }}Requests { 0 };
std::unique_ptr<FTC::{{ ft.name }}> m_ftc{{ ft.name }};
{% endfor %}
private:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
LOGGER_T m_logger;
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+90
View File
@@ -0,0 +1,90 @@
/**
* {{ namespace }} Function Topic Server interface
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_fts.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
#include <DDS/Core/function_reply_object.h>
namespace DDSConnector
{
class DdsConnector;
}
namespace {{ namespace }}
{
namespace Interface
{
class Fts
{
protected:
Fts() = default;
public:
virtual ~Fts() = default;
/**
* Create a new instance for {{ namespace }}::Interface::Dts.
* The caller takes ownership of the returned instance
*/
{{ namespace_abbr_upper }}_SERVER_EXPORT static std::shared_ptr<Fts> create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
/**
* A unique identifier for this FTC
*/
{{ namespace_abbr_upper }}_SERVER_EXPORT static inline const char *identifier() { return "{{ namespace }}.Interface.Fts"; }
/**
* initialize this Fts instance with all function topic servers
*/
virtual bool enableAll(bool enable = true) = 0;
/**
* only initialize the specific function topic writer (can be called instead enableAll())
*/
{% for ft in functionTopics.member %}
virtual bool enable{{ ft.name }}(bool enable = true) = 0;
virtual bool is{{ ft.name }}Enabled() const = 0;
{% endfor %}
{% for ft in functionTopics.member %}
//
// Function Topic '{{ ft.name }}'
//
class {{ ft.requestHandler }}
{
public:
explicit {{ ft.requestHandler }}(const std::shared_ptr<Fts> &fts)
: m_fts(fts)
{
if (fts)
fts->set{{ ft.requestHandler }}(this);
}
virtual ~{{ ft.requestHandler }}()
{
auto sp = m_fts.lock();
if (sp)
sp->set{{ ft.requestHandler }}(nullptr);
}
virtual void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
const {{ ft.requestType }} &request) = 0;
private:
std::weak_ptr<Fts> m_fts;
};
virtual void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) = 0;
virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) = 0;
{% if ft.isCoreFt %}
virtual bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) = 0;
{% endif %}
{% endfor %}
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+114
View File
@@ -0,0 +1,114 @@
/**
* {{ namespace }} Function Topic Server implementation
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_fts_impl.cpp
#include "{{ namespace_abbr }}_fts_impl.h"
#include <DDS/FTS/generic_fts_impl.h>
namespace {{ namespace }}
{
namespace Interface
{
// initializer function
std::shared_ptr<Fts> Fts::create(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
{
return std::make_shared<FtsImpl>(ddsConnector);
}
// FtsImpl
FtsImpl::FtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector)
: Fts()
, m_ddsConnector(ddsConnector)
, m_logger(getLogger("Fts"))
{}
FtsImpl::~FtsImpl()
{
enableAll(false);
}
bool FtsImpl::enableAll(bool enable)
{
DEBUG_LOG(m_logger) << "FtsImpl::enableAll(" << (enable ? "true" : "false") << ")";
{% for ft in functionTopics.member %}
if (!enable{{ ft.name }}(enable))
{
ERROR_LOG(m_logger) << "Error initializing function topic {{ ft.name }}";
return false;
}
{% endfor %}
return true;
}
{% for ft in functionTopics.member %}
//
// Function Topic '{{ ft.name }}'
//
bool FtsImpl::enable{{ ft.name }}(bool enable)
{
if (enable)
{
if (m_fts{{ ft.name }})
{
INFO_LOG(m_logger) << "{{ ft.name }} already enabled!";
return true;
}
m_fts{{ ft.name }}.reset(m_ddsConnector->createFTS<FTS::{{ ft.name }}>());
assert(m_fts{{ ft.name }}.get());
m_fts{{ ft.name }}->setSubscriberCallback([this] (const rti::core::SampleIdentity &identity, const {{ namespace }}::{{ ft.request }} &request) { this->{{ ft.onRequest }}(identity, request); });
}
else
{
m_fts{{ ft.name }}.reset();
}
return true;
}
bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply)
{
if (!m_fts{{ ft.name }})
{
WARNING_LOG(m_logger) << "Error publishing function topic {{ dt.name }} - not enabled";
Q_ASSERT(false);
return false;
}
return m_fts{{ ft.name }}->sendReply(cookie, reply);
}
{% if ft.isCoreFt %}
bool FtsImpl::{{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject)
{
return {{ ft.replyFlc }}(cookie, {{ ft.replyType }}(replyObject.id(), replyObject.str(), replyObject.args(), replyObject.additionalInfo()));
}
{% endif %}
void FtsImpl::set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler)
{
m_{{ ft.requestHandlerFlc }} = handler;
}
void FtsImpl::{{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
const {{ namespace }}::{{ ft.request }} &request)
{
DEBUG_LOG(m_logger) << "{{ ft.onRequest }}";
if (!m_{{ ft.requestHandlerFlc }})
{
WARNING_LOG(m_logger) << "Incoming {{ ft.request }} without registered handler!";
Q_ASSERT(false);
return;
}
m_{{ ft.requestHandlerFlc }}->{{ ft.onRequest }}(cookie, request);
}
{% endfor %}
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+66
View File
@@ -0,0 +1,66 @@
/**
* {{ namespace }} Function Topic Server implementation
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_fts_impl.h
#pragma once
#include <{{ include_prefix }}/{{ namespace_abbr }}_fts.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_fts_types.h>
#include <{{ include_prefix }}/logger.h>
#include <DDS/DDSConnector/dds_connector.h>
#include <QObject>
#if !defined(MAKE_SVC{{ namespace_abbr_upper }}SERVER_LIB)
#error "You are not allowed to include this file in your code!"
#endif
namespace {{ namespace }}
{
namespace Interface
{
class FtsImpl final : public QObject, public Fts
{
Q_OBJECT
public:
explicit FtsImpl(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector);
~FtsImpl();
bool enableAll(bool enable = true) override;
{% for ft in functionTopics.member %}
//
// {{ ft.name }}
//
public:
bool enable{{ ft.name }}(bool enable = true) override;
bool is{{ ft.name }}Enabled() const override { return m_fts{{ ft.name }} != nullptr && m_{{ ft.requestHandlerFlc }} != nullptr; }
bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const {{ ft.replyType }} &reply) override;
{% if ft.isCoreFt %}
bool {{ ft.replyFlc }}(const rti::core::SampleIdentity &cookie, const Core::FunctionReplyObject &replyObject) override;
{% endif %}
void set{{ ft.requestHandler }}({{ ft.requestHandler }} *handler) override;
private Q_SLOTS:
void {{ ft.onRequest }}(const rti::core::SampleIdentity &cookie,
const {{ namespace }}::{{ ft.request }} &request);
private:
std::unique_ptr<FTS::{{ ft.name }}> m_fts{{ ft.name }};
{{ ft.requestHandler }} *m_{{ ft.requestHandlerFlc }} = nullptr;
{% endfor %}
private:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
LOGGER_T m_logger;
};
} // namespace Interface
} // namespace {{ namespace }}
{% endautoescape %}
+160
View File
@@ -0,0 +1,160 @@
/**
* Json <-> DDS conversion routines
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-11-30
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_json.cpp
#include "{{ namespace_abbr }}_json.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
#include <LtsUtilsCore/dds_vector.h>
#include <dds/core/String.hpp>
namespace {{ namespace }}
{
template<>
int fromJson(const QJsonValue &val, type<int>)
{
return val.toInt();
}
template<>
double fromJson(const QJsonValue &val, type<double>)
{
return val.toDouble();
}
template<>
qint64 fromJson(const QJsonValue &val, type<qint64>)
{
return val.toString().toLongLong();
}
QJsonValue toJson(int vec)
{
return QJsonValue(vec);
}
QJsonValue toJson(double vec)
{
return QJsonValue(vec);
}
QJsonValue toJson(qint64 vec)
{
return QJsonValue(QString::number(vec));
}
// std::string
QJsonValue toJson(const std::string &element)
{
return QJsonValue(QString::fromStdString(element));
}
template<>
std::string fromJson(const QJsonValue &val, type<std::string>)
{
return val.toString().toStdString();
}
// std::vector<T>
template<typename T>
QJsonArray toJson(const std::vector<T> &vec)
{
QJsonArray arr;
for (const auto &element : vec)
arr.push_back(toJson(element));
return arr;
}
template<typename T>
std::vector<T> fromJson(const QJsonValue &val, type<std::vector<T>>)
{
std::vector<T> ret;
if (!val.isArray())
return ret;
const QJsonArray array(val.toArray());
ret.reserve(array.size());
for(int i = 0; i < array.size(); ++i)
ret.push_back(fromJson(array.at(i), type<T>{}));
return ret;
}
// dds::core:vector<uint8_t> (bytestream)
inline QJsonValue toJson(const std::vector<uint8_t> &element)
{
return QJsonValue(QString::fromLatin1(QByteArray::fromRawData(reinterpret_cast<const char *>(&element.at(0)),
static_cast<int>(element.size())).toBase64()));
}
template<>
std::vector<uint8_t> fromJson(const QJsonValue &val, type<std::vector<uint8_t>>)
{
const QByteArray ba(QByteArray::fromBase64(val.toString().toLatin1()));
return LtsUtils::convert<std::vector<uint8_t>>(ba);
}
{% for dt in jsonTopics %}
// {{ dt.name }}
QJsonObject toJson(const {{ dt.name }} &element)
{
QJsonObject obj;
{% for row in dt.cppMember %}
{{ row.ddsToJsonElement }};
{% endfor %}
return obj;
}
template<>
{{ dt.name }} fromJson(const QJsonValue &value, type<{{ dt.name }}>)
{
const QJsonObject objElement(value.toObject());
{{ dt.name }} element;
{% for row in dt.cppMember %}
{ {{ row.jsonElementToDds }}; } {% endfor %}
return element;
}
//
// extensions to support sequence of seqence of Non-POD :
//
QJsonValue toJson(const std::vector <{{ dt.name }} > &element)
{
if (element.size() == 0)
return QJsonValue();
QJsonArray array;
for(size_t i = 0; i < element.size(); ++i)
array.push_back(toJson(element.at(i)));
return array;
}
template<>
std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector <{{ dt.name }} > >)
{
std::vector < {{ dt.name }} > ret;
if (value.isNull())
return ret;
if (!value.isArray())
return ret;
const QJsonArray array = value.toArray();
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<{{ dt.name }}>{}));
return ret;
}
{% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
+50
View File
@@ -0,0 +1,50 @@
/**
* Json <-> DDS conversion routines
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2017-11-30
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_json.h
#pragma once
#include "{{ namespace_abbr }}.hpp"
#include "{{ namespace_abbr }}_export.h"
#include <QJsonObject>
#include <QJsonValue>
namespace {{ namespace }}
{
template<typename T>
struct type{};
template<typename T>
T fromJson(const QJsonValue &val, type<T>);
template<typename T>
T fromJson(const QJsonValue &val)
{
return fromJson(val, type<T>{});
}
{% for dt in jsonTopics %}
// {{ dt.name }}
{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonObject toJson(const {{ dt.name }} &element);
template<> {{ namespace_abbr_upper }}_COMMON_EXPORT {{ dt.name }} fromJson(const QJsonValue &val, type<{{ dt.name }}>);
{{ namespace_abbr_upper }}_COMMON_EXPORT QJsonValue toJson(const std::vector< {{ dt.name }} > &element);
template<> {{ namespace_abbr_upper }}_COMMON_EXPORT std::vector<{{ dt.name }}> fromJson(const QJsonValue &value, type< std::vector< {{ dt.name }} > >);
{% endfor %}
//
// extensions to support sequence of seqence of Non-POD :
//
} // namespace {{ namespace }}
{% endautoescape %}
+876
View File
@@ -0,0 +1,876 @@
/**
* RecordReplay Plugin
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-07-21
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_record_replay_plugin.cpp
#include "{{ namespace_abbr }}_record_replay_plugin.h"
#include <ngldb/nsqlquery.h>
#include <DDS/DTS/generic_dts_impl.h>
#include <DDS/DTC/generic_dtc_impl.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QtSql/QSqlError>
#include <LtsUtilsCore/dds_vector.h>
namespace {{ namespace }}
{
template<typename T>
struct type{};
// conversion DDS -> QVariant, encapsulate complex members into json
template<typename T>
QJsonValue toJson(const T &val);
template<typename T>
QVariant toQVariant(const T &val)
{
return QVariant::fromValue(val);
}
template<>
QJsonValue toJson(const int &vec)
{
return QJsonValue(vec);
}
template<>
QJsonValue toJson(const double &vec)
{
return QJsonValue(vec);
}
template<>
QJsonValue toJson(const qint64 &val)
{
return QJsonValue(QString::number(val));
}
template<>
QJsonValue toJson(const std::vector<int> &val)
{
if (val.size() == 0)
return QJsonValue();
QJsonArray array;
for(size_t i = 0; i < val.size(); ++i)
array.push_back(toJson(val.at(i)));
return QJsonValue(array);
}
template<typename T>
QVariant toQVariant(const dds::core::safe_enum<T> &val)
{
// do not use T here - otherwise the implicit ctor dds::core::safe_enum<T>() is used which leads to infinite recursion
return toQVariant<int>(val.underlying());
}
template<>
QJsonValue toJson(const std::string &element)
{
return QJsonValue(QString::fromStdString(element));
}
template<>
QVariant toQVariant(const std::string &val)
{
return QString::fromStdString(val);
}
template<typename T>
QVariant toQVariant(const std::vector<T> &data)
{
if (data.size() == 0)
return QString();
QJsonArray array;
for(size_t i = 0; i < data.size(); ++i)
array.push_back(toJson(data.at(i)));
return QString::fromUtf8(QJsonDocument(array).toJson());
}
template<>
QVariant toQVariant(const std::vector<uint8_t> &data)
{
if (data.size() == 0)
return QVariant();
return QVariant(LtsUtils::convert<QByteArray>(data));
}
template<typename T>
static QVariant toQVariant(const dds::core::optional<T> &val)
{
if (val.is_set())
return toQVariant(val.get());
return QVariant();
}
// conversion QVariant -> DDS, decode complex members from json
template<typename T>
T fromJson(const QJsonValue &val, type<T>);
template<>
int fromJson(const QJsonValue &val, type<int>)
{
return val.toInt();
}
template<>
double fromJson(const QJsonValue &val, type<double>)
{
return val.toDouble();
}
template<>
qint64 fromJson(const QJsonValue &val, type<qint64>)
{
return val.toString().toLongLong();
}
/*
template<>
std::vector<int> fromJson(const QJsonValue &val, type<std::vector<int>>)
{
std::vector<int> ret;
if (val.isNull())
return ret;
const QJsonArray array(QJsonDocument::fromJson(val.toString().toUtf8()).array());
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<int>{} ));
return ret;
}
*/
template<typename T>
std::vector<T> fromJson(const QJsonValue &jsonData, type<std::vector<T>>)
{
std::vector<T> ret;
if (jsonData.isNull())
return ret;
const QJsonArray array(jsonData.toArray());
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<T>{}));
return ret;
}
template<typename T>
std::vector<T> fromJson(const QVariant &jsonData, type<std::vector<T>>)
{
std::vector<T> ret;
if (jsonData.isNull())
return ret;
const QJsonArray array(QJsonDocument::fromJson(jsonData.toString().toUtf8()).array()); // warning: ugly and didn't work in the fromJson version above
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<T>{}));
return ret;
}
template<typename T>
T fromQVariant(const QVariant &val, type<T>);
template<>
bool fromQVariant(const QVariant &val, type<bool>)
{
return val.toBool();
}
template<>
short fromQVariant(const QVariant &val, type<short>)
{
return static_cast<short>(val.toInt());
}
template<>
int fromQVariant(const QVariant &val, type<int>)
{
return val.toInt();
}
template<>
long long fromQVariant(const QVariant &val, type<long long>)
{
return val.toLongLong();
}
template<>
double fromQVariant(const QVariant &val, type<double>)
{
return val.toDouble();
}
template<>
std::string fromJson(const QJsonValue &val, type<std::string>)
{
return val.toString().toStdString();
}
template<>
std::string fromQVariant(const QVariant &val, type<std::string>)
{
return val.toString().toStdString();
}
template<typename T>
dds::core::safe_enum<T> fromQVariant(const QVariant &val, type<dds::core::safe_enum<T>>)
{
using inner = typename dds::core::safe_enum<T>::inner_enum;
return static_cast<inner>(val.toInt());
}
template<typename T>
std::vector<T> fromQVariant(const QVariant &val, type<std::vector<T>> t)
{
return fromJson(val, t);
}
template<>
std::vector<uint8_t> fromQVariant(const QVariant &in, type<std::vector<uint8_t>>)
{
return LtsUtils::convert<std::vector<uint8_t>>(in.toByteArray());
}
template<typename T>
static dds::core::optional<T> fromQVariant(const QVariant &val, type<dds::core::optional<T>>)
{
if (val.isNull())
return dds::core::optional<T>();
return fromQVariant(val, type<T>{});
}
template<typename T>
T fromJson(const QJsonValue &val)
{
return fromJson(val, type<T>{});
}
template<typename T>
T fromQVariant(const QVariant &val)
{
return fromQVariant(val, type<T>{});
}
{% for dt in serializeTopics.member %}
//
// Begin {{ dt.typeFq }}
//
template<>
{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>)
{
const QJsonObject objElement(val.toObject());
{{ dt.typeFq }} element;
{% for row in dt.recTable.cppMember %}
{ {{ row.jsonElementToDds }}; } {% endfor %}
return element;
}
template<>
QJsonValue toJson(const {{ dt.typeFq }} &element)
{
QJsonObject obj;
{% for row in dt.recTable.cppMember %}
{{ row.ddsToJsonElement }}; {% endfor %}
return obj;
}
template <>
QVariant toQVariant(const {{ dt.typeFq }} &val)
{
return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson());
}
template<>
{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>)
{
return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{});
}
//
// extensions to support sequence of seqence of Non-POD :
//
template<>
QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data)
{
if (data.size() == 0)
return QJsonValue();
QJsonArray array;
for(size_t i = 0; i < data.size(); ++i)
array.push_back(toJson(data.at(i)));
return array;
}
template<>
std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val)
{
std::vector < {{ dt.typeFq }} > ret;
if (val.isNull())
return ret;
if (!val.isArray())
return ret;
const QJsonArray array = val.toArray();
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{}));
return ret;
}
//
// : extensions to support sequence of seqence of Non-POD
//
//
// End {{ dt.typeFq }}
//
{% endfor %}
RecordReplayPlugin::RecordReplayPlugin()
: m_exerciseId(0)
, m_bCanHandleSchemasRead(true)
, m_bCanHandleSchemasWrite(true)
, m_logger(getLogger("RecordReplay"))
{}
RecordReplayPlugin::~RecordReplayPlugin() = default;
QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const
{
if (!bCanHandleSchemas)
return tableName;
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
}
bool RecordReplayPlugin::initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId)
{
m_ddsConnector = ddsConnector;
m_exerciseId = exerciseId;
if (!m_ddsConnector)
return false;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId,
bool bCanHandleSchemas,
NSqlQuery &query)
{
Q_UNUSED(query);
m_ddsConnector = ddsConnector;
m_exerciseId = exerciseId;
m_bCanHandleSchemasRead = bCanHandleSchemas;
m_bCanHandleSchemasWrite = bCanHandleSchemas;
if (!m_ddsConnector)
return false;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite)
{
m_exerciseId = exerciseId;
m_bCanHandleSchemasRead = bCanHandleSchemasRead;
m_bCanHandleSchemasWrite = bCanHandleSchemasWrite;
return true;
}
bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
// {{ dt.name }}
if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false;
if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!read{{ dt.name }}At(q, startTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!read{{ dt.name }}(q, fromTime, toTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::publishData(qint64 desiredTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!publish{{ dt.name }}(desiredTime)) return false;
{% endif %} {% endfor %}
return true;
}
void RecordReplayPlugin::clearData()
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
clearData{{ dt.name }}();
{% endif %} {% endfor %}
}
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q,
qint64 toTime,
qint64 interval,
const QHash<QString, qint64> &minRecordedSampleTimes)
{
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> ret;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
{
const QString &topicName(QStringLiteral("{{ dt.name }}"));
std::vector<RecordReplayPlugin::RecSnapshot> vec;
auto it = minRecordedSampleTimes.find(topicName);
if (it != minRecordedSampleTimes.end())
{
qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
if (nextSampleTime != m_snapshot{{ dt.name }}MapTime)
{
// the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys
readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime);
//DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime
// << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries";
}
while (nextSampleTime > 0 && nextSampleTime <= toTime)
{
const qint64 intervalStart = nextSampleTime;
const qint64 intervalEnd = intervalStart + interval;
updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd);
vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map));
nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd);
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
}
}
if (!vec.empty())
ret.insert({topicName, std::move(vec)});
}
{% endif %} {% endfor %}
return ret;
}
QVector<QString> RecordReplayPlugin::getRecordTopicNames() const
{
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
};
}
QVector<QString> RecordReplayPlugin::getRecordTableNames() const
{
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
};
}
qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const
{
QString tableName;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (topicName == QLatin1String("{{ dt.name }}"))
tableName = QStringLiteral("{{ dt.recTable.name }}");
{% endif %} {% endfor %}
if (tableName.isEmpty())
return defaultValue;
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName);
if (!q.execAndFetch(stmt))
return defaultValue;
const QVariant val = q.value(0);
return val.isNull() ? defaultValue : val.toLongLong();
}
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime)
{
// read snapshot data for {{ dt.name }}
if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false;
// read other samples between snapshot time and startTime
if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false;
return true;
}
bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
{
using DATA = {{ namespace }}::{{ dt.name }};
if (!m_dtc{{ dt.name }})
return false;
qint64 minRecordSampleTime = std::numeric_limits<qint64>::max();
if (m_dtc{{ dt.name }}->hasSamples())
{
TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true ";
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
if (!q.prepare(stmt))
return false;
dds::sub::LoanedSamples<DATA> samples = m_dtc{{ dt.name }}->takeAlive();
TRACE_LOG(m_logger) << "samples count: " << samples.length();
for (const auto &sample : samples)
{
const DATA &data = sample.data();
INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }}();
{% for row in dt.recTable.cppMember %}
q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }});
{% endfor %}
q.bindValue(QStringLiteral(":dds_is_alive"), true);
if (!q.exec())
return false;
minRecordSampleTime = std::min(minRecordSampleTime, data.sampleTime());
}
}
dds::sub::LoanedSamples<DATA> disposedSamples = m_dtc{{ dt.name }}->takeDisposed();
if (disposedSamples.length() != 0)
{
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES "
" (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});"));
if (!q.prepare(stmt))
return false;
DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length();
auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader();
{{ dt.name }} data;
for (const auto &sample : disposedSamples)
{
const qint64 sampleTime = sample.info().source_timestamp().to_millisecs();
dr.key_value(data, sample.info().instance_handle());
q.bindValue(QStringLiteral(":dds_is_alive"), false);
q.bindValue(QStringLiteral(":sample_time"), sampleTime);
q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }});
if (!q.exec())
return false;
minRecordSampleTime = std::min(minRecordSampleTime, sampleTime);
}
}
if (minRecordSampleTime != std::numeric_limits<qint64>::max())
minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime;
return true;
}
// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time
bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime)
{
// rebuild m_{{ dt.nameFlc }}Map content for given startTime
clearData{{ dt.name }}();
const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
return false;
}
if (!q.next() || q.value(0).isNull())
{
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
return true;
}
const qint64 idSnapshot = q.value(0).toLongLong();
const qint64 timeSnapshot = q.value(1).toLongLong();
const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
if (!q.exec(stmt2))
return false;
{{ namespace }}::{{ dt.name }} data;
while (q.next())
{
int i = 0;
const bool bIsAlive = q.value(i++).toBool();
{% for row in dt.recTable.viewMember %}
{{ row.qVariantToDds }};
{% endfor %}
if (bIsAlive)
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
else
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
}
m_snapshot{{ dt.name }}MapTime = timeSnapshot;
return true;
}
bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
if (!q.exec(stmt))
return false;
{{ namespace }}::{{ dt.name }} data;
while (q.next())
{
int i = 0;
const bool bIsAlive = q.value(i++).toBool();
{% for row in dt.recTable.viewMember %}
{{ row.qVariantToDds }};
{% endfor %}
if (bIsAlive)
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
else
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
}
return true;
}
bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) +
QStringLiteral(" ORDER BY sample_time "));
DEBUG_LOG(m_logger) << "stmt: " << stmt;
if (!q.exec(stmt))
return false;
while (q.next())
{
const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }};
const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }};
const bool isAlive = q.value(2).toBool();
if (isAlive)
{
DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
m_snapshot{{ dt.name }}Map[key] = pk;
m_{{ dt.nameFlc }}Keys.insert(key);
}
else
{
DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
m_snapshot{{ dt.name }}Map.erase(key);
m_{{ dt.nameFlc }}Keys.erase(key);
}
}
// new validity time for snapshot data
m_snapshot{{ dt.name }}MapTime = toTime;
DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = "
<< m_snapshot{{ dt.name }}Map.size();
return true;
}
bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
{
const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(fromTime));
// no previous snapshot found?
if (!q.exec(stmt))
return false;
if (!q.next() || q.value(0).isNull())
{
DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found";
return true;
}
m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())();
DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size();
return true;
}
qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
{
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime));
qint64 ret = -1;
if (q.execAndFetch(stmt) && !q.value(0).isNull())
ret = q.value(0).toLongLong();
DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")";
return ret;
}
bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime)
{
if (!m_dts{{ dt.name }})
return false;
{
auto it = m_{{ dt.nameFlc }}Map.begin();
while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime)
{
m_dts{{ dt.name }}->write(it.value());
it = m_{{ dt.nameFlc }}Map.erase(it);
}
}
{
auto it = m_{{ dt.nameFlc }}DisposedMap.begin();
while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime)
{
m_dts{{ dt.name }}->remove(it.value());
it = m_{{ dt.nameFlc }}DisposedMap.erase(it);
}
}
return true;
}
void RecordReplayPlugin::clearData{{ dt.name }}()
{
if (m_dts{{ dt.name }})
{
{{ dt.name }} tmpData;
for (auto key : m_{{ dt.nameFlc }}Keys)
{
tmpData.{{ dt.key.name }}(key);
m_dts{{ dt.name }}->remove(tmpData);
}
}
m_{{ dt.nameFlc }}Keys.clear();
m_{{ dt.nameFlc }}Map.clear();
m_{{ dt.nameFlc }}DisposedMap.clear();
}
bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime)
{
const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
if (!queryFrom.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
return false;
}
if (!queryFrom.next() || queryFrom.value(0).isNull())
{
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
return true;
}
const qint64 idSnapshot = queryFrom.value(0).toLongLong();
const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
if (!queryFrom.exec(stmtSelect))
return false;
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
}
bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
if (!queryFrom.exec(stmt))
return false;
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
}
bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo)
{
const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
if (!queryTo.prepare(stmtInsert))
return false;
while (queryFromPrepared.next())
{
int i = 0;
queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++));
{% for row in dt.recTable.cppMember %}
queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++));
{% endfor %}
if (!queryTo.exec())
return false;
}
return true;
}
{% endif %} {% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
@@ -0,0 +1,637 @@
/**
* RecordReplay Plugin
* $Id: svc_record_replay_plugin.cpp.grantlee 50268 2020-03-06 07:42:09Z ehrlicher $
* $HeadURL: svn://slssvn/TDS/branches/product/iterations/21_07_assault_energy/Lts/Common/CodeGen/svc_record_replay_plugin.cpp.grantlee $
* $Author: ehrlicher $
* Created by Christian Ehrlicher, SLSE, 2016-07-21
*/
Leider gibnt es Probleme, wenn man Sequenzen von Sequenzen von Non-PODs erstellt.
Es erbgaben sich unresolved symbols... (Compiler war zufrieden, Linker aber nicht - hmm)
Mit dem folgenden Konstrukt konnten ich diese "beheben", doch je nach Konstellation in der XYZ_service.xml
ergaben sich Fehler a la "template specialication after instatiation"
Daher haben wir (zumindest zunächst) Abstand von dieser Erweiterung genommen.
...
{% for dt in serializeTopics.member %}
//
// Begin {{ dt.typeFq }}
//
template<>
{{ dt.typeFq }} fromJson(const QJsonValue &val, type<{{ dt.typeFq }}>)
{
const QJsonObject objElement(val.toObject());
{{ dt.typeFq }} element;
{% for row in dt.recTable.cppMember %}
{ {{ row.jsonElementToDds }}; } {% endfor %}
return element;
}
template<>
QJsonValue toJson(const {{ dt.typeFq }} &element)
{
QJsonObject obj;
{% for row in dt.recTable.cppMember %}
{{ row.ddsToJsonElement }}; {% endfor %}
return obj;
}
template <>
QVariant toQVariant(const {{ dt.typeFq }} &val)
{
return QString::fromUtf8(QJsonDocument(toJson(val).toObject()).toJson());
}
template<>
{{ dt.typeFq }} fromQVariant(const QVariant &val, type<{{ dt.typeFq }}>)
{
return fromJson(QJsonDocument::fromJson(val.toString().toUtf8()).object(), type<{{ dt.typeFq }}>{});
}
//
// extensions to support sequence of seqence of Non-POD :
//
template<>
QJsonValue toJson(const std::vector < {{ dt.typeFq }} > &data)
{
if (data.size() == 0)
return QJsonValue();
QJsonArray array;
for(size_t i = 0; i < data.size(); ++i)
array.push_back(toJson(data.at(i)));
return QJsonDocument(array).object();
}
template<>
std::vector < {{ dt.typeFq }} > fromJson(const QJsonValue &val)
{
std::vector < {{ dt.typeFq }} > ret;
if (val.isNull() == 0)
return ret;
if (val.isArray() == 0) return ret;
const QJsonArray array = val.toArray();
ret.reserve(array.size());
for(const auto &ele : array)
ret.push_back(fromJson(ele, type<{{ dt.typeFq }}>{}));
return ret;
}
//
// : extensions to support sequence of seqence of Non-POD
//
//
// End {{ dt.typeFq }}
//
{% endfor %}
RecordReplayPlugin::RecordReplayPlugin()
: m_exerciseId(0)
, m_bCanHandleSchemasRead(true)
, m_bCanHandleSchemasWrite(true)
, m_logger(getLogger("RecordReplay"))
{}
RecordReplayPlugin::~RecordReplayPlugin() = default;
QString RecordReplayPlugin::getTableName(const QString &tableName, bool bCanHandleSchemas) const
{
if (!bCanHandleSchemas)
return tableName;
return DBCommon::getExerciseTableName(tableName, m_exerciseId);
}
bool RecordReplayPlugin::initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId)
{
m_ddsConnector = ddsConnector;
m_exerciseId = exerciseId;
if (!m_ddsConnector)
return false;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
m_dtc{{ dt.name }}.reset(m_ddsConnector->createDTC<DTC::{{ dt.name }}Impl>());
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId,
bool bCanHandleSchemas,
NSqlQuery &query)
{
Q_UNUSED(query);
m_ddsConnector = ddsConnector;
m_exerciseId = exerciseId;
m_bCanHandleSchemasRead = bCanHandleSchemas;
m_bCanHandleSchemasWrite = bCanHandleSchemas;
if (!m_ddsConnector)
return false;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
m_dts{{ dt.name }}.reset(m_ddsConnector->createDTS<DTS::{{ dt.name }}Impl>());
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite)
{
m_exerciseId = exerciseId;
m_bCanHandleSchemasRead = bCanHandleSchemasRead;
m_bCanHandleSchemasWrite = bCanHandleSchemasWrite;
return true;
}
bool RecordReplayPlugin::copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
// {{ dt.name }}
if (!copyInitialData{{ dt.name }}(queryFrom, queryTo, fromTime)) return false;
if (!copyData{{ dt.name }}(queryFrom, queryTo, fromTime, toTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!write{{ dt.name }}(q, minRecordSampleTimes)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::setReplayStartTime(NSqlQuery &q, qint64 startTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!read{{ dt.name }}At(q, startTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!read{{ dt.name }}(q, fromTime, toTime)) return false;
{% endif %} {% endfor %}
return true;
}
bool RecordReplayPlugin::publishData(qint64 desiredTime)
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (!publish{{ dt.name }}(desiredTime)) return false;
{% endif %} {% endfor %}
return true;
}
void RecordReplayPlugin::clearData()
{
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
clearData{{ dt.name }}();
{% endif %} {% endfor %}
}
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> RecordReplayPlugin::getSnapshotTimes(NSqlQuery &q,
qint64 toTime,
qint64 interval,
const QHash<QString, qint64> &minRecordedSampleTimes)
{
std::map<QString, std::vector<RecordReplayPlugin::RecSnapshot>> ret;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
{
const QString &topicName(QStringLiteral("{{ dt.name }}"));
std::vector<RecordReplayPlugin::RecSnapshot> vec;
auto it = minRecordedSampleTimes.find(topicName);
if (it != minRecordedSampleTimes.end())
{
qint64 nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, it.value()); // is 0 or -1 if no samples are available
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
if (nextSampleTime != m_snapshot{{ dt.name }}MapTime)
{
// the current snapshot data does not match the starting time -> reread m_{{ dt.nameFlc }}Keys
readLastSnapshotKeysFor{{ dt.name }}(q, nextSampleTime);
//DEBUG_LOG(m_logger) << "fromTime: " << fromTime << ", m_snapshotParticipantMapTime: " << m_snapshotParticipantMapTime
// << " - rereading snapshot times! Map now contains " << m_snapshot{{ dt.name }}Map.size() << " entries";
}
while (nextSampleTime > 0 && nextSampleTime <= toTime)
{
const qint64 intervalStart = nextSampleTime;
const qint64 intervalEnd = intervalStart + interval;
updateSnapshotKeys{{ dt.name }}(q, intervalStart, intervalEnd);
vec.emplace_back(RecSnapshot(intervalEnd, m_snapshot{{ dt.name }}Map));
nextSampleTime = getNextSampleTimeFor{{ dt.name }}(q, intervalEnd);
nextSampleTime -= (nextSampleTime % interval); // sync to interval - only needed for better debugging
}
}
if (!vec.empty())
ret.insert({topicName, std::move(vec)});
}
{% endif %} {% endfor %}
return ret;
}
QVector<QString> RecordReplayPlugin::getRecordTopicNames() const
{
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
QStringLiteral("{{ dt.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
};
}
QVector<QString> RecordReplayPlugin::getRecordTableNames() const
{
return {{% for dt in dataTopics.member %} {% if dt.recordTopic %}
QStringLiteral("{{ dt.recTable.name }}"){% if not forloop.last %}, {% endif %} {% endif %} {% endfor %}
};
}
qint64 RecordReplayPlugin::getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const
{
QString tableName;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
if (topicName == QLatin1String("{{ dt.name }}"))
tableName = QStringLiteral("{{ dt.recTable.name }}");
{% endif %} {% endfor %}
if (tableName.isEmpty())
return defaultValue;
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") + tableName);
if (!q.execAndFetch(stmt))
return defaultValue;
const QVariant val = q.value(0);
return val.isNull() ? defaultValue : val.toLongLong();
}
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
bool RecordReplayPlugin::read{{ dt.name }}At(NSqlQuery &q, qint64 startTime)
{
// read snapshot data for {{ dt.name }}
if (!readLastSnapshotFor{{ dt.name }}(q, startTime)) return false;
// read other samples between snapshot time and startTime
if (!read{{ dt.name }}(q, m_snapshot{{ dt.name }}MapTime, startTime)) return false;
return true;
}
bool RecordReplayPlugin::write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes)
{
using DATA = {{ namespace }}::{{ dt.name }};
if (!m_dtc{{ dt.name }})
return false;
qint64 minRecordSampleTime = std::numeric_limits<qint64>::max();
if (m_dtc{{ dt.name }}->hasSamples())
{
TRACE_LOG(m_logger) << "m_dtc{{ dt.name }}->hasSamples() == true ";
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
if (!q.prepare(stmt))
return false;
dds::sub::LoanedSamples<DATA> samples = m_dtc{{ dt.name }}->takeAlive();
TRACE_LOG(m_logger) << "samples count: " << samples.length();
for (const auto &sample : samples)
{
const DATA &data = sample.data();
INFO_LOG(m_logger) << "m_dtc{{ dt.name }} {{ dt.recTable.ddsKey.cppMemberName }}: " << data.{{ dt.recTable.ddsKey.cppMemberName }}();
{% for row in dt.recTable.cppMember %}
q.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), {{ row.ddsToQVariant }});
{% endfor %}
q.bindValue(QStringLiteral(":dds_is_alive"), true);
if (!q.exec())
return false;
minRecordSampleTime = std::min(minRecordSampleTime, data.sampleTime());
}
}
dds::sub::LoanedSamples<DATA> disposedSamples = m_dtc{{ dt.name }}->takeDisposed();
if (disposedSamples.length() != 0)
{
const QString stmt(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, sample_time, {{ dt.recTable.ddsKey.dbColumnName }}) VALUES "
" (:dds_is_alive,:sample_time,:{{ dt.recTable.ddsKey.dbColumnName }});"));
if (!q.prepare(stmt))
return false;
DEBUG_LOG(m_logger) << "disposedSamples count: " << disposedSamples.length();
auto dr = m_dtc{{ dt.name }}->dataReader()->dataReader();
{{ dt.name }} data;
for (const auto &sample : disposedSamples)
{
const qint64 sampleTime = sample.info().source_timestamp().to_millisecs();
dr.key_value(data, sample.info().instance_handle());
q.bindValue(QStringLiteral(":dds_is_alive"), false);
q.bindValue(QStringLiteral(":sample_time"), sampleTime);
q.bindValue(QStringLiteral(":{{ dt.recTable.ddsKey.cppMemberName }}"), {{ dt.recTable.ddsKey.ddsToQVariant }});
if (!q.exec())
return false;
minRecordSampleTime = std::min(minRecordSampleTime, sampleTime);
}
}
if (minRecordSampleTime != std::numeric_limits<qint64>::max())
minRecordSampleTimes[QStringLiteral("{{ dt.name }}")] = minRecordSampleTime;
return true;
}
// read the last snapshot which is <= startTime, set m_snapshot{{ dt.name }}MapTime to the snapshot time
bool RecordReplayPlugin::readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime)
{
// rebuild m_{{ dt.nameFlc }}Map content for given startTime
clearData{{ dt.name }}();
const QString stmt(QStringLiteral("SELECT id, time_stamp FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
if (!q.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
return false;
}
if (!q.next() || q.value(0).isNull())
{
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
return true;
}
const qint64 idSnapshot = q.value(0).toLongLong();
const qint64 timeSnapshot = q.value(1).toLongLong();
const QString stmt2(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
if (!q.exec(stmt2))
return false;
{{ namespace }}::{{ dt.name }} data;
while (q.next())
{
int i = 0;
const bool bIsAlive = q.value(i++).toBool();
{% for row in dt.recTable.viewMember %}
{{ row.qVariantToDds }};
{% endfor %}
if (bIsAlive)
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
else
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
}
m_snapshot{{ dt.name }}MapTime = timeSnapshot;
return true;
}
bool RecordReplayPlugin::read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
if (!q.exec(stmt))
return false;
{{ namespace }}::{{ dt.name }} data;
while (q.next())
{
int i = 0;
const bool bIsAlive = q.value(i++).toBool();
{% for row in dt.recTable.viewMember %}
{{ row.qVariantToDds }};
{% endfor %}
if (bIsAlive)
m_{{ dt.nameFlc }}Map.insert(data.sampleTime(), data);
else
m_{{ dt.nameFlc }}DisposedMap.insert(data.sampleTime(), data);
m_{{ dt.nameFlc }}Keys.insert(data.{{ dt.key.name }}());
}
return true;
}
bool RecordReplayPlugin::updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT {{ dt.recTable.ddsKey.dbColumnName }}, {{ dt.recTable.primaryKey.dbColumnName }}, dds_is_alive FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) +
QStringLiteral(" ORDER BY sample_time "));
DEBUG_LOG(m_logger) << "stmt: " << stmt;
if (!q.exec(stmt))
return false;
while (q.next())
{
const qint64 key = q.value(0){{ dt.recTable.ddsKey.cppFromQvPost }};
const qint64 pk = q.value(1){{ dt.recTable.primaryKey.cppFromQvPost }};
const bool isAlive = q.value(2).toBool();
if (isAlive)
{
DEBUG_LOG(m_logger) << "INSERT {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
m_snapshot{{ dt.name }}Map[key] = pk;
m_{{ dt.nameFlc }}Keys.insert(key);
}
else
{
DEBUG_LOG(m_logger) << "REMOVE {{ dt.name }} key: " << key << ", pk: " << pk << ", isAlive: " << isAlive;
m_snapshot{{ dt.name }}Map.erase(key);
m_{{ dt.nameFlc }}Keys.erase(key);
}
}
// new validity time for snapshot data
m_snapshot{{ dt.name }}MapTime = toTime;
DEBUG_LOG(m_logger) << "updateSnapshotKeys{{ dt.name }}(" << fromTime << ", " << toTime << ") = "
<< m_snapshot{{ dt.name }}Map.size();
return true;
}
bool RecordReplayPlugin::readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
{
const QString stmt(QStringLiteral("SELECT rec_id_array FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(fromTime));
// no previous snapshot found?
if (!q.exec(stmt))
return false;
if (!q.next() || q.value(0).isNull())
{
DEBUG_LOG(m_logger) << "no snapshot entry for {{ dt.name }} found";
return true;
}
m_snapshot{{ dt.name }}Map = LtsUtils::BigintArray2D::fromPgString(q.value(0).toString())();
DEBUG_LOG(m_logger) << "Map size {{ dt.name }}: " << m_snapshot{{ dt.name }}Map.size();
return true;
}
qint64 RecordReplayPlugin::getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime)
{
const QString stmt(QStringLiteral("SELECT MIN(sample_time) FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > ") + QString::number(fromTime));
qint64 ret = -1;
if (q.execAndFetch(stmt) && !q.value(0).isNull())
ret = q.value(0).toLongLong();
DEBUG_LOG(m_logger) << "next min sampleTime for {{ dt.name }} is " << ret << " (> " << fromTime << ")";
return ret;
}
bool RecordReplayPlugin::publish{{ dt.name }}(qint64 desiredTime)
{
if (!m_dts{{ dt.name }})
return false;
{
auto it = m_{{ dt.nameFlc }}Map.begin();
while (it != m_{{ dt.nameFlc }}Map.end() && it.key() <= desiredTime)
{
m_dts{{ dt.name }}->write(it.value());
it = m_{{ dt.nameFlc }}Map.erase(it);
}
}
{
auto it = m_{{ dt.nameFlc }}DisposedMap.begin();
while (it != m_{{ dt.nameFlc }}DisposedMap.end() && it.key() <= desiredTime)
{
m_dts{{ dt.name }}->remove(it.value());
it = m_{{ dt.nameFlc }}DisposedMap.erase(it);
}
}
return true;
}
void RecordReplayPlugin::clearData{{ dt.name }}()
{
if (m_dts{{ dt.name }})
{
{{ dt.name }} tmpData;
for (auto key : m_{{ dt.nameFlc }}Keys)
{
tmpData.{{ dt.key.name }}(key);
m_dts{{ dt.name }}->remove(tmpData);
}
}
m_{{ dt.nameFlc }}Keys.clear();
m_{{ dt.nameFlc }}Map.clear();
m_{{ dt.nameFlc }}DisposedMap.clear();
}
bool RecordReplayPlugin::copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime)
{
const QString stmt(QStringLiteral("SELECT id FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE svc_name = '{{ namespace_abbr}}' AND topic_name = '{{ dt.name }}' AND time_stamp <= %1 "
" ORDER BY time_stamp DESC LIMIT 1").arg(startTime));
if (!queryFrom.exec(stmt))
{
ERROR_LOG(m_logger) << "Error while fetch max(time_stamp) for topic {{ dt.name }}";
return false;
}
if (!queryFrom.next() || queryFrom.value(0).isNull())
{
INFO_LOG(m_logger) << "No snapshot available for topic {{ dt.name }}";
return true;
}
const qint64 idSnapshot = queryFrom.value(0).toLongLong();
const QString stmtSelect(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE rec_id IN (SELECT reduce_dim2(rec_id_array) FROM ") + getTableName(QStringLiteral("rec_snapshot"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE id = ") + QString::number(idSnapshot) + QLatin1Char(')'));
if (!queryFrom.exec(stmtSelect))
return false;
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
}
bool RecordReplayPlugin::copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime)
{
const QString stmt(QStringLiteral("SELECT dds_is_alive, {% for row in dt.recTable.viewMember %}{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %} FROM ") +
getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasRead) +
QStringLiteral(" WHERE sample_time > " ) + QString::number(fromTime) +
QStringLiteral(" AND sample_time <= ") + QString::number(toTime) + QLatin1Char(';'));
if (!queryFrom.exec(stmt))
return false;
return copyDataPrepared{{ dt.name }}(queryFrom, queryTo);
}
bool RecordReplayPlugin::copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo)
{
const QString stmtInsert(QStringLiteral("INSERT INTO ") + getTableName(QStringLiteral("{{ dt.recTable.name }}"), m_bCanHandleSchemasWrite) +
QStringLiteral(" ( dds_is_alive, {% for row in dt.recTable.cppMember %} {{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %}) VALUES "
" (:dds_is_alive, {% for row in dt.recTable.cppMember %}:{{ row.dbColumnName }}{% if not forloop.last %}, {% endif %}{% endfor %});"));
if (!queryTo.prepare(stmtInsert))
return false;
while (queryFromPrepared.next())
{
int i = 0;
queryTo.bindValue(QStringLiteral(":dds_is_alive"), queryFromPrepared.value(i++));
{% for row in dt.recTable.cppMember %}
queryTo.bindValue(QStringLiteral(":{{ row.dbColumnName }}"), queryFromPrepared.value(i++));
{% endfor %}
if (!queryTo.exec())
return false;
}
return true;
}
{% endif %} {% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
+126
View File
@@ -0,0 +1,126 @@
/**
* RecordReplay Plugin
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-07-21
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_record_replay_plugin.h
#pragma once
#include <QMultiMap>
#include <QVector>
#include <set>
#include <DDS/DDSConnector/dds_connector.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_dtc_types.h>
#include <{{ include_prefix }}/{{ namespace_abbr }}_dts_types.h>
#include <{{ include_prefix }}/logger.h>
// TODO: Problem with QT5_WRAP_CPP ...
#include <../../Common/PluginInterfaces/record_replay_plugin.h>
#include <LtsUtilsCore/pgarray.h>
{% if custom_record_replay %}
#ifndef {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT
# else
# if defined(MAKE_{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_LIB)
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_EXPORT
# else
# define {{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
{% endif %}
namespace {{ namespace }} {
class {% if custom_record_replay %}{{ namespace_abbr_upper }}RECORDREPLAYPLUGIN_EXPORT{% endif %}
RecordReplayPlugin {% if not custom_record_replay %}final{% endif %}: public QObject, public RecordReplay::RecordReplayPlugin
{
Q_OBJECT
{% if not custom_record_replay %}
Q_PLUGIN_METADATA(IID "special:3:recordreplay.{{ namespace_abbr }}")
{% endif %}
Q_INTERFACES(RecordReplay::RecordReplayPlugin)
public:
RecordReplayPlugin();
~RecordReplayPlugin();
QString pluginName() const override { return QStringLiteral("{{ namespace_abbr }}RecordReplayPlugin"); }
bool initDtc(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId) override;
bool initDts(const std::shared_ptr<DDSConnector::DdsConnector> &ddsConnector,
int exerciseId,
bool bCanHandleSchemas,
NSqlQuery &query) override;
bool initCopy(int exerciseId, bool bCanHandleSchemasRead, bool bCanHandleSchemasWrite) override;
bool copyData(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime) override;
QString svcShortName() const override { return QStringLiteral("{{ namespace_abbr }}"); }
// recording functions
bool ddsTopicsToDatabase(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes) override;
// publishing functions
bool setReplayStartTime(NSqlQuery &q, qint64 startTime) override;
bool ddsTopicsFromDatabase(NSqlQuery &q, qint64 fromTime, qint64 toTime) override;
bool publishData(qint64 desiredTime) override;
void clearData() override;
// structure/functions for snapshot creation
std::map<QString, std::vector<RecSnapshot>> getSnapshotTimes(NSqlQuery &query,
qint64 toTime,
qint64 interval,
const QHash<QString, qint64> &minRecordedSampleTimes) override;
QVector<QString> getRecordTopicNames() const override;
QVector<QString> getRecordTableNames() const override;
qint64 getMinimumSampleTime(NSqlQuery &q, const QString &topicName, qint64 defaultValue) const override;
// getter for initial service startup
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
// {{ dt.name }}
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> &get{{ dt.name }}Map() { return m_{{ dt.nameFlc }}Map; }
const QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> &get{{ dt.name }}Map() const { return m_{{ dt.nameFlc }}Map; }
bool read{{ dt.name }}At(NSqlQuery &q, qint64 startTime);
{% endif %} {% endfor %}
protected:
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
// {{ dt.name }}
bool write{{ dt.name }}(NSqlQuery &q, QHash<QString, qint64> &minRecordSampleTimes);
bool read{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime);
bool readLastSnapshotFor{{ dt.name }}(NSqlQuery &q, qint64 startTime);
bool updateSnapshotKeys{{ dt.name }}(NSqlQuery &q, qint64 fromTime, qint64 toTime);
bool readLastSnapshotKeysFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime);
qint64 getNextSampleTimeFor{{ dt.name }}(NSqlQuery &q, qint64 fromTime);
bool publish{{ dt.name }}(qint64 desiredTime);
void clearData{{ dt.name }}();
bool copyInitialData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 startTime);
bool copyData{{ dt.name }}(NSqlQuery &queryFrom, NSqlQuery &queryTo, qint64 fromTime, qint64 toTime);
bool copyDataPrepared{{ dt.name }}(NSqlQuery &queryFromPrepared, NSqlQuery &queryTo);
{% endif %} {% endfor %}
QString getTableName(const QString &tableName, bool bCanHandleSchemas) const;
protected:
std::shared_ptr<DDSConnector::DdsConnector> m_ddsConnector;
int m_exerciseId;
bool m_bCanHandleSchemasRead;
bool m_bCanHandleSchemasWrite;
{% for dt in dataTopics.member %} {% if dt.recordTopic %}
// {{ dt.name }}
std::unique_ptr<DTC::{{ dt.name }}> m_dtc{{ dt.name }};
std::unique_ptr<DTS::{{ dt.name }}> m_dts{{ dt.name }};
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> m_{{ dt.nameFlc }}Map;
QMultiMap<qint64, {{ include_prefix }}::{{ dt.name }}> m_{{ dt.nameFlc }}DisposedMap;
std::set<qint64> m_{{ dt.nameFlc }}Keys;
std::map<int64_t,int64_t> m_snapshot{{ dt.name }}Map;
int64_t m_snapshot{{ dt.name }}MapTime = -1; // time for which the snapshot is valid
{% endif %} {% endfor %}
LOGGER_T m_logger;
};
}
{% endautoescape %}
+102
View File
@@ -0,0 +1,102 @@
/**
* DDS Type Plugin to provide custom types to other applications like ddsview
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-07-08
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_topic_type_plugin.cpp
#include "{{ namespace_abbr }}_topic_type_plugin.h"
#include <{{ namespace_abbr }}_dtc_types.h>
#include <{{ namespace_abbr }}_ftc_types.h>
#include <{{ namespace_abbr }}_fts_types.h>
#include <DDS/FTC/generic_ftc_impl.h>
#include <DDS/FTS/generic_fts_impl.h>
#include <DDS/Routing/routing_plugin.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace {{ namespace }}
{
QMap<std::string, DDSGeneric::TopicDescription> TopicTypePlugin::getDataTopics() const
{
return
{{% for dt in dataTopics.member %}
{
dds::topic::topic_type_name<{{ dt.name }}>::value(),
{
DDS::Helper::getShortTopicName(dds::topic::topic_type_name<{{ dt.name }}>::value()),
dds::topic::topic_type_name<{{ dt.name }}>::value(),
rti::topic::dynamic_type<{{ dt.name }}>::get()
}
}{% if not forloop.last %}, {% endif %}{% endfor %}
};
}
QMap<std::string, DDSGeneric::TopicDescription> TopicTypePlugin::getFunctionTopics() const
{
return
{{% for ft in functionTopics.member %}
{
FTC::TopicNames<FTC::{{ ft.name }}>::requestTopicName(),
{
FTC::TopicNames<FTC::{{ ft.name }}>::requestTopicName(),
dds::topic::topic_type_name<FTC::{{ ft.name }}::RequestTopic>::value(),
rti::topic::dynamic_type<FTC::{{ ft.name }}::RequestTopic>::get()
}
},
{
FTC::TopicNames<FTC::{{ ft.name }}>::replyTopicName(),
{
FTC::TopicNames<FTC::{{ ft.name }}>::replyTopicName(),
dds::topic::topic_type_name<FTC::{{ ft.name }}::ReplyTopic>::value(),
rti::topic::dynamic_type<FTC::{{ ft.name }}::ReplyTopic>::get()
}
}{% if not forloop.last %}, {% endif %}{% endfor %}
};
}
inline NGL::INGLLogger &logger(const char *name)
{
return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(name);
}
std::vector<RoutingService::TopicDispatcherInterface *> TopicTypePlugin::topicsToRouteToWs(IDL::PartitionType partitionType) const
{
if (partitionType == IDL::PartitionType::Replay)
return
{{% for dt in dataTopics.topicsToRouteInReplay %}
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
{% for ft in functionTopics.topicsToRouteInReplay %}
new RoutingService::FunctionTopicReplyDispatcher<FTS::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %}
};
return
{{% for dt in dataTopics.topicsToRouteToClient %}
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
{% for ft in functionTopics.member %}
new RoutingService::FunctionTopicReplyDispatcher<FTS::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Reply")){% if not forloop.last %},{% endif %}{% endfor %}
};
}
std::vector<RoutingService::TopicDispatcherInterface *> TopicTypePlugin::topicsToRouteToSv(IDL::PartitionType partitionType) const
{
if (partitionType == IDL::PartitionType::Replay)
return
{{% for ft in functionTopics.topicsToRouteInReplay %}
new RoutingService::FunctionTopicRequestDispatcher<FTC::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %}
};
return
{{% for dt in dataTopics.topicsToRouteToServer %}
new RoutingService::DataTopicDispatcher<{{ dt.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ dt.name }}")),{% endfor %}
{% for ft in functionTopics.member %}
new RoutingService::FunctionTopicRequestDispatcher<FTC::{{ ft.name }}>(logger("RSC.{{ namespace_abbr_upper }}.{{ ft.name }}.Request")){% if not forloop.last %},{% endif %}{% endfor %}
};
}
} // namespace {{ namespace }}
{% endautoescape %}
+39
View File
@@ -0,0 +1,39 @@
/**
* DDS Type Plugin to provide custom types to other applications like ddsview
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, SLSE, 2016-07-08
*/
#pragma once
{% autoescape off %}
// filename: {{ namespace_abbr }}_topic_type_plugin.h
#include <{{ include_prefix }}/{{ namespace_abbr }}.hpp>
#include <Common/ProjectConfig/project_defines.h>
#include <QtCore/QObject>
// TODO: Problem with QT5_WRAP_CPP ...
#include <../../DDS/Generic/topic_type_plugin.h>
namespace {{ namespace }}
{
class TopicTypePlugin final : public QObject, public DDSGeneric::TopicTypePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "topic_type:3:{{ namespace_abbr }}")
Q_INTERFACES(DDSGeneric::TopicTypePlugin)
public:
TopicTypePlugin() = default;
QMap<std::string, DDSGeneric::TopicDescription> getDataTopics() const override final;
QMap<std::string, DDSGeneric::TopicDescription> getFunctionTopics() const override final;
std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToWs(IDL::PartitionType partitionType) const override final;
std::vector<RoutingService::TopicDispatcherInterface *> topicsToRouteToSv(IDL::PartitionType partitionType) const override final;
QString getServiceShortName() const override final { return QStringLiteral("{{ namespace_abbr_upper }}"); }
};
}
{% endautoescape %}
+43
View File
@@ -0,0 +1,43 @@
/**
* @RDE_HEADER@
* Convert DSS enumerations to translatable strings
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
// filename: {{ namespace_abbr }}_translation.cpp
#include "{{ namespace_abbr }}_translation.h"
#include <QCoreApplication>
namespace {{ namespace }}
{
{% for idl_enum in idl_enums %}
//
// {{ idl_enum.name }}
//
QString toString({{ idl_enum.name }} state)
{
switch(state.underlying())
{{% for idl_member in idl_enum.member %}
case {{ idl_enum.name }}_def::{{ idl_member.key }}: return QCoreApplication::translate("{{ idl_enum.name }}", "{{ idl_member.key }}");
{% endfor %}
default: break;
}
return QCoreApplication::translate("{{ idl_enum.name }}", "Unknown (%1)").arg(state.underlying());
}
{% endfor %}
{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %}
// {{ dt.name }}::{{ tr.name }}
QString tr{{ dt.name }}{{ tr.nameCc }}(const std::string &text)
{
return QCoreApplication::translate("{{ dt.name }}", text.c_str(), "{{ tr.name }}");
}
{% endfor %}{% endif %}{% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
+31
View File
@@ -0,0 +1,31 @@
/**
* @RDE_HEADER@
* Convert DSS enumerations to translatable strings
* $Id$
* $HeadURL$
* $Author$
*/
{% autoescape off %}
#pragma once
// filename: {{ namespace_abbr }}_translation.h
#include "{{ namespace_abbr }}_enums.hpp"
#include <{{ include_prefix }}/{{ namespace_abbr }}_export.h>
class QString;
namespace {{ namespace }}
{
// Translatable enumerations:
{% for idl_enum in idl_enums %}
{{ namespace_abbr_upper }}_COMMON_EXPORT QString toString({{ idl_enum.name }} state);{% endfor %}
// Translatable struct members:
{% for dt in dataTopics.member %}{% if dt.hasTrColumns %}{% for tr in dt.trColumns %}
{{ namespace_abbr_upper }}_COMMON_EXPORT QString tr{{ dt.nameCc }}{{ tr.nameCc }}(const std::string &text); // {{ dt.nameCc }}::{{ tr.name }} {% endfor %}{% endif %}{% endfor %}
} // namespace {{ namespace }}
{% endautoescape %}
+78
View File
@@ -0,0 +1,78 @@
#
# $Id$
# $HeadURL$
# $Author$
#
# Created by Mike Wille, SLSE, 2016-10-24
#
# LtsAppDescriptor (lib)
SET(LtsAppDescriptor_SRCS
lts_app_descriptor.cpp
lts_app_descriptor.h
)
NGL_ADD_LIBRARY(WS LtsAppDescriptor SHARED ${LtsAppDescriptor_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptor
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptor
PUBLIC
Qt5::Core
)
NGL_INSTALL_TARGET(LtsAppDescriptor)
# LtsAppDescriptorChecker
SET(LtsAppDescriptorChecker_SRCS
checker/main.cpp
)
NGL_ADD_EXECUTABLE(WS LtsAppDescriptorChecker ${LtsAppDescriptorChecker_SRCS})
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorChecker
PRIVATE
LtsAppDescriptor
PUBLIC
Qt5::Core
)
# LtsAppDescriptorEditor => Hat niemand benutzt, wird keiner benutzen, also wird der bis auf Weiteres auch nicht an die neue Syntax angepasst.
#SET(LtsAppDescriptorEditor_SRCS
# lts_app_descriptor_editor.cpp
# lts_app_descriptor_editor.h
#)
#NGL_WRAP_UI(LtsAppDescriptorEditor_SRCS
# lts_app_descriptor_editor.ui
#)
#NGL_ADD_LIBRARY(WS NO_TRANSLATION LtsAppDescriptorEditor SHARED ${LtsAppDescriptorEditor_SRCS})
#NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptorEditor
# PUBLIC
# ${CMAKE_CURRENT_SOURCE_DIR}/../
#)
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditor
# PUBLIC
# Qt5::Core
# Qt5::Widgets
# LtsAppDescriptor
#)
#NGL_INSTALL_TARGET(LtsAppDescriptorEditor) #not visible for end-user
## Executable:
#NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsAppDescriptorEditorApp main.cpp)
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditorApp LtsAppDescriptorEditor)
+138
View File
@@ -0,0 +1,138 @@
#include <LtsAppDescriptor/lts_app_descriptor.h>
#include <QCoreApplication>
#include <QFile>
#include <QFileInfoList>
#include <QDirIterator>
#include <QTextStream>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <cstdio>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
const QStringList args(a.arguments());
if (args.count() != 3)
{
printf("usage: %s <current source directory> <apps.json>\n", args.at(0).toStdString().c_str());
return -1;
}
const QString currentSrcDir(a.arguments().at(1));
const QString appsJsonFilePath(QStringLiteral("%1/%2").arg(currentSrcDir, a.arguments().at(2)));
QFile f(appsJsonFilePath);
if (!f.open(QFile::ReadOnly))
{
printf("LtsAppDescriptorChecker: cannot open apps.json at %s: %s\n", appsJsonFilePath.toStdString().c_str(), f.errorString().toStdString().c_str());
return -2;
}
printf("LtsAppDescriptorChecker: checking %s\n", appsJsonFilePath.toStdString().c_str());
QStringList lastErrors;
QStringList dummy1;
QString dummy2;
LtsAppDescriptor::AppDescriptionVector adv = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(f.readAll(), lastErrors, dummy1, dummy2);
if (!lastErrors.isEmpty())
{
printf("LtsAppDescriptorChecker: apps.json at %s invalid:\n%s\n", appsJsonFilePath.toStdString().c_str(), lastErrors.join(QStringLiteral("\r\n\r\n")).toStdString().c_str());
return 1;
}
// looking for gui plugins
QFileInfoList guiPluginFiles;
QDirIterator it(QStringLiteral("%1/../../Plugins/").arg(currentSrcDir), QDirIterator::Subdirectories);
while (it.hasNext())
{
QString filename(it.next());
QFileInfo file(filename);
if (file.isDir())
continue;
if (file.fileName().endsWith(QStringLiteral("_gui_plugin.h"), Qt::CaseInsensitive))
guiPluginFiles.append(file);
}
QStringList filenames;
filenames.reserve(guiPluginFiles.size());
for (const QFileInfo &fi : qAsConst(guiPluginFiles))
{
filenames += fi.absoluteFilePath();
}
QStringList guiPluginIIDs;
for (const QFileInfo &fi : qAsConst(guiPluginFiles))
{
QFile f2(fi.absoluteFilePath());
if (!f2.open(QFile::ReadOnly))
continue;
QTextStream ts(&f2);
while (!ts.atEnd())
{
QString line(ts.readLine().trimmed().simplified());
static QString iidIdentifier(QStringLiteral("Q_PLUGIN_METADATA"));
if (line.contains(iidIdentifier))
{
QRegularExpression re(QStringLiteral("(?<=gui:3:).*(?=\"\\))"));
QRegularExpressionMatch match = re.match(line);
if (match.hasMatch())
{
QString matched = match.captured(0);
//printf("matched = '%s'\n", matched.toStdString().c_str());
guiPluginIIDs += matched;
}
break;
}
}
}
// match gui plugin iids with adv listed plugins
bool ok = true;
for (const LtsAppDescriptor::AppDescription &appdesc : qAsConst(adv))
{
for (const LtsAppDescriptor::PluginInfo &pi : qAsConst(appdesc.pluginInfos))
{
if (!guiPluginIIDs.contains(pi.iid))
{
printf("LtsAppDescriptorChecker: apps.json at '%s' uses unknown IID '%s'\n", appsJsonFilePath.toStdString().c_str(), pi.iid.toStdString().c_str());
ok = false;
}
}
}
//printf("gui plugins found in '%s': %s\n", it.fileName().toStdString().c_str(), filenames.join(QStringLiteral(", ")).toStdString().c_str());
return !ok;
}
//
+222
View File
@@ -0,0 +1,222 @@
/**
* @RDE_HEADER@
* App Descriptor - Class to modify App Descriptor JSON Documents
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2016-10-24
*/
#include "lts_app_descriptor.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonParseError>
#include <QJsonValue>
#include <QCoreApplication>
//#include <QDebug>
namespace LtsAppDescriptor
{
static void addOptionToPlugin(PluginInfo &pluginInfo, const PluginInfo::Flag &flag)
{
pluginInfo.flags |= flag;
}
static void addOptionToPlugin(QVector<PluginInfo> &pluginInfos, const PluginInfo::Flag &flag, const QString &pluginName)
{
for (PluginInfo &pi : pluginInfos)
{
if (pi.iid == pluginName)
{
addOptionToPlugin(pi, flag);
return; // done
}
}
PluginInfo pi;
pi.iid = pluginName;
pi.type = PluginInfo::PluginType::GUI; // must be a gui plugin if it has flags, huh
addOptionToPlugin(pi, flag);
pluginInfos.append(pi);
}
static bool addOptionsToPlugin(AppDescription &appDesc, const QString &optionIdentifier, const QStringList &ignoredPlugins,
const QJsonObject &optionsObject, const PluginInfo::Flag flag, QStringList &lastErrors,
bool atLeastOnePluginNeeded)
{
bool hasPlugins = false;
auto optionsObjectIt = optionsObject.constFind(optionIdentifier);
if (optionsObjectIt != optionsObject.constEnd())
{
const QJsonArray plugins((*optionsObjectIt).toObject()[QStringLiteral("Plugins")].toArray());
for (const auto &pluginElem : plugins)
{
const QString pluginName = pluginElem.toString();
if (!ignoredPlugins.contains(pluginName))
{
addOptionToPlugin(appDesc.pluginInfos, flag, pluginName);
hasPlugins = true;
}
}
}
if (atLeastOnePluginNeeded && !hasPlugins)
{
lastErrors.append(QCoreApplication::tr("No plugins specified for '%1' option for app '%2'").arg(optionIdentifier, appDesc.appName));
return false;
}
return true;
}
App LtsAppDescriptor::appFromInteger(int i)
{
switch (i)
{
case int(App::Login):
case int(App::Situation):
case int(App::ResourceManager):
case int(App::PlanEditor):
case int(App::BuildingViewer):
case int(App::OC):
case int(App::OrgChart):
case int(App::Scripts):
// merged into Scripts (aka. ScriptEditor) ScriptPlayer = 7,
case int(App::Snapshot):
case int(App::SystemMonitor):
case int(App::Timeline):
case int(App::SymbolEditor):
case int(App::ScriptEditorEssentialsView):
case int(App::BaseDownloader):
case int(App::OnlineHelp):
case int(App::ThpSituationReplay):
case int(App::SituationTablet):
case int(App::SilhouetteEditor):
case int(App::Shootback):
case int(App::SAAs):
return static_cast<App>(i);
}
Q_ASSERT_X(false, "LtsAppDescriptor::appFromInteger", qPrintable(QStringLiteral("Unknown app id: %1").arg(i)));
return App::Login;
}
AppDescriptionVector LtsAppDescriptor::getAppDescriptionVector(const QByteArray &jsonDocument,
QStringList &lastErrors,
const QStringList &ignoredPlugins,
QString &loginPluginIid)
{
// TODO: find duplicates
AppDescriptionVector appDescriptions;
QJsonParseError parseError;
QJsonDocument appsDoc(QJsonDocument::fromJson(jsonDocument, &parseError)); // TODO: limit?
if (parseError.error != QJsonParseError::NoError)
{
// QJson is retarded, we must calculate line / column by ourselves
int row = 0;
int column = 0;
QChar x = QLatin1Char('\n');
for (int i = 0; i < jsonDocument.count(); i++) // file must be smaller than a 32 bit signed can hold;)
{
if (i == parseError.offset)
break; // found!
if (QChar::fromLatin1(jsonDocument[i]) == x) // TODO: FIXME: windows
{
row++;
column = 0;
}
else
{
column++;
}
}
lastErrors.append(QCoreApplication::tr("JSON: %1<br>Row: %2, Column: %3").arg(parseError.errorString()).arg(row+1).arg(column+1)); // skip line and column 0
return appDescriptions;
}
const QJsonObject root(appsDoc.object());
loginPluginIid = root[QStringLiteral("LoginPlugin")].toString();
const QJsonArray appArray(root[QStringLiteral("Apps")].toArray());
if (appArray.isEmpty())
{
lastErrors.append(QCoreApplication::tr("Invalid json: Could not find 'Apps' array"));
return appDescriptions;
}
for (const auto &appElem : appArray) // iterate through apps
{
AppDescription appDesc;
const QJsonObject appObject = appElem.toObject();
//qWarning() << "app id" << appObject.value(QStringLiteral("Id"));
appDesc.id = appFromInteger(appObject.value(QStringLiteral("Id")).toInt(-1));
if (appDesc.id == App::Login)
{
lastErrors.append(QCoreApplication::tr("App '%1': no App Id or invalid App Id given").arg(appDesc.appName));
return appDescriptions;
}
appDesc.appName = appObject.value(QStringLiteral("AppName")).toString();
const QJsonValue valueActive = appObject.value(QStringLiteral("Active"));
if (!valueActive.isUndefined())
{
if (valueActive.type() != QJsonValue::Type::Bool)
{
lastErrors.append(QCoreApplication::tr("App '%1': value App.Active is not a bool (%2)").arg(appDesc.appName).arg((int)valueActive.type()));
return appDescriptions;
}
}
appDesc.active = valueActive.toBool(false);
const QJsonValue valueVisible = appObject.value(QStringLiteral("Visible"));
if (!valueVisible.isUndefined() && valueVisible.type() != QJsonValue::Type::Bool)
{
lastErrors.append(QCoreApplication::tr("App '%1': value App.Visible is not a bool (%2)").arg(appDesc.appName).arg((int)valueVisible.type()));
return appDescriptions;
}
appDesc.visible = valueVisible.toBool(true);
const QJsonArray array(appObject.value(QStringLiteral("ACLs")).toArray());
for (const QJsonValue &value : array)
appDesc.requiredAcls.append(value.toInt());
// add options plugins
const QJsonObject optionsObject(appObject.value(QStringLiteral("Options")).toObject());
addOptionsToPlugin(appDesc, QStringLiteral("Payloads"), ignoredPlugins, optionsObject, PluginInfo::Flag::PAYLOAD, lastErrors, false);
addOptionsToPlugin(appDesc, QStringLiteral("Actions"), ignoredPlugins, optionsObject, PluginInfo::Flag::ACTIONS, lastErrors, true /* at least one plugin needed */);
addOptionsToPlugin(appDesc, QStringLiteral("Main"), ignoredPlugins, optionsObject, PluginInfo::Flag::MAIN, lastErrors, false);
appDescriptions.append(appDesc);
//qWarning() << "added app id" << appObject.value(QStringLiteral("Id"));
}
//debug_print_descriptions(appDescriptions);
return appDescriptions;
}
} // namespace LtsAppDescriptor
+103
View File
@@ -0,0 +1,103 @@
/**
* @RDE_HEADER@
* App Descriptor - Class to modify App Descriptor JSON Documents
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2016-10-24
*/
#pragma once
#include <QVector>
#include <QStringList>
#ifndef LTSAPPDESCRIPTOR_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define LTSAPPDESCRIPTOR_EXPORT
# else
# if defined(MAKE_LTSAPPDESCRIPTOR_LIB)
# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_EXPORT
# else
# define LTSAPPDESCRIPTOR_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
namespace LtsAppDescriptor
{
enum class App
{
Login = -1,
Situation = 0,
ResourceManager = 1,
PlanEditor = 2,
BuildingViewer = 3,
OC = 4,
OrgChart = 5,
Scripts = 6,
// merged into Scripts (aka. ScriptEditor) ScriptPlayer = 7,
Snapshot = 8,
SystemMonitor = 9,
Timeline = 10,
SymbolEditor = 11,
ScriptEditorEssentialsView = 12,
BaseDownloader = 13,
OnlineHelp = 14,
ThpSituationReplay = 15,
SituationTablet = 17,
SilhouetteEditor = 18, // for L400P2
Shootback = 19, // for L400P2
SAAs = 20, //Schnittstelle Adapter Adler
};
struct PluginInfo
{
enum class PluginType
{
UNKNOWN,
DATA,
GUI,
};
enum class Flag
{
UNKNOWN = 1 << 0,
ACTIONS = 1 << 1,
MAIN = 1 << 2,
PAYLOAD = 1 << 3,
OPTION = 1 << 4,
};
PluginType type = PluginType::UNKNOWN;
QString iid;
QFlags<Flag> flags;
QStringList options;
};
struct AppDescription
{
App id = App::Login;
bool active = false;
bool visible = true;
QString appName;
QVector<int> requiredAcls;
QVector<PluginInfo> pluginInfos;
bool isValid() const { return !appName.isEmpty(); }
};
typedef QVector<AppDescription> AppDescriptionVector;
namespace LtsAppDescriptor // TODO: make qobject class singleton - for tr
{
LTSAPPDESCRIPTOR_EXPORT App appFromInteger(int i);
LTSAPPDESCRIPTOR_EXPORT AppDescriptionVector getAppDescriptionVector(const QByteArray &jsonDocument, QStringList &lastErrors,
const QStringList &ignoredPlugins, QString &loginPluginIid);
}
} // namespace LtsAppDescriptor
+554
View File
@@ -0,0 +1,554 @@
#include "lts_app_descriptor_editor.h"
#include "ui_lts_app_descriptor_editor.h"
#include <QFile>
#include <QJsonDocument>
#include <QStringListModel>
#include <QFileDialog>
#include <QMessageBox>
#include <QTimer>
#include <QDebug>
LtsAppDescriptorEditor::LtsAppDescriptorEditor(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui_LtsAppDescriptionEditor)
{
m_ui->setupUi(this);
m_vector = LtsAppDescriptor::AppDescriptionVector();
m_appIndex = QModelIndex();
m_pluginIndex = QModelIndex();
m_fileName = QString();
m_changed = false;
connect(m_ui->lvApps, SIGNAL(clicked(QModelIndex)), this, SLOT(onAppsClicked(QModelIndex)));
connect(m_ui->lvPlugins, SIGNAL(clicked(QModelIndex)), this, SLOT(onPluginsClicked(QModelIndex)));
connect(m_ui->lePluginType, SIGNAL(editingFinished()), this, SLOT(pluginTypeChanged()));
connect(m_ui->pbAddApp, SIGNAL(pressed()), this, SLOT(onAppAddClicked()));
connect(m_ui->pbAddPlugin, SIGNAL(pressed()), this, SLOT(onPluginAddClicked()));
connect(m_ui->pbDeleteApp, SIGNAL(pressed()), this, SLOT(onAppDeleteClicked()));
connect(m_ui->pbDeletePlugin, SIGNAL(pressed()), this, SLOT(onPluginDeletedClicked()));
connect(m_ui->pbUpdate, SIGNAL(pressed()), this, SLOT(onUpdateClicked()));
connect(m_ui->pbNewPlugin, SIGNAL(pressed()), this, SLOT(onNewpluginClicked()));
connect(m_ui->pbNewApp, SIGNAL(pressed()), this, SLOT(onNewAppClicked()));
connect(m_ui->lePluginName, SIGNAL(textChanged(QString)), this, SLOT(enableNewPlugin()));
connect(m_ui->lePluginType, SIGNAL(textChanged(QString)), this, SLOT(enableNewPlugin()));
m_ui->gbPlugins->setEnabled(false);
m_ui->pbDeletePlugin->setEnabled(false);
m_ui->pbDeleteApp->setEnabled(false);
m_ui->pbAddPlugin->setEnabled(false);
m_ui->pbUpdate->setEnabled(false);
m_ui->pbNewPlugin->setEnabled(false);
m_ui->pbNewApp->setEnabled(false);
QTimer::singleShot(0, this, &LtsAppDescriptorEditor::getAppDescriptionVector);
}
LtsAppDescriptorEditor::~LtsAppDescriptorEditor()
{
checkSave();
delete m_ui;
}
void LtsAppDescriptorEditor::getAppDescriptionVector()
{
m_fileName = QFileDialog::getOpenFileName(this, tr("Open JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"),
tr("JSON File (*.json)"));
if (m_fileName.isEmpty())
return;
QFile file;
file.setFileName(m_fileName);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QString val(QString::fromUtf8(file.readAll()));
file.close();
QStringList errors;
m_vector = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(val.toUtf8(), errors);
updateAppModel();
}
void LtsAppDescriptorEditor::onAppsClicked(const QModelIndex &index)
{
m_ui->gbPlugins->setEnabled(true);
m_ui->pbDeleteApp->setEnabled(true);
m_ui->pbAddPlugin->setEnabled(true);
m_ui->pbNewApp->setEnabled(true);
m_appIndex = index;
QStringList pluginList;
Q_FOREACH(const LtsAppDescriptor::PluginInfo &reqPlugin, m_vector.at(m_appIndex.row()).requiredPlugins)
{
LtsAppDescriptor::PluginInfo::PluginType pluginType = reqPlugin.type;
QString type;
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
type = QLatin1String("data");
else if(pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
type = QLatin1String("gui");
else
type = QLatin1String("unknown");
if(type == QLatin1String("data"))
pluginList << QString(type + QLatin1String(": ") +reqPlugin.iid);
else
{
QString strFlags;
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = reqPlugin.flags;
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
if(actionsFlag)
strFlags.append(QLatin1String("actions "));
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
if(mainFlag)
strFlags.append(QLatin1String("main "));
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
if(payloadFlag)
strFlags.append(QLatin1String("payload "));
bool optionalFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::OPTION);
if(optionalFlag)
strFlags.append(QLatin1String("optional "));
pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid + QLatin1String(" [ ") + strFlags + QLatin1String("]"));
}
}
updatePluginModel(pluginList);
m_ui->chbAppActive->setChecked(m_vector.at(m_appIndex.row()).active);
m_ui->leAppName->setText(m_vector.at(m_appIndex.row()).appName);
}
void LtsAppDescriptorEditor::onPluginsClicked(const QModelIndex &index)
{
m_pluginIndex = index;
m_ui->pbDeletePlugin->setEnabled(true);
m_ui->pbUpdate->setEnabled(true);
m_ui->pbNewPlugin->setEnabled(true);
const LtsAppDescriptor::PluginInfo plugin = m_vector.at(m_appIndex.row()).requiredPlugins.at(m_pluginIndex.row());
QString pluginName = plugin.iid;
m_ui->lePluginName->setText(pluginName);
LtsAppDescriptor::PluginInfo::PluginType pluginType = plugin.type;
QString type;
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
type = QLatin1String("data");
else if(pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
type = QLatin1String("gui");
else
type = QLatin1String("unknown");
m_ui->lePluginType->setText(type);
if(type == QLatin1String("gui"))
{
m_ui->chbActions->setEnabled(true);
m_ui->chbMain->setEnabled(true);
m_ui->chbPayload->setEnabled(true);
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = plugin.flags;
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
m_ui->chbActions->setChecked(actionsFlag);
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
m_ui->chbMain->setChecked(mainFlag);
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
m_ui->chbPayload->setChecked(payloadFlag);
}
else
{
m_ui->chbActions->setChecked(false);
m_ui->chbActions->setEnabled(false);
m_ui->chbMain->setChecked(false);
m_ui->chbMain->setEnabled(false);
m_ui->chbPayload->setChecked(false);
m_ui->chbPayload->setEnabled(false);
}
}
void LtsAppDescriptorEditor::pluginTypeChanged()
{
if(m_ui->lePluginType->text().isEmpty())
return;
if(m_ui->lePluginType->text() == QLatin1String("gui"))
{
m_ui->chbActions->setEnabled(true);
m_ui->chbMain->setEnabled(true);
m_ui->chbPayload->setEnabled(true);
}
else if(m_ui->lePluginType->text() == QLatin1String("data"))
{
m_ui->chbActions->setChecked(false);
m_ui->chbActions->setEnabled(false);
m_ui->chbMain->setChecked(false);
m_ui->chbMain->setEnabled(false);
m_ui->chbPayload->setChecked(false);
m_ui->chbPayload->setEnabled(false);
}
else
{
QMessageBox msgBox(QMessageBox::Information,
tr("Error"),
tr("Plugin Type must either be \"gui\" or \"data\"."));
msgBox.exec();
m_ui->lePluginType->setText(QString());
}
}
void LtsAppDescriptorEditor::onAppAddClicked()
{
m_changed = true;
if(m_ui->leAppName->text().isEmpty())
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("The App needs to have a name."));
msgBox.exec();
return;
}
QStringList appList;
Q_FOREACH(const LtsAppDescriptor::AppDescription &appDesc, m_vector)
{
appList << appDesc.appName;
}
if(appList.contains(m_ui->leAppName->text(),Qt::CaseInsensitive))
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("There already exists an app named \"%1\".").arg(m_ui->leAppName->text()));
msgBox.exec();
return;
}
LtsAppDescriptor::AppDescription newApp;
newApp.appName = m_ui->leAppName->text();
newApp.active = m_ui->chbAppActive->isChecked();
m_vector.append(newApp);
updateAppModel();
}
void LtsAppDescriptorEditor::onPluginAddClicked()
{
m_changed = true;
if(m_ui->lePluginType->text().isEmpty())
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("The Plugin needs to contain a type."));
msgBox.exec();
return;
}
if(m_ui->lePluginName->text().isEmpty())
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("The Plugin needs to have a name."));
msgBox.exec();
return;
}
QStringList pluginList;
Q_FOREACH(const LtsAppDescriptor::PluginInfo &pluginDesc, m_vector.at(m_appIndex.row()).requiredPlugins)
{
pluginList << pluginDesc.iid;
}
if(pluginList.contains(m_ui->lePluginName->text(),Qt::CaseInsensitive))
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("There already exists an plugin named \"%1\".").arg(m_ui->lePluginName->text()));
msgBox.exec();
return;
}
LtsAppDescriptor::PluginInfo newPlugin;
newPlugin.iid = m_ui->lePluginName->text();
if(m_ui->lePluginType->text() == QLatin1String("gui"))
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::GUI;
else if(m_ui->lePluginType->text() == QLatin1String("data"))
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA;
else
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN; //Should never happen.
if(m_ui->chbActions->isChecked())
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
if(m_ui->chbMain->isChecked())
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN;
if(m_ui->chbPayload->isChecked())
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
m_vector[m_appIndex.row()].requiredPlugins.append(newPlugin);
onAppsClicked(m_appIndex);
onNewpluginClicked();
}
void LtsAppDescriptorEditor::onAppDeleteClicked()
{
m_changed = true;
if(!m_appIndex.isValid()) return;
QMessageBox msgBox(tr("Warning"),
tr("The App \n\"%1\" \nwill be removed. Continue?").arg(m_appIndex.data().toString()),
QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape,
QMessageBox::NoButton);
if (msgBox.exec() == QMessageBox::No)
return;
m_vector.remove(m_appIndex.row());
updateAppModel();
}
void LtsAppDescriptorEditor::onPluginDeletedClicked()
{
m_changed = true;
if(!m_pluginIndex.isValid()) return;
QMessageBox msgBox(tr("Warning"),
tr("The Plugin \n\"%1\" \nwill be removed. Continue?").arg(m_pluginIndex.data().toString()),
QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape,
QMessageBox::NoButton);
if (msgBox.exec() == QMessageBox::No)
return;
m_vector[m_appIndex.row()].requiredPlugins.remove(m_pluginIndex.row());
m_pluginIndex = QModelIndex();
m_ui->lePluginName->setText(QString());
m_ui->lePluginType->setText(QString());
m_ui->chbActions->setChecked(false);
m_ui->chbMain->setChecked(false);
m_ui->chbPayload->setChecked(false);
m_ui->pbDeletePlugin->setEnabled(false);
m_ui->pbUpdate->setEnabled(false);
m_ui->pbNewPlugin->setEnabled(false);
onAppsClicked(m_appIndex);
}
void LtsAppDescriptorEditor::onUpdateClicked()
{
m_changed = true;
if(m_ui->lePluginType->text().isEmpty())
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("The Plugin needs to contain a type."));
msgBox.exec();
return;
}
if(m_ui->lePluginName->text().isEmpty())
{
QMessageBox msgBox(QMessageBox::Critical,
tr("Error"),
tr("The Plugin needs to have a name."));
msgBox.exec();
return;
}
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].iid = m_ui->lePluginName->text();
if(m_ui->lePluginType->text() == QLatin1String("gui"))
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::GUI;
else if(m_ui->lePluginType->text() == QLatin1String("data"))
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::DATA;
else
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN; //Should never happen.
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags = QFlags<LtsAppDescriptor::PluginInfo::Flag>();
if(m_ui->chbActions->isChecked())
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
if(m_ui->chbMain->isChecked())
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN;
if(m_ui->chbPayload->isChecked())
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
onAppsClicked(m_appIndex);
onNewpluginClicked();
}
void LtsAppDescriptorEditor::onNewpluginClicked()
{
m_ui->lePluginName->setText(QString());
m_ui->lePluginType->setText(QString());
m_ui->chbActions->setEnabled(true);
m_ui->chbMain->setEnabled(true);
m_ui->chbPayload->setEnabled(true);
m_ui->chbActions->setChecked(false);
m_ui->chbMain->setChecked(false);
m_ui->chbPayload->setChecked(false);
m_ui->pbUpdate->setEnabled(false);
m_ui->pbNewPlugin->setEnabled(false);
m_ui->pbDeletePlugin->setEnabled(false);
if(m_pluginIndex.isValid())
m_ui->lvPlugins->selectionModel()->select(m_pluginIndex,QItemSelectionModel::Deselect);
m_pluginIndex = QModelIndex();
}
void LtsAppDescriptorEditor::onNewAppClicked()
{
updatePluginModel(QStringList());
m_ui->gbPlugins->setEnabled(false);
m_ui->pbDeletePlugin->setEnabled(false);
m_ui->pbDeleteApp->setEnabled(false);
m_ui->pbAddPlugin->setEnabled(false);
m_ui->pbUpdate->setEnabled(false);
m_ui->pbNewPlugin->setEnabled(false);
m_ui->leAppName->setText(QString());
m_ui->chbAppActive->setChecked(false);
m_ui->lePluginName->setText(QString());
m_ui->lePluginType->setText(QString());
m_ui->chbActions->setChecked(false);
m_ui->chbMain->setChecked(false);
m_ui->chbPayload->setChecked(false);
m_ui->pbNewApp->setEnabled(false);
if(m_appIndex.isValid())
m_ui->lvApps->selectionModel()->select(m_appIndex,QItemSelectionModel::Deselect);
m_appIndex = QModelIndex();
}
void LtsAppDescriptorEditor::updateAppModel()
{
onNewAppClicked();
QStringList appList;
Q_FOREACH(const LtsAppDescriptor::AppDescription &appDesc, m_vector)
{
appList << appDesc.appName;
}
if (!m_ui->lvApps->model())
m_ui->lvApps->setModel(new QStringListModel(this));
QStringListModel *m = qobject_cast<QStringListModel*>(m_ui->lvApps->model());
if (!m)
return; // should never happen
m->setStringList(appList);
}
void LtsAppDescriptorEditor::updatePluginModel(const QStringList &list)
{
if (!m_ui->lvPlugins->model())
m_ui->lvPlugins->setModel(new QStringListModel(this));
QStringListModel *m = qobject_cast<QStringListModel*>(m_ui->lvPlugins->model());
if (!m)
return; // should never happen
m->setStringList(list);
}
void LtsAppDescriptorEditor::checkSave()
{
if(m_changed)
{
QMessageBox msgBox(tr("Warning"),
tr("The Data has been modified.\nDo you want to save your changes?"),
QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape,
QMessageBox::NoButton);
if (msgBox.exec() == QMessageBox::Yes)
onSaveAsClicked();
}
}
void LtsAppDescriptorEditor::onSaveAsClicked()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("Save JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"),
tr("JSON File (*.json)"));
if(fileName.isEmpty())
return;
QByteArray jsonByteArray;
jsonByteArray = LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector);
QFile f(fileName);
f.open(QIODevice::WriteOnly);
f.write(jsonByteArray);
f.close();
m_changed = false;
QMessageBox msgBox(QMessageBox::Information,
tr("Success"),
tr("Your file has been saved."));
msgBox.exec();
}
void LtsAppDescriptorEditor::onSaveClicked()
{
const QByteArray jsonByteArray(LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector));
QFile f(m_fileName);
f.open(QIODevice::WriteOnly);
f.write(jsonByteArray);
f.close();
m_changed = false;
QMessageBox msgBox(QMessageBox::Information,
tr("Success"),
tr("Your file has been saved."));
msgBox.exec();
}
void LtsAppDescriptorEditor::onOpenClicked()
{
checkSave();
getAppDescriptionVector();
m_changed = false;
}
void LtsAppDescriptorEditor::onNewClicked()
{
checkSave();
m_ui->chbActions->setChecked(false);
m_ui->chbMain->setChecked(false);
m_ui->chbPayload->setChecked(false);
m_ui->chbAppActive->setChecked(false);
m_ui->leAppName->setText(QString());
m_ui->lePluginName->setText(QString());
m_ui->lePluginType->setText(QString());
m_ui->gbPlugins->setEnabled(false);
m_vector = LtsAppDescriptor::AppDescriptionVector();
updateAppModel();
updatePluginModel(QStringList());
}
void LtsAppDescriptorEditor::enableNewPlugin()
{
m_ui->pbNewPlugin->setEnabled(true);
}
void LtsAppDescriptorEditor::onQuitClicked()
{
QApplication::quit();
}
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <QtWidgets/QWidget>
#include <QModelIndex>
#include "lts_app_descriptor.h"
#ifndef LTSAPPDESCRIPTOREDITOR_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define LTSAPPDESCRIPTOREDITOR_EXPORT
# else
# if defined(MAKE_LTSAPPDESCRIPTOREDITOR_LIB)
# define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_EXPORT
# else
# define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
class Ui_LtsAppDescriptionEditor;
class LTSAPPDESCRIPTOREDITOR_EXPORT LtsAppDescriptorEditor : public QWidget
{
Q_OBJECT
public:
explicit LtsAppDescriptorEditor(QWidget *parent = 0);
~LtsAppDescriptorEditor();
private:
Ui_LtsAppDescriptionEditor *m_ui;
LtsAppDescriptor::AppDescriptionVector m_vector;
QModelIndex m_appIndex;
QModelIndex m_pluginIndex;
QString m_fileName;
bool m_changed;
void getAppDescriptionVector();
void setupMainWindow();
void updateAppModel();
void updatePluginModel(const QStringList &list);
void checkSave();
private slots:
void onAppsClicked(const QModelIndex &index);
void onPluginsClicked(const QModelIndex &index);
void pluginTypeChanged();
void onAppAddClicked();
void onPluginAddClicked();
void onAppDeleteClicked();
void onPluginDeletedClicked();
void onUpdateClicked();
void onNewpluginClicked();
void onNewAppClicked();
void enableNewPlugin();
public slots:
void onSaveClicked();
void onSaveAsClicked();
void onOpenClicked();
void onNewClicked();
void onQuitClicked();
};
+434
View File
@@ -0,0 +1,434 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LtsAppDescriptionEditor</class>
<widget class="QWidget" name="LtsAppDescriptionEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>865</width>
<height>337</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string/>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="3" colspan="2">
<widget class="QLabel" name="Plugins">
<property name="text">
<string>Required Plugins:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="AppLabel">
<property name="text">
<string>App:</string>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="4" colspan="3">
<widget class="QListView" name="lvApps">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item row="5" column="7">
<widget class="QPushButton" name="pbNewPlugin">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item row="2" column="3" colspan="2">
<widget class="QLabel" name="AppName">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Name:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="5" column="6">
<widget class="QPushButton" name="pbDeletePlugin">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item row="1" column="7" rowspan="4" colspan="3">
<widget class="QGroupBox" name="gbPlugins">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>New Plugin Requirements</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="PluginType">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lePluginType">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>210</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>gui/data</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="PluginName">
<property name="text">
<string>IID:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lePluginName">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>210</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Name of the Plugin</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="PluginFlags">
<property name="text">
<string>Flags:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="chbActions">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>Actions</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="chbMain">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>Main</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="chbPayload">
<property name="minimumSize">
<size>
<width>0</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>Payload</string>
</property>
</widget>
</item>
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLabel" name="AppActive">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Active:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QPushButton" name="pbDeleteApp">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item row="4" column="3" colspan="4">
<widget class="QListView" name="lvPlugins">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
<item row="5" column="9">
<widget class="QPushButton" name="pbUpdate">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
<item row="2" column="5">
<widget class="QLineEdit" name="leAppName">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>30</height>
</size>
</property>
<property name="toolTip">
<string>Name of the App</string>
</property>
</widget>
</item>
<item row="5" column="8">
<widget class="QPushButton" name="pbAddPlugin">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="pbNewApp">
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QCheckBox" name="chbAppActive">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="pbAddApp">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>List of Apps</string>
</property>
</widget>
</item>
<item row="1" column="6" rowspan="3">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<tabstops>
<tabstop>lvApps</tabstop>
<tabstop>pbNewApp</tabstop>
<tabstop>pbAddApp</tabstop>
<tabstop>pbDeleteApp</tabstop>
<tabstop>chbAppActive</tabstop>
<tabstop>leAppName</tabstop>
<tabstop>lvPlugins</tabstop>
<tabstop>pbDeletePlugin</tabstop>
<tabstop>pbNewPlugin</tabstop>
<tabstop>lePluginType</tabstop>
<tabstop>lePluginName</tabstop>
<tabstop>chbPayload</tabstop>
<tabstop>chbMain</tabstop>
<tabstop>chbActions</tabstop>
<tabstop>pbAddPlugin</tabstop>
<tabstop>pbUpdate</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
+63
View File
@@ -0,0 +1,63 @@
/**
* @RDE_HEADER@
* App Descriptor Editor
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2016-10-24
*/
#include "lts_app_descriptor_editor.h"
#include <QAction>
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow *mainWindow = new QMainWindow();
QMenu *fileMenu = mainWindow->menuBar()->addMenu(QObject::tr("&File"));
QAction *openAct = new QAction(QObject::tr("&Open"), mainWindow);
openAct->setShortcuts(QKeySequence::Open);
openAct->setStatusTip(QObject::tr("Open an existing file"));
fileMenu->addAction(openAct);
QAction *newAct = new QAction(QObject::tr("&New"), mainWindow);
newAct->setShortcuts(QKeySequence::New);
newAct->setStatusTip(QObject::tr("Create a new App"));
fileMenu->addAction(newAct);
QAction *saveAct = new QAction(QObject::tr("&Save"), mainWindow);
saveAct->setShortcuts(QKeySequence::Save);
saveAct->setStatusTip(QObject::tr("Save modified file"));
fileMenu->addAction(saveAct);
QAction *saveAsAct = new QAction(QObject::tr("&SaveAs"), mainWindow);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(QObject::tr("Save modified file as"));
fileMenu->addAction(saveAsAct);
QAction *quitAct = new QAction(QObject::tr("&Quit"), mainWindow);
quitAct->setShortcuts(QKeySequence::Quit);
quitAct->setStatusTip(QObject::tr("Quit the Application"));
fileMenu->addAction(quitAct);
LtsAppDescriptorEditor *editor = new LtsAppDescriptorEditor(mainWindow);
mainWindow->setCentralWidget(editor);
QObject::connect(openAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onOpenClicked);
QObject::connect(newAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onNewClicked);
QObject::connect(saveAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveClicked);
QObject::connect(saveAsAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveAsClicked);
QObject::connect(quitAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onQuitClicked);
mainWindow->show();
int ret = a.exec();
delete mainWindow; // also deletes editor due to parent/child
return ret;
}
+42
View File
@@ -0,0 +1,42 @@
/**
* @RDE_HEADER@
* CustomView for App
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2016-11-11
*/
#include "app.h"
#include "ui_app.h"
#include "AppChooser/app_chooser_model.h"
namespace LtsAppManager
{
CustomViewApp::CustomViewApp(QWidget *parent)
: AbstractDisplayWidget(parent)
, m_ui(new Ui::CustomViewApp)
{
m_ui->setupUi(this);
m_ui->image->setMenuEnabled(false);
}
CustomViewApp::~CustomViewApp()
{
delete m_ui;
}
bool CustomViewApp::init(const QModelIndex &idx)
{
if (!idx.isValid())
return false;
m_ui->lbAppName->setText(rowData(idx, AppChooserModel::Name).toString());
//m_ui->lbDescription->setText(rowData(idx, AppChooserModel::Description).toString());
return true;
}
} // namespace LtsAppManager
+37
View File
@@ -0,0 +1,37 @@
/**
* @RDE_HEADER@
* CustomView for App
* $Id$
* $HeadURL$
* $Author$
*
* Created by Mike Wille, SLSE, 2016-11-11
*/
#pragma once
#include <Viewer/LtsDataView/abstract_display_widget.h>
namespace Ui
{
class CustomViewApp;
}
namespace LtsAppManager
{
class CustomViewApp final : public LtsDataView::AbstractDisplayWidget
{
Q_OBJECT
public:
explicit CustomViewApp(QWidget *parent = nullptr);
~CustomViewApp();
bool init(const QModelIndex &idx) override;
private:
Ui::CustomViewApp* m_ui;
};
} // namespace LtsAppManager
+108
View File
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CustomViewApp</class>
<widget class="QWidget" name="CustomViewApp">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>150</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="LtsWidgets::LtsImageEditor" name="image">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>128</width>
<height>128</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="lbAppName">
<property name="text">
<string notr="true">&lt;App Name&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lbDescription">
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&amp;lt;Description with wordwrap&amp;gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsWidgets::LtsImageEditor</class>
<extends>QWidget</extends>
<header>LtsWidgets/lts_image_editor.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+93
View File
@@ -0,0 +1,93 @@
// TODO: hdr
#include "AppChooser/app_chooser.h"
#include "ui_app_chooser.h"
#include "AppChooser/app_chooser_model.h"
#include "lts_app_manager.h"
#include "AppChooser/CustomViews/app.h"
#include <QCloseEvent>
#include <QDebug>
namespace LtsAppManager
{
AppChooser::AppChooser()
: QWidget(nullptr)
, m_ui(new Ui::AppChooser)
, m_model()
, m_madeSelection(false)
{
m_ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose); // delete when closed
connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalDoubleClicked,
this, &AppChooser::slotAppSelected);
connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalEnterPressed,
this, &AppChooser::slotAppSelected);
connect(m_ui->widget, &LtsDataView::LtsDataView::LtsDataView::signalSelectionChanged,
this, &AppChooser::enaDisa);
connect(m_ui->pbQuit, &QPushButton::clicked,
this, &QWidget::close); // calls closeEvent
connect(m_ui->pbSelect, &QPushButton::clicked,
this, &AppChooser::slotAppSelected);
m_model = new AppChooserModel(this);
m_model->initModel(LtsAppManager::getInstance()->getAvailableApps()); // TODO: test return val?
m_ui->widget->seamless();
//m_ui->widget->selectCustomView();
m_ui->widget->setModel(m_model);//, [&]() { return new CustomViewApp(this); });
m_ui->widget->hideColumn(AppChooserModel::Columns::Visible);
}
AppChooser::~AppChooser()
{
// qCritical() << "~AppChooser";
delete m_ui;
}
void AppChooser::slotQuit()
{
if (m_alreadyCloseEmitted)
return;
m_alreadyCloseEmitted = true;
emit signalLogout(); // if this window closes and no selection was made, then logout! (no apps, no login)
}
void AppChooser::closeEvent(QCloseEvent */*event*/)
{
if (!m_madeSelection)
slotQuit();
}
void AppChooser::slotAppSelected()
{
const QModelIndex idx = m_ui->widget->currentIndex();
const LtsAppDescriptor::AppDescription app = m_model->getRowData(idx.row());
if (!app.active)
return;
hide();
m_madeSelection = true;
emit signalAppSelected(app.id);
close();
}
void AppChooser::enaDisa()
{
const QModelIndexList selectedRows = m_ui->widget->selectedRows();
const int cntSelectedRows = selectedRows.size();
m_ui->pbSelect->setEnabled(cntSelectedRows == 1);
}
} // namespace LtsAppManager
+47
View File
@@ -0,0 +1,47 @@
// TODO: hdr
#pragma once
#include <QWidget>
#include <LtsAppDescriptor/lts_app_descriptor.h>
namespace Ui
{
class AppChooser;
}
namespace LtsAppManager
{
class AppChooserModel;
class AppChooser : public QWidget
{
Q_OBJECT
public:
AppChooser();
~AppChooser();
private:
Ui::AppChooser *m_ui;
AppChooserModel *m_model;
bool m_madeSelection;
bool m_alreadyCloseEmitted { false };
void enaDisa();
protected:
void closeEvent(QCloseEvent *);
signals:
void signalAppSelected(LtsAppDescriptor::App appId);
void signalLogout();
private slots:
void slotAppSelected();
void slotQuit();
};
} // namespace LtsAppManager
+92
View File
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AppChooser</class>
<widget class="QWidget" name="AppChooser">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>App Chooser</string>
</property>
<property name="windowIcon">
<iconset resource="../../LtsWidgets/resources/lts_resources.qrc">
<normaloff>:/rde_icon_256.png</normaloff>:/rde_icon_256.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Select App:</string>
</property>
</widget>
</item>
<item>
<widget class="LtsDataView::LtsDataView" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pbQuit">
<property name="text">
<string>Quit</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pbSelect">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Select</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>LtsDataView::LtsDataView</class>
<extends>QWidget</extends>
<header location="global">Viewer/LtsDataView/lts_data_view.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../LtsWidgets/resources/lts_resources.qrc"/>
</resources>
<connections/>
</ui>
+50
View File
@@ -0,0 +1,50 @@
// TODO: hdr
#include "app_chooser_model.h"
#include <LtsSession/lts_session.h>
namespace LtsAppManager
{
AppChooserModel::AppChooserModel(QObject *parent)
: LtsWidgets::LtsAbstractTableModel<LtsAppDescriptor::AppDescription>({tr("Name")}, parent)
//, m_logger(getLogger("AppChooserModel"))
{}
bool AppChooserModel::initModel(const LtsAppDescriptor::AppDescriptionVector &availableApps)
{
for (auto i = availableApps.constBegin(); i != availableApps.constEnd(); ++i)
{
const LtsAppDescriptor::AppDescription& appDesc = *i;
if (!appDesc.visible)
continue;
insertRow(appDesc);
}
return true;
}
QVariant AppChooserModel::data(const QModelIndex &index, int role) const
{
if (role != Qt::DisplayRole || !indexIsValid(index))
return QVariant();
const LtsAppDescriptor::AppDescription &ad = m_vecData.at(index.row()); // TODO: ExerciseManagement uses non-const-ref ! edit it there
switch (index.column())
{
case Name:
return LtsSession::LtsSession::appToString(ad.id);
case Visible:
return ad.visible;
}
return QVariant();
}
} // namespace LtsAppManager
+29
View File
@@ -0,0 +1,29 @@
// TODO: hdr
#pragma once
#include <LtsAppDescriptor/lts_app_descriptor.h>
#include <LtsWidgets/lts_abstract_table_model.h>
namespace LtsAppManager
{
class AppChooserModel final : public LtsWidgets::LtsAbstractTableModel<LtsAppDescriptor::AppDescription>
{
Q_OBJECT
public:
enum Columns
{
Name = 0,
Visible,
};
explicit AppChooserModel(QObject *parent = nullptr);
bool initModel(const LtsAppDescriptor::AppDescriptionVector &availableApps);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override final;
};
}
+52
View File
@@ -0,0 +1,52 @@
#
# $Id$
# $HeadURL$
# $Author$
# Created by Mike Wille, SLSE, 2016-10-27
#
# LtsAppManager
set(LtsAppManager_SRCS
lts_app_manager.cpp
AppChooser/app_chooser.cpp
AppChooser/app_chooser_model.cpp
AppChooser/CustomViews/app.cpp
)
NGL_WRAP_UI(LtsAppManager_SRCS
AppChooser/app_chooser.ui
AppChooser/CustomViews/app.ui
)
NGL_ADD_LIBRARY(WS LtsAppManager SHARED ${LtsAppManager_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppManager
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_SOURCE_DIR}/AppChooser/
)
NGL_TARGET_LINK_LIBRARIES(LtsAppManager
PRIVATE
NGLLogger
LtsDataView
LtsWidgets
LtsLogin
LtsPluginManager
LtsSession
LtsSessionData
LtsUtilsSettings
LtsMultiMonitor
ProjectConfig
Qt5::Gui
Qt5::Widgets
Qt5::Network # debug server
OpmSettingsGuiPluginInterface
LtsPluginLoader
PUBLIC
Qt5::Core
LtsAppDescriptor
)
NGL_INSTALL_TARGET(LtsAppManager)
File diff suppressed because it is too large. Load diff
+136
View File
@@ -0,0 +1,136 @@
#pragma once
#include <LtsAppDescriptor/lts_app_descriptor.h>
#include <Common/ProjectConfig/project_defines.h>
#include <LtsWidgets/lts_dialog.h>
#include <LtsWidgets/lts_base_layout_widget.h>
#include <LtsSession/lts_session.h>
#include <memory>
#include <QObject>
#include <QVector>
#ifndef LTSAPPMANAGER_EXPORT
# ifdef BUILD_STATIC_LIBRARIES
# define LTSAPPMANAGER_EXPORT
# else
# if defined(MAKE_LTSAPPMANAGER_LIB)
# define LTSAPPMANAGER_EXPORT Q_DECL_EXPORT
# else
# define LTSAPPMANAGER_EXPORT Q_DECL_IMPORT
# endif
# endif
#endif
namespace LtsPluginManager
{
class LtsPluginManager;
}
namespace LtsLogin
{
class LtsLogin;
}
namespace LtsSession
{
class LtsSession;
struct DdsConnectionParams;
}
namespace DeveloperTools
{
class DevDialog;
}
class QTcpSocket;
namespace LtsAppManager
{
class LTSAPPMANAGER_EXPORT LtsAppManager : public QObject
{
Q_OBJECT
public:
static LtsAppManager* getInstance();
void showLogin();
/**
* @brief createApp
* @param appId
* @param connParams
* @return Warning: can be null!!
*/
LtsPluginManager::LtsPluginManager *createApp(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams,
const QString &workspaceId = QString(), bool hidden = false, bool force = false);
bool startAppOrLetUserChoose(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, bool force = false);
void setLoginPluginIid(const QString &loginPluginIid) { m_loginPluginIid = loginPluginIid; }
void setMainApp(LtsAppDescriptor::App appId = LtsAppDescriptor::App::Login); // main app which is started after login
QVector<LtsPluginManager::LtsPluginManager *> getAppList() const { return m_appList ;}
LtsAppDescriptor::AppDescriptionVector getAvailableApps() const { return m_availableApps; }
void setAvailableApps(const LtsAppDescriptor::AppDescriptionVector &availableApps);
bool hasRightForApp(const LtsAppDescriptor::AppDescription &appDescription) const;
LtsAppDescriptor::AppDescription getApp(LtsAppDescriptor::App appId) const;
bool hasApp(LtsAppDescriptor::App appId) const;
int getNumberOfSituationMapInstances() const;
void debugSendMessage(const QString &msg);
/**
* @brief otherSessionsResponsibility checks if there is a session in all apps that can take a specified responsibility
* Example: Open a window in ONE session only. E.g. when receiving an alert via dtc to many sessions.
* @param thisSession
* @param requiredAppTypes
* @param requiredPartitionTypes
* @param menuGroup
* @param identifier
* @param area
* @return
*/
bool otherSessionsResponsibility(LtsSession::LtsSession *thisSession, const QVector<LtsAppDescriptor::App> &requiredAppTypes,
const QVector<IDL::PartitionType> &requiredPartitionTypes, const QString &menuGroup,
const QString &identifier, LtsWidgets::LayoutArea area);
private:
friend struct LtsAppManagerHandler;
friend class DeveloperTools::DevDialog;
LtsAppManager();
~LtsAppManager(); // no need to remove all apps, as the application only exits if all apps are closed
bool startAppOrLetUserChoose(const LtsAppDescriptor::AppDescription &appDescription, const LtsSession::DdsConnectionParams &connParams, bool force = false);
LtsPluginManager::LtsPluginManager *createApp(const LtsAppDescriptor::AppDescription &appDescription,
const LtsSession::DdsConnectionParams &connParams,
const QString &workspaceId = QString(),
bool hidden = false, bool force = false);
void saveState();
LtsAppDescriptor::AppDescriptionVector m_availableApps;
QVector<LtsPluginManager::LtsPluginManager *> m_appList; // started apps
LtsLogin::LtsLogin *m_login;
LtsAppDescriptor::AppDescription m_app; // the app to be started when the event loop is running
QString m_loginPluginIid;
public slots:
void slotLogout();
private slots:
void slotQuit(); // quit an app
void slotSoftQuit(); // quit an app, but make sure aboutToClose, etc. is called before deleting the app
void slotShowLogin();
void slotAppDestroyed();
void slotStartDebugServer();
void hideAllOtherBLWs(QObject *_caller);
void taskSwitch(QObject *_caller);
signals:
void signalLoginDone();
void signalLogoffDone();
void signalShowLogin();
void signalInitializationFinished(); // emit a signal if the base layout is shown (to notify that an app has been fully started)
void signalCommand(QTcpSocket *client, const QStringList argList);
};
} // namespace LtsAppManager
+12
View File
@@ -0,0 +1,12 @@
find_package(Botan2)
if (BOTAN_FOUND AND CMAKE_VERSION VERSION_GREATER 3.15.9) # botan doesn't use cmake, make it a cmake target | imported targets don't work correctly with 3.10
add_library(LtsBotan STATIC IMPORTED GLOBAL)
set_target_properties(LtsBotan PROPERTIES IMPORTED_LOCATION ${BOTAN_LIBRARIES})
set_target_properties(LtsBotan PROPERTIES INCLUDE_DIRECTORIES ${BOTAN_INCLUDE_DIRS})
target_compile_definitions(LtsBotan INTERFACE BOTAN_FOUND)
target_include_directories(LtsBotan SYSTEM INTERFACE ${BOTAN_INCLUDE_DIRS})
set(LTS_BOTAN LtsBotan)
message(STATUS "BOTAN_INCLUDE_DIRS = ${BOTAN_INCLUDE_DIRS}, BOTAN_LIBRARIES = ${BOTAN_LIBRARIES}, LTS_BOTAN = ${LTS_BOTAN}")
endif()
+21
View File
@@ -0,0 +1,21 @@
#
# $Id$
# $HeadURL$
# $Author$
#
NGL_ADD_LIBRARY2(ALL LtsCommandLineParser SHARED
LtsCommandLineParser.cpp
LtsCommandLineParser.h
)
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCommandLineParser
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsCommandLineParser
PRIVATE
ngltools
Qt5::Network
PUBLIC
Qt5::Core
)
+187
View File
@@ -0,0 +1,187 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
*/
#include "LtsCommandLineParser.h"
#include <ngltools/ngltools_filesystem.h>
#include <QNetworkInterface>
#include <QFlags>
#include <QSettings>
#include <QUrl>
namespace LtsCommandLineParser
{
static constexpr int s_maxDomainId = 232;
LtsCommandLineParser::LtsCommandLineParser(ProvideCommandLineParameterOptions opts)
: QCommandLineParser()
, m_enabledOptions(opts)
{
if (NGL::isAppInBuildBasePath() || m_enabledOptions.testFlag(CreateDomainIdFromIp))
{
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
for (const QHostAddress &address : addresses)
{
if (address.isLoopback() || address.isNull())
continue;
if (address.protocol() != QAbstractSocket::IPv4Protocol)
continue;
// use the lowest 8 bits (== last number in ip string) for domainId
// domain id max = 250
m_generatedDomainId = (address.toIPv4Address() & 0x000000ff) % 232;
if (m_generatedDomainId == 0)
m_generatedDomainId = s_maxDomainId;
break;
}
}
if (m_enabledOptions.testFlag(ExerciseId))
addOption({{QStringLiteral("e"), QStringLiteral("exerciseid")},
QStringLiteral("Default: -1 (invalid, get from lts-cc.conf)"),
QStringLiteral("exerciseId"),
QStringLiteral("-1")
});
if (m_enabledOptions.testFlag(DomainId))
addOption({{QStringLiteral("d"), QStringLiteral("domainid")},
QStringLiteral("Default: ") + QString::number(m_generatedDomainId),
QStringLiteral("domainId"),
QString::number(m_generatedDomainId)
});
if (m_enabledOptions.testFlag(CustomConfigFile))
{
const QString cf(NGL::findProjectConfigFile());
addOption({{QStringLiteral("i"), QStringLiteral("ini")},
QStringLiteral("Default: ") + cf,
QStringLiteral("filename"),
cf
});
}
if (m_enabledOptions.testFlag(TcpConnectionUrl))
{
const QString defaultTcpUrl(QStringLiteral("EXCTDS01:7400"));
addOption({{QStringLiteral("t"), QStringLiteral("tcpurl")},
QStringLiteral("Default: ") + defaultTcpUrl,
QStringLiteral("hostname:port"),
defaultTcpUrl
});
}
addHelpOption();
if (!m_enabledOptions.testFlag(NoVersion))
addVersionOption();
}
int LtsCommandLineParser::domainId() const
{
if (m_domainId == 0 && m_generatedDomainId != 0 && !m_domainIdSetByFunction)
{
#ifdef WIN32
// no ansi escapes on windows ...
fprintf(stdout, "\n\nAttention: using a default domain id != 0 because executable is run inside builddir - default domainid is %d\n\n", m_generatedDomainId);
#else
fprintf(stdout, "\n\n\e[31mAttention: using a default domain id != 0 because executable is run inside builddir - default domainid is %d\e[0m\n\n", m_generatedDomainId);
#endif
return m_generatedDomainId;
}
return m_domainId;
}
void LtsCommandLineParser::setDomainId(int id)
{
m_domainIdSetByFunction = true;
m_domainId = id;
}
bool LtsCommandLineParser::iniFileSet() const
{
if (!m_enabledOptions.testFlag(CustomConfigFile))
return false;
return isSet(QStringLiteral("i"));
}
bool LtsCommandLineParser::useTcpConnection() const
{
if (!m_enabledOptions.testFlag(TcpConnectionUrl))
return false;
return isSet(QStringLiteral("t"));
}
QUrl LtsCommandLineParser::tcpConnectionUrl() const
{
if (!m_enabledOptions.testFlag(TcpConnectionUrl))
return QUrl();
if (!useTcpConnection())
return QUrl();
QString str = value(QStringLiteral("t"));
// '_' is not allowed in a scheme, but rti needs it ...
str.replace(QLatin1String("tcpv4_lan"), QLatin1String("tcpv4lan"));
QUrl url = QUrl::fromUserInput(str);
if (url.scheme() == QLatin1String("http") ||
url.scheme() == QLatin1String("file"))
url.setScheme(QStringLiteral("tcpv4lan"));
return url;
}
bool LtsCommandLineParser::init(bool bParseHelp)
{
if (!parse(QCoreApplication::arguments()))
{
fprintf(stdout, "\nBad option: %s \n", qPrintable(errorText()));
if (!m_unitModuleTestMode)
return false;
}
if (m_enabledOptions.testFlag(ExerciseId))
m_exerciseId = value(QStringLiteral("e")).toInt();
if (m_enabledOptions.testFlag(DomainId))
m_domainId = value(QStringLiteral("d")).toInt();
if (m_enabledOptions.testFlag(CustomConfigFile))
m_iniFile = value(QStringLiteral("i"));
if (bParseHelp && isSet(QStringLiteral("h")))
showHelp();
if (!m_unitModuleTestMode && isSet(QStringLiteral("v")))
showVersion();
if (!domainIdProvided())
{
const QString iniFile = m_iniFile.isEmpty() ? NGL::findProjectConfigFile() : m_iniFile;
if (!iniFile.isEmpty())
{
QSettings s(iniFile, QSettings::IniFormat);
bool bOk;
int domainId = s.value(QStringLiteral("Global/DomainId"), m_domainId).toInt(&bOk);
if (bOk && domainId > 0 && domainId <= s_maxDomainId)
m_domainId = domainId;
}
}
return true;
}
void LtsCommandLineParser::showHelp(int exitCode)
{
if (!m_additionalHelpStr.isEmpty())
fprintf(stdout, "%s", qPrintable(m_additionalHelpStr));
// fprintf(stdout, "\n\n\e[33m example OWS : %s -m -a0 -d1 -b legatus -t EXCTDS01:7400 -l de_DE \n\n\e[0m", QCoreApplication::arguments().at(0).toLatin1().data());
QCommandLineParser::showHelp(exitCode);
}
bool LtsCommandLineParser::domainIdProvided() const
{
if (!m_enabledOptions.testFlag(DomainId))
return false;
return isSet(QStringLiteral("d"));
}
bool LtsCommandLineParser::exerciseIdProvided() const
{
if (!m_enabledOptions.testFlag(ExerciseId))
return false;
return isSet(QStringLiteral("e"));
}
} // namespace Common
+60
View File
@@ -0,0 +1,60 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QCommandLineParser>
#include <LtsCommandLineParser/ltscommandlineparser_export.h>
class QUrl;
namespace LtsCommandLineParser
{
class LTSCOMMANDLINEPARSER_EXPORT LtsCommandLineParser : public QCommandLineParser
{
public:
enum ProvideCommandLineParameterOptions
{
None = 0x0000,
CreateDomainIdFromIp = 0x0001,
DomainId = 0x0002,
ExerciseId = 0x0004,
CustomConfigFile = 0x0008,
TcpConnectionUrl = 0x0010,
NoVersion = 0x0020
};
explicit LtsCommandLineParser(ProvideCommandLineParameterOptions opts = ProvideCommandLineParameterOptions(CreateDomainIdFromIp|DomainId|ExerciseId|CustomConfigFile));
virtual ~LtsCommandLineParser() = default;
int domainId() const;
void setDomainId(int id);
int exerciseId() const { return m_exerciseId; }
QString iniFile() const { return m_iniFile; }
bool iniFileSet() const;
bool useTcpConnection() const;
QUrl tcpConnectionUrl() const;
virtual bool init(bool bParseHelp = true);
void showHelp(int exitCode = 0);
bool domainIdProvided() const;
bool exerciseIdProvided() const;
void setUnitModuleTestMode(bool onOff) { m_unitModuleTestMode = onOff; }
protected:
QString m_additionalHelpStr;
private:
QFlags<ProvideCommandLineParameterOptions> m_enabledOptions;
int m_exerciseId{0};
int m_domainId{0};
int m_generatedDomainId{0};
bool m_domainIdSetByFunction{false};
bool m_unitModuleTestMode{false};
QString m_iniFile;
};
} // namespace Common
+78
View File
@@ -0,0 +1,78 @@
#
# $Id$
# $HeadURL$
# $Author$
# Created by Christian Ehrlicher, SLSE, 2016-08-31
#
FIND_PACKAGE(GeographicLib REQUIRED)
NGL_ADD_LIBRARY2(LtsCoordTrans WS AAR SV HLA SHARED
lts_coord_trans.h
lts_coord_trans.cpp
)
NGL_TARGET_LINK_LIBRARIES(LtsCoordTrans
PRIVATE
GeographicLib
PUBLIC
Qt5::Core
)
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordTrans
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
SET(ltsCoordWidget_SRCS
lts_coord_widget.cpp
lts_coord_widget.h
lts_coord_widget_p.cpp
lts_coord_widget_p.h
)
NGL_WRAP_UI(ltsCoordWidget_SRCS
lts_coord_widget_mgrs.ui
lts_coord_widget_wgs84.ui
)
NGL_ADD_LIBRARY2(ltsCoordWidget WS AAR SV SHARED ${ltsCoordWidget_SRCS})
NGL_TARGET_LINK_LIBRARIES(ltsCoordWidget
PRIVATE
LtsCoordTrans
PUBLIC
Qt5::Core
Qt5::Widgets
)
NGL_TARGET_INCLUDE_DIRECTORIES(ltsCoordWidget
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
IF(TARGET LtsCoordTrans)
SET(_tools lonlat2mgrs mgrs2lonlat)
FOREACH(_tool ${_tools})
NGL_ADD_EXECUTABLE(ALL ${_tool} ${_tool}.cpp)
NGL_TARGET_LINK_LIBRARIES(${_tool}
Qt5::Core
GeographicLib
)
NGL_INSTALL_TARGET(${_tool})
ENDFOREACH()
ENDIF()
IF(BUILD_TESTING AND TARGET ltsCoordWidget)
ADD_EXECUTABLE(lts_coord_widget_test lts_coord_widget_test.cpp)
TARGET_LINK_LIBRARIES(lts_coord_widget_test
ltsCoordWidget
LtsCoordTrans
)
ENDIF()
IF(BUILD_TESTING AND TARGET LtsCoordTrans)
ADD_EXECUTABLE(lts_coord_trans_unit_test
lts_coord_trans_unit_test.cpp
lts_coord_trans_unit_test.h
)
TARGET_LINK_LIBRARIES(lts_coord_trans_unit_test
LtsCoordTrans
Qt5::Test
GeographicLib
)
ADD_TEST(NAME lts_coord_trans_unit_test COMMAND $<TARGET_FILE:lts_coord_trans_unit_test>)
ENDIF()
+71
View File
@@ -0,0 +1,71 @@
/**
* Convert a lon/lat coordinate to mgrs
* $Id$
* $HeadURL$
* $Author$
*/
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <cstring>
#include <string>
int main(int argc, char **argv)
{
if (argc < 3 || argc > 5)
{
fprintf(stderr, "Usage: %s <lon_degree> <lat_degree> [<precision>]\n", argv[0]);
fprintf(stderr, "Usage: %s <lon_rad> <lat_rad> -r [<precision>]\n", argv[0]);
return -1;
}
bool lonLatInRad = false;
double lon = atof(argv[1]);
double lat = atof(argv[2]);
int prec = 5;
if (argc >= 4)
{
int precArgId = -1;
if (strcmp(argv[3], "-r") == 0)
{
lonLatInRad = true;
if (argc == 5)
precArgId = 4;
}
else
precArgId = 3;
if (precArgId > 0)
prec = atoi(argv[precArgId]);
}
std::string mgrs;
int zone;
bool northp;
double x, y;
try
{
const double latDeg = lonLatInRad ? lat * 180 / M_PI : lat;
const double lonDeg = lonLatInRad ? lon * 180 / M_PI : lon;
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
}
catch (...)
{
fprintf(stderr, "Error converting %9.6f, %9.6f to MGRS coords\n", lat, lon);
return -2;
}
std::string part1, part2, part3, part4;
part1 = mgrs.substr(0, 3);
part2 = mgrs.substr(3, 2);
if (prec > 0)
{
part3 = mgrs.substr(5, prec);
part4 = mgrs.substr(5 + prec, prec);
}
fprintf(stdout, "%13.10f, %13.10f = %s %s %s %s (UTM: %d %.2f, %.2f %c)\n",
lon, lat,
part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(),
zone, x, y, northp ? 'N' : 'S');
return 0;
}
+452
View File
@@ -0,0 +1,452 @@
/**
* @RDE_HEADER@
* Provide some coordinate translation functions on top of GeographicLib
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-08-31
*/
#include "lts_coord_trans.h"
#include <GeographicLib/Geocentric.hpp>
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <QPointF>
#include <QRectF>
#include <QString>
#include <QStringList>
// #include <QDebug>
#include <iomanip> // setprecision
namespace LtsCoordTrans
{
static long s_conversion_counter_MGRS_Forward = 0; // counts conversion
static long s_conversion_counter_MGRS_Reverse = 0; // counts conversion
static long s_conversion_counter_WGS84_Forward = 0; // counts conversion
static long s_conversion_counter_WGS84_Reverse = 0; // counts conversion
static long s_conversion_counter_UTMUPS_Forward = 0; // counts conversion
static long s_conversion_counter_UTMUPS_Reverse = 0; // counts conversion
inline void incrementCounter(long &counterRef)
{
#ifdef LTS_DEBUG
++counterRef;
#endif
}
static inline std::string cleanupMgrsString(const QString &in)
{
const QByteArray ba(in.toLatin1());
std::string ret;
ret.reserve(ba.size());
for (const char c : ba)
{
if (c != ' ')
ret += c;
}
return ret;
}
QString UtmCoordinate::mgrs(int prec, bool withSpaces) const
{
if (prec < 0)
prec = 0;
if (prec > 11)
prec = 11;
std::string mgrs;
try
{
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
incrementCounter(s_conversion_counter_MGRS_Forward);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating mgrs from utm %d, %9.6f, %9.6f: %s\n", zone, x, y, e.what());
mgrs.clear();
}
QString ret;
if (withSpaces && mgrs.size() == static_cast<size_t>(5 + 2 * prec))
{
// 32UMD1234512345 -> 32U MD 12345 12345
const char *ptr = mgrs.c_str();
// for utm zones < 10, GeiographicLib appends a zero -> 08
ret = QString::fromLatin1(ptr, 3) + QLatin1Char(' ') +
QString::fromLatin1(ptr + 3, 2);
if (prec > 0)
{
ret += QLatin1Char(' ') + QString::fromLatin1(ptr + 5, prec) +
QLatin1Char(' ') + QString::fromLatin1(ptr + 5 + prec, prec);
}
}
else
ret = QString::fromLatin1(mgrs.c_str(), mgrs.size());
return ret;
}
QPointF UtmCoordinate::toPoint() const
{
return {x,y};
}
QPointF UtmCoordinate::geographic(double offsetX, double offsetY) const
{
QPointF ret;
try
{
GeographicLib::UTMUPS::Reverse(zone, northp, x + offsetX, y + offsetY, ret.ry(), ret.rx());
incrementCounter(s_conversion_counter_UTMUPS_Reverse);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating geographic from utm %d, %9.6f, %9.6f: %s\n", zone, x + offsetX, y + offsetY, e.what());
ret = QPointF();
}
return ret * deg2rad();
}
QPointF UtmCoordinate::geographic(const QPointF &offset) const
{
return geographic(offset.x(), offset.y());
}
QPointF UtmCoordinate::relativeTo(const UtmCoordinate &other) const
{
if (zone == other.zone)
return QPointF(x - other.x, y - other.y);
UtmCoordinate otherInMyZone = fromGeographic(other.geographic(), zone);
return QPointF(x - otherInMyZone.x, y - otherInMyZone.y);
}
UtmCoordinate UtmCoordinate::fromGeographic(double lon, double lat, int setZone)
{
// radian -> degree
lon *= rad2deg();
lat *= rad2deg();
UtmCoordinate ret;
try
{
// lat/lon -> utm
GeographicLib::UTMUPS::Forward(lat, lon, ret.zone, ret.northp, ret.x, ret.y, setZone);
incrementCounter(s_conversion_counter_UTMUPS_Forward);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while calculating mgrs base point from %9.6f, %9.6f: %s\n", lat, lon, e.what());
ret = UtmCoordinate();
}
return ret;
}
UtmCoordinate UtmCoordinate::fromGeographic(const QPointF &lonLat, int setZone)
{
return fromGeographic(lonLat.x(), lonLat.y(), setZone);
}
UtmCoordinate UtmCoordinate::fromMgrs(const QString &mgrs)
{
UtmCoordinate utm;
try
{
int prec;
// centerp must be true (GeographicLib::MGRS::Forward calculates with this!)
GeographicLib::MGRS::Reverse(cleanupMgrsString(mgrs), utm.zone, utm.northp, utm.x, utm.y, prec, true);
incrementCounter(s_conversion_counter_MGRS_Reverse);
}
catch (const std::exception &e)
{
fprintf(stderr, "Error while converting mgrs coordinate %s to utm: %s\n", qPrintable(mgrs), e.what());
utm = UtmCoordinate();
}
return utm;
}
QString mgrsFromGeographic(double lon, double lat, int prec, bool withSpaces)
{
return UtmCoordinate::fromGeographic(lon, lat, -1).mgrs(prec, withSpaces);
}
QPointF geographicFromMgrs(const QString &mgrs)
{
return UtmCoordinate::fromMgrs(mgrs).geographic();
}
QVector<UtmCoordinate> &staticDefaultSquaresHelper()
{
static QVector<UtmCoordinate> s_defaultMgrsSquares;
return s_defaultMgrsSquares;
}
UtmCoordinate &staticPreferedMgrsSquareHelper()
{
static UtmCoordinate s_preferedMgrsSquare;
return s_preferedMgrsSquare;
}
QVector<UtmCoordinate> &staticMgrsSquaresWithOffsetsHelper()
{
static QVector<UtmCoordinate> s_mgrsSquaresWithOffsets;
return s_mgrsSquaresWithOffsets;
}
QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter)
{
double lonMin, lonMax, latMin, latMmax;
rect_LatLon_radiant.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
// In that case upperRight and lowerLeft have to be converted to an UtmCoordinate
// using the utm zone of the basepoint.
// so caluclate the basepoint of the training area:
// see also basePointOfTrainingArea at Lts/Proxies/TrainingAreaBase/tab_training_area_helper.h
LtsCoordTrans::UtmCoordinate basePoint;
{
// calc tetra base point from training area center
const QPointF centerRad = rect_LatLon_radiant.center();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
mgrs += QStringLiteral("00000000000000");
// now we've the correct tetra base point
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
}
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
int offsetX = marginMeter.width();
int offsetY = marginMeter.height();
const QPointF topLeft(lowerLeft.geographic(-offsetX, -offsetY));
const QPointF bottomRight(upperRight.geographic(offsetX, offsetY));
QRectF res = QRectF(topLeft, bottomRight);
return res;
}
QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin)
{
QVector<UtmCoordinate> ret = getMgrsSquaresInternal(rect);
if (setAsDefaultSquares)
staticDefaultSquaresHelper() = ret;
if (setAsPreferedSquare)
staticPreferedMgrsSquareHelper() = LtsCoordTrans::UtmCoordinate::fromGeographic(rect.center());
if (setAsSquareWithMargin)
staticMgrsSquaresWithOffsetsHelper() = ret;
return ret;
}
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect)
{
QVector<UtmCoordinate> ret;
QVector<QString> tmpMgrsSquares;
// calculate mgrs squares
// 10km - Schritte - hoffentlich sind wir nicht in kleineren Zonen unterwegs
double lonMin, lonMax, latMin, latMmax;
rect.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
// maybe the training area is NOT within a single UTM square.
bool withInSingleUtmSquare = true;
{
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax));
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin));
withInSingleUtmSquare = upperRight.zone == lowerLeft.zone;
}
// In that case upperRight and lowerLeft have to be converted to an UtmCoordinate
// using the utm zone of the basepoint.
// so caluclate the basepoint of the training area:
// see also basePointOfTrainingArea at Lts/Proxies/TrainingAreaBase/tab_training_area_helper.h
LtsCoordTrans::UtmCoordinate basePoint;
{
// calc tetra base point from training area center
QPointF centerRad = rect.center();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
mgrs += QStringLiteral("00000000000000");
// now we've the correct tetra base point
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
}
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
const double maxX(upperRight.x - lowerLeft.x); // meter
const double maxY(upperRight.y - lowerLeft.y); // meter
// +10000 um auch den Rest rechts und oben zu berücksichtigen
// allerdings dann genau den Rand und auf keinen Fall darüber hinaus
const int increment = withInSingleUtmSquare ? 10000 : 1000;
for (double x = 0; x <= maxX + increment; x += increment)
{
const double xOfs = std::min(x, maxX);
for (double y = 0; y <= maxY + increment; y += increment)
{
const double yOfs = std::min(y, maxY);
// convert to utm coordinate on mgrs intersection point
const UtmCoordinate utm(UtmCoordinate::fromGeographic(lowerLeft.geographic(xOfs, yOfs)));
const QString mgrs(utm.mgrs(0));
// qDebug("mgrs: %s, %s, %lf, %lf", qPrintable(mgrs), qPrintable(utm.mgrs(5)), xOfs, yOfs);
if (!tmpMgrsSquares.contains(mgrs))
{
tmpMgrsSquares += mgrs;
ret += utm;
}
}
}
return ret;
}
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter)
{
// get main squares
const QVector<UtmCoordinate> mainUtms = getMgrsSquaresInternal(geoRect);
QStringList mainSquares;
mainSquares.reserve(mainUtms.count());
for (const auto &utm : mainUtms)
mainSquares.append(utm.mgrs(0));
// get mainsquare with surrounding squares rect by main square center pos and offsets
const QRectF rectWithOffset = addMarginToArea(geoRect,offsetMeter);
QVector<UtmCoordinate> mainUtmsWithOffset = getMgrsSquaresInternal(rectWithOffset);
QStringList mainSquaresWithOffset;
mainSquaresWithOffset.reserve(mainUtmsWithOffset.count());
for (const auto &utm : qAsConst(mainUtmsWithOffset))
mainSquaresWithOffset.append(utm.mgrs(0));
// get only surrounding squares
QStringList surroundingSquares;
surroundingSquares.reserve(mainSquaresWithOffset.count());
for (const auto & mgrs0 : mainSquaresWithOffset)
{
if (!mainSquares.contains(mgrs0))
surroundingSquares.append(mgrs0);
}
return surroundingSquares;
}
QVector<UtmCoordinate> defaultMgrsSquareWithOffsets()
{
return staticMgrsSquaresWithOffsetsHelper();
}
void setDefaultMgrsSquares(const QVector<QString> &squares)
{
QVector<UtmCoordinate> coords;
coords.reserve(squares.size());
for (const QString &s : squares)
coords += UtmCoordinate::fromMgrs(s + QStringLiteral("00000000000000"));
staticDefaultSquaresHelper() = coords;
}
QVector<UtmCoordinate> defaultMgrsSquares()
{
return staticDefaultSquaresHelper();
}
UtmCoordinate preferedMgrsSquare()
{
return staticPreferedMgrsSquareHelper();
}
bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut)
{
// radian -> degree
lon *= rad2deg();
lat *= rad2deg();
try
{
GeographicLib::Geocentric::WGS84().Forward(lat, lon, altitude, xOut, yOut, zOut);
incrementCounter(s_conversion_counter_WGS84_Forward);
}
catch (...)
{
xOut = 0;
yOut = 0;
zOut = 0;
return false;
}
return true;
}
bool geocentric2LonLatAlt(double x, double y, double z,
double &lon, double &lat, double &altitude)
{
// Achtung: LatLon statt LonLat
// Geocentric -> LonLat
try
{
GeographicLib::Geocentric::WGS84().Reverse(x, y, z, lat, lon, altitude);
incrementCounter(s_conversion_counter_WGS84_Reverse);
}
catch (...)
{
lon = 0;
lat = 0;
altitude = 0;
return false;
}
// degree -> radian
lon *= deg2rad();
lat *= deg2rad();
return true;
}
double distance(const QPointF &p1, const QPointF &p2)
{
return distance(p1.x(), p1.y(), p2.x(), p2.y());
}
double squareDistance(const QPointF &p1, const QPointF &p2)
{
return squareDistance(p1.x(), p1.y(), p2.x(), p2.y());
}
std::ostream &operator<< (std::ostream &stream,
const UtmCoordinate &coord)
{
stream << "UtmCoordinate(" << std::setprecision(8) << coord.x
<< "#" << std::setprecision(8) << coord.y
<< " zone=" << coord.zone << ")";
return stream;
}
void dumpConversionCounter()
{
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Forward : - mgrs : %ld\n" , s_conversion_counter_MGRS_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Reverse - fromMgrs : %ld\n" , s_conversion_counter_MGRS_Reverse);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Forward - lonLatAltToGeocentric : %ld\n" , s_conversion_counter_WGS84_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Reverse - geocentric2LonLatAlt : %ld\n" , s_conversion_counter_WGS84_Reverse);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Forward - fromGeographic : %ld\n" , s_conversion_counter_UTMUPS_Forward);
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Reverse - geographic : %ld\n" , s_conversion_counter_UTMUPS_Reverse);
}
long getConversionCounter_MGRS_Forward() { return s_conversion_counter_MGRS_Forward; }
long getConversionCounter_MGRS_Reverse() { return s_conversion_counter_MGRS_Reverse; }
long getConversionCounter_WGS84_Forward() { return s_conversion_counter_WGS84_Forward; }
long getConversionCounter_WGS84_Reverse() { return s_conversion_counter_WGS84_Reverse; }
long getConversionCounter_UTMUPS_Forward() { return s_conversion_counter_UTMUPS_Forward; }
long getConversionCounter_UTMUPS_Reverse() { return s_conversion_counter_UTMUPS_Reverse; }
} // namespace LtsCoordTrans
+355
View File
@@ -0,0 +1,355 @@
/**
* @RDE_HEADER@
* Provide some coordinate translation functions on top of GeographicLib
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-08-31
*/
#pragma once
#include <LtsCoordTrans/ltscoordtrans_export.h>
#include <string>
#include <cmath>
#include <QVector>
class QPointF;
class QRectF;
class QSize;
class QString;
namespace LtsCoordTrans
{
/**
* What do we want to achieve:
* - internally our coordinates are represented as geographic coordinates
* (lat/lon, wgs84, radian)
* - convert them to a user-readable format which is (currently) MGRS
* - convert to/from a metric offset relative to a base point from/to
* geographic coordinates (e.g. for equipment in training area, for calculations)
* - calculate a base point from a given geogrphic coordinate (base point
* should be on an mgrs intersection (e.g. 32UPD 0 0)
*
* a nice view of utm and mgrs square could be found here:
* https://mappingsupport.com/p2/coordinates-mgrs-gissurfer-maps.html
*/
// some helper functions
inline Q_CONSTEXPR double pi() { return M_PI; }
inline Q_CONSTEXPR double deg2rad() { return pi() / 180.; }
inline Q_CONSTEXPR double rad2deg() { return 1. / deg2rad(); }
struct LTSCOORDTRANS_EXPORT UtmCoordinate
{
int zone = -1;
bool northp = true;
double x = 0.; // in meter
double y = 0.; // in meter
UtmCoordinate() = default;
UtmCoordinate(int zone_, double x_, double y_, bool northp_ = true)
: zone(zone_)
, northp(northp_)
, x(x_)
, y(y_)
{}
/**
* @brief Checks if the UtmCoordinate is properly initialized (just checks the zone)
* @return if UtmCoordinate is valid
*/
bool isValid() const { return zone > 0 && zone <= 60; }
/**
* @brief Convert the utm coordinate into a mgrs string (prettified - with spaces)
* @param prec precision to use (default: 5)
* @param withSpaces Insert spaces between mgrs parts
* @return mgrs string (empty if conversion did not work)
*/
QString mgrs(int prec = 5, bool withSpaces = true) const;
QPointF toPoint() const;
/**
* @brief Convert the utm coordinate to geographic coordinate, add offset if desired
* @param offsetX the possible x offset (east) to the utm coordinate
* @param offsetY the possible y offset (north) to the utm coorinate
* @return
*/
QPointF geographic(double offsetX = 0., double offsetY = 0.) const;
/**
* @brief overload of QPointF geographic(double, double) const
*/
QPointF geographic(const QPointF &offset) const;
/**
* @brief Get relative Position of coordinate to other coordinate
* @param other Coordinate to get the offset to.
* @return
*/
QPointF relativeTo(const UtmCoordinate &other) const;
/**
* @brief convert a geographic coordinate into a utm coordinate
* @param lon longitude in radian
* @param lat latitude in radian
* @param setZone override default zone (needed e.g. to be sure that every utm coord in an vector gets the same utm zone)
* @return BasePointInformation for conversions from/to relative coordinates
*/
static UtmCoordinate fromGeographic(double lon, double lat, int setZone = -1);
/**
* @brief overload of UtmCoordinate fromGeographic(double, double, bool)
*/
static UtmCoordinate fromGeographic(const QPointF &lonLat, int setZone = -1);
/**
* @brief convert a mgrs coordinate into a utm coordinate
* @param mgrs coordinate to convert
*/
static UtmCoordinate fromMgrs(const QString &mgrs);
};
/**
* @brief convert lon/lat coordinates to mgrs. Shortcut for UtmCoordinate::fromGeographic().mgrs()
* @param lon longitude in radian
* @param lat latitude in radian
* @param prec the precision for the mgrs string
* @param withSpaces Insert spaces between mgrs parts
* @return mgrs as one string with spaces
*/
LTSCOORDTRANS_EXPORT QString mgrsFromGeographic(double lon, double lat, int prec = 5, bool withSpaces = true);
/**
* @brief convert a mgrs string into geographic coordinates. Shortcut for UtmCoordinate::fromMgrs().geographic()
* @param mgrs coordinate to convert
* @return geographic coordinate (invalid QPointF on conversion errror)
*/
LTSCOORDTRANS_EXPORT QPointF geographicFromMgrs(const QString &mgrs);
/**
* @brief addMarginToArea add a margin in meter to a north _orientated_ area
* @param rect_LatLon_radiant rect top be increased
* @param marginMeter margin to be added
* @return increase Area
*/
LTSCOORDTRANS_EXPORT QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter);
/**
* Return all MGRS squares covered by the current exercise area
* @param rect the rectangle to retriebe the mgrs squares for (in geographic coordinates)
* @param setAsDefaultSquares if true, then set the calculated squares as default squares returned by defaultMgrsSquares()
* @param setAsPreferedSquare if true, then set the center square as prefered square returned by preferedMgrsSquare()
* @return all current available mgrs squares
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin = false);
/**
* @brief getMgrsSquaresInternal
* @param rect
* @return
*/
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect);
/**
* @brief getMgrsSurroundingSquares
* @param geoRect
* @param offsetMeter
* @return
*/
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter);
/**
* @brief defaultMgrsSquareWithOffsets
* @return
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquareWithOffsets();
/**
* @brief Set default MGRS squares to use if getMgrsSquares() is not possible
*/
LTSCOORDTRANS_EXPORT void setDefaultMgrsSquares(const QVector<QString> &squares);
/**
* @brief Return previously set default squares (e.g. for LtsCoordWidget)
* @return all available mgrs squares for the rect set with getMgrsSquares()
*/
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquares();
/**
* @brief Return previously set prefered square (e.g. for LtsCoordWidget)
* @return prefered mgrs square (center) of the rect set with getMgrsSquares()
*/
LTSCOORDTRANS_EXPORT UtmCoordinate preferedMgrsSquare();
/**
* @brief Convert lon/lat/altitude to geocentric coordinates, output in x,y,z
* @param lon longitude in radian
* @param lat latitude in radian
* @param altitude in meter above ground
* @param xOut geocentric x-coordinate
* @param yOut geocentric y-coordinate
* @param zOut geocentric z-coordinate
* @return true on success
*/
LTSCOORDTRANS_EXPORT bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut);
/**
* @brief Convert geocentric coordinates to lon/lat/altitude output in lon,lat,altiude
* @param x geocentric x-coordinate
* @param y geocentric y-coordinate
* @param z geocentric z-coordinate
* @param lon longitude in radian
* @param lat latitude in radian
* @param altitude in meter above ground
* @return true on success
*/
LTSCOORDTRANS_EXPORT bool geocentric2LonLatAlt(double x, double y, double z,
double &lon, double &lat, double &altitude);
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double squareDistance(const UtmCoordinate &p1, const UtmCoordinate &p2)
{
if (p1.zone != p2.zone)
{
Q_ASSERT(false);
return std::nan("");
}
return std::pow(p1.x - p2.x, 2) + std::pow(p1.y - p2.y, 2);
}
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
LTSCOORDTRANS_EXPORT double squareDistance(const QPointF &p1, const QPointF &p2);
/**
* @brief Calculate the square distance between two geographicPoints in meter
* @param lon1 longitude of the first point
* @param lat1 latitude of the first point
* @param lon2 longitude of the second point
* @param lat2 latitude of the second point
* @return distance between the two points in meter
*/
inline double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
{
const auto p1(UtmCoordinate::fromGeographic(lon1, lat1));
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
return squareDistance(p1, p2);
}
/**
* Overload of double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double squareDistance(const UtmCoordinate &p1, const double lon2, const double lat2)
{
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
return squareDistance(p1, p2);
}
/**
* Overload of double distance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
inline double distance(const UtmCoordinate &p1, const UtmCoordinate &p2)
{
if (p1.zone != p2.zone)
{
Q_ASSERT(false);
return std::nan("");
}
return sqrt(squareDistance(p1, p2));
}
/**
* Overload of double distance(const double lon1, const double lat1, const double lon2, const double lat2)
*/
LTSCOORDTRANS_EXPORT double distance(const QPointF &p1, const QPointF &p2);
/**
* @brief Calculate the distance between two geographicPoints in meter
* @param lon1 longitude of the first point
* @param lat1 latitude of the first point
* @param lon2 longitude of the second point
* @param lat2 latitude of the second point
* @return distance between the two points in meter
*/
inline double distance(const double lon1, const double lat1, const double lon2, const double lat2)
{
return sqrt(squareDistance(lon1, lat1, lon2, lat2));
}
inline double distance(const UtmCoordinate &p1, const double lon2, const double lat2)
{
return sqrt(squareDistance(p1, lon2, lat2));
}
// for debugging purpose:
LTSCOORDTRANS_EXPORT void dumpConversionCounter();
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Reverse();
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Reverse();
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Forward();
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Reverse();
LTSCOORDTRANS_EXPORT std::ostream &operator<< (std::ostream &stream,
const UtmCoordinate &coord);
//
// class UtmCoordinate3D
//
/*!
* \brief The class UtmCoordinate3D add just a zValue to its baseClass UtmCoordinate
*/
class LTSCOORDTRANS_EXPORT UtmCoordinate3D : public LtsCoordTrans::UtmCoordinate
{
using BaseClass = LtsCoordTrans::UtmCoordinate;
public:
UtmCoordinate3D()
{
reset();
}
explicit UtmCoordinate3D(const LtsCoordTrans::UtmCoordinate &utm2d, double _zValue = std::nan(""))
:BaseClass()
{
zone = utm2d.zone;
northp = utm2d.northp;
x = utm2d.x;
y = utm2d.y;
zValue = _zValue;
}
void reset()
{
zone = -1;
northp = true;
// To be consistent with LtsCoordinate:
// For elevation a value of '0' should be valid.
// So an invalid zValue has to be different: choose NaN
// To be consistent to default constructor of Core::GeoPosition, the (invalid) default value for lon, lat is '0':
x = 0.; // in meter
y = 0.; // in meter
zValue = std::nan("");
}
double z() const { return zValue; }
protected:
double zValue{std::nan("")};
};
} // namespace LtsCoordTrans
+473
View File
@@ -0,0 +1,473 @@
/**
* Unit test for ltscoordtrans
* $Id$
* $HeadURL$
* $Author$
*/
#include "lts_coord_trans_unit_test.h"
#include "lts_coord_trans.h"
#include <GeographicLib/Math.hpp>
#include <QTest>
#include <QDebug>
QTEST_MAIN(coordtrans_test)
void coordtrans_test::initTestCase()
{
qDebug("sizeof(UtmCoordinate) = %lu", sizeof(LtsCoordTrans::UtmCoordinate));
}
void coordtrans_test::cleanupTestCase()
{}
void coordtrans_test::testFromGeographic()
{
// 32UMD78 = 8.6264954126, 53.1138437183 -> 32UMD
// 32UPD55 = 11.3003696521, 52.8224535191 -> 32UPD
// 40RBM88 = 54.8821607021, 24.2631973752 -> 40QBM
LtsCoordTrans::UtmCoordinate utm;
QPointF lonlat;
lonlat = QPointF(8.6264954126, 53.1138437183) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("32U MD"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(8), QStringLiteral("32U MD 74999999 84999999"));
lonlat = QPointF(11.3003696521, 52.8224535191) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("32U PD"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(5), QStringLiteral("32U PD 55000 55000"));
lonlat = QPointF(54.8821607021, 24.2631973752) * LtsCoordTrans::deg2rad();
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
QCOMPARE(utm.mgrs(0), QStringLiteral("40R BM"));
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QCOMPARE(utm.mgrs(5), QStringLiteral("40R BM 85000 85000"));
// 40R BM 00000 00000 (= base point) is not a 'valid' point because
// it overlaps with 40Q BM and therefore it should be 40Q BM 00000 00000
utm = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("40R BM 00000 00000"));
QCOMPARE(utm.mgrs(0), QStringLiteral("40Q BM"));
}
void coordtrans_test::testMgrsFromGeographic()
{
// 8.6191049344, 53.1048309844 -> 32U MD 745000000 840000000
QString mgrs;
QPointF lonlat;
lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 8, false);
QCOMPARE(mgrs, QStringLiteral("32UMD7450000084000000"));
}
void coordtrans_test::testRelativeMetricalOffet_data()
{
// define set of data needed
QTest::addColumn<QPoint>("offset");
QTest::addColumn<QString>("correctMgrs");
// noch in der urspruenglichen zone:
QTest::newRow("set 1") << QPoint( 5, 5) << QStringLiteral("32U PD 99995 99995");
// impliziter zonewechsel:
QTest::newRow("set 2") << QPoint(100, 100) << QStringLiteral("32U QE 00090 00090");
QTest::newRow("set 3") << QPoint(500, 500) << QStringLiteral("32U QE 00490 00490");
}
void coordtrans_test::testRelativeMetricalOffet()
{
QFETCH(QPoint, offset);
QFETCH(QString, correctMgrs);
const LtsCoordTrans::UtmCoordinate &utmRef = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 99990 99990"));
qDebug() << "utmRef = " << utmRef.mgrs();
{
LtsCoordTrans::UtmCoordinate utmRaw;
utmRaw.x = utmRef.x + offset.x();
utmRaw.y = utmRef.y + offset.y();
utmRaw.zone = utmRef.zone;
qDebug() << "offset = " << offset << " - utmRaw = " << utmRaw.mgrs();
QCOMPARE(utmRaw.mgrs(), correctMgrs);
}
// besser:
// - api, also ctor nutzen,
// - koennen es const machen
{
const LtsCoordTrans::UtmCoordinate utmSimple(utmRef.zone,
utmRef.x + offset.x(),
utmRef.y + offset.y());
qDebug() << "offset = " << offset << " - utmSimple = " << utmSimple.mgrs();
QCOMPARE(utmSimple.mgrs(), correctMgrs);
}
}
QRectF createArea(const QString &base,
int xCenter, int yCenter, int width, int height)
{
// qDebug() << "createArea : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
// qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
// qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
QRectF rect (topLeft, bottomRight);
return rect;
}
void coordtrans_test::testAddMarginToArea()
{
// Altmark
{
qDebug() << "Altmark";
QRectF area = createArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000);
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(5000, 20000));
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg();
QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
}
// Luxemburg:
{
qDebug() << "Luxemburg";
QRectF area = createArea(QStringLiteral("32U LA 0000000000"), 100, 100, 5000, 5000);
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(50000, 20000));
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight()*LtsCoordTrans::rad2deg();
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight() << " - degree:" << area1Increased.bottomRight()*LtsCoordTrans::rad2deg();
QVERIFY2( area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
QVERIFY2( area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
QVERIFY2( area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
QVERIFY2( area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
}
}
void coordtrans_test::testIncreaseRect()
{
// punkt definieren:
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
LtsCoordTrans::UtmCoordinate topLeftUtm;
LtsCoordTrans::UtmCoordinate topLeftUtm2;
// Variante zum Vergroessern #1 via .geographic
{
QPointF topLeftGeo = centerPoint.geographic(-1111, 2222); // offset_m
QPointF bottomRightGeo = centerPoint.geographic( 3000, -4000); // offset_m
topLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(topLeftGeo);
const LtsCoordTrans::UtmCoordinate bottomRightUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(bottomRightGeo);
qDebug() << "topLeftUtm: " << topLeftUtm.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm.y;
qDebug() << "bottomRightUtm: " << bottomRightUtm.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm.y;
}
// Variante zum Vergroessern #2 via addition vom UtmCoordinates
{
topLeftUtm2.x = centerPoint.x - 1111;
topLeftUtm2.y = centerPoint.y + 2222;
LtsCoordTrans::UtmCoordinate bottomRightUtm2;
bottomRightUtm2.x = centerPoint.x + 3000;
bottomRightUtm2.y = centerPoint.y - 4000;
qDebug() << "topLeftUtm2: " << topLeftUtm2.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm2.y;
qDebug() << "bottomRightUtm2: " << bottomRightUtm2.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm2.y;
}
QVERIFY2(qFuzzyCompare(topLeftUtm.x, topLeftUtm2.x), "testIncreaseRect x failed");
QVERIFY2(qFuzzyCompare(topLeftUtm.y, topLeftUtm2.y), "testIncreaseRect y failed");
}
void coordtrans_test::testIncreaseRect2()
{
// punkt definieren:
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
QRectF myRect;
QRectF myRectIncreased;
{
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
QPointF topLeftGeo = centerPoint.geographic(-3000, 2000); // offset_m
QPointF bottomRightGeo = centerPoint.geographic( 3000, -2000); // offset_m
myRect = QRectF(topLeftGeo, bottomRightGeo).normalized();
qDebug() << "myRect : " << myRect;
qDebug() << "myRect center : " << myRect.center();
qDebug() << "myRect height : " << myRect.height();
}
// hier geht es los:
{
const QPointF centerGeo = myRect.center();
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerGeo);
QPointF topLeftGeo = centerUtm.geographic(-6000, 4000);
QPointF bottomRightGeo = centerUtm.geographic( 6000, -4000);
myRectIncreased = QRectF(topLeftGeo, bottomRightGeo).normalized();
qDebug() << "myRectIncreased : " << myRectIncreased;
qDebug() << "myRectIncreased center: " << myRectIncreased.center();
qDebug() << "myRectIncreased height: " << myRectIncreased.height();
}
// qFuzzyCompae ist zu empfindlich, daher machen wir es selbst:
QVERIFY2( (int)(myRectIncreased.center().x()*10000) == (int)(myRect.center().x()*10000), "testIncreaseRect2 centerX failed");
QVERIFY2( (int)(myRectIncreased.center().y()*10000) == (int)(myRect.center().y()*10000), "testIncreaseRect2 centerY failed");
QVERIFY2(myRectIncreased.width() > myRect.width(), "testIncreaseRect2 width failed");
QVERIFY2(myRectIncreased.height() > myRect.height(), "testIncreaseRect2 height failed");
}
void coordtrans_test::testGeographicFromMgrs()
{
// 32UPD7654398765 -> 11.6436815550, 53.2088411454
QString mgrs(QStringLiteral("32UPD7654398765"));
QPointF geographic = LtsCoordTrans::geographicFromMgrs(mgrs);
QCOMPARE(geographic, QPointF(11.6436815550, 53.2088411454) * LtsCoordTrans::deg2rad());
}
void coordtrans_test::testMgrsSquares()
{
// a nice view of utm and mgrs square could be found here:
// https://mappingsupport.com/p2/coordinates-mgrs-gissurfer-maps.html
checkArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U PC"),
QStringLiteral("32U PD")
});
checkArea(QStringLiteral("32U MD 0000000000"), 50000, 10000, 30000, 30000, { QStringLiteral("32U MC"),
QStringLiteral("32U MD")
});
checkArea(QStringLiteral("32U MD 0000000000"), 89999, 89999, 21000, 21000, { QStringLiteral("32U MD"),
QStringLiteral("32U ME"),
QStringLiteral("32U ND"),
QStringLiteral("32U NE")
});
// UAE Areas
checkArea(QStringLiteral("39Q XG 0000000000"), 40000, 35000, 60000, 70000, { QStringLiteral("39Q XG"),
QStringLiteral("39R XG")
});
checkArea(QStringLiteral("40R CP 0000000000"), 94000, 9000, 7000, 16000, { QStringLiteral("40R CP") });
checkArea(QStringLiteral("40R DN 0000000000"), 500, 95500, 12000, 7000, { QStringLiteral("40R CN"),
QStringLiteral("40R DN")
});
checkArea(QStringLiteral("40R DN 0000000000"), 500, 96760, 30000, 30000, { QStringLiteral("40R CN"),
QStringLiteral("40R CP"),
QStringLiteral("40R DN"),
QStringLiteral("40R DP")
});
checkArea(QStringLiteral("40R BM 0000000000"), 59000, 59500, 46000, 40000, { QStringLiteral("40Q BM"),
QStringLiteral("40R BM")
});
// Luxemburg
//
checkArea(QStringLiteral("32U LA 0000000000"), 10000, 10000, 5000, 5000, { QStringLiteral("32U LA"),
});
// and here in different utm zones: 31, 32
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100, 60000, 60000, { QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("32U KA"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LV")
});
// is extent > 100000 realistic?
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,150000,150000, { QStringLiteral("31U FQ"),
QStringLiteral("31U FR"),
QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("32U KA"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LV")
});
checkArea(QStringLiteral("32U LA 0000000000"), 100, 100,200000,200000, { QStringLiteral("31U FP"),
QStringLiteral("31U FQ"),
QStringLiteral("31U FR"),
QStringLiteral("31U GP"),
QStringLiteral("31U GQ"),
QStringLiteral("31U GR"),
QStringLiteral("31U GS"),
QStringLiteral("32U KA"),
QStringLiteral("32U KB"),
QStringLiteral("32U KV"),
QStringLiteral("32U LA"),
QStringLiteral("32U LB"),
QStringLiteral("32U LV"),
QStringLiteral("32U MA"),
QStringLiteral("32U MB"),
QStringLiteral("32U MV")
});
}
QVector<LtsCoordTrans::UtmCoordinate> checkAreaGetMgrsSquares(const QString &base,
int xCenter, int yCenter, int width, int height)
{
// qDebug() << "checkAreaGetMgrsSquares : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
// qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
// qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
QStringList src;
src.reserve(squares.size());
for (const auto &utm : squares)
src += utm.mgrs(0);
src.sort();
// qDebug() << "src: '" << src;
return squares;
}
void coordtrans_test::testMgrsSquaresWithSurrounding()
{
// // Result: ONE MGRS-Square
// checkArea(QStringLiteral("32U NC 0000000000"), 50000, 50000, 5000, 5000, { QStringLiteral("32U NC")
// });
// // Result: NINE MGRS-Square
// checkArea(QStringLiteral("32U NC 0000000000"), 50000, 50000, 120000, 120000, { QStringLiteral("32U MB"),
// QStringLiteral("32U MC"),
// QStringLiteral("32U MD"),
// QStringLiteral("32U NB"),
// QStringLiteral("32U NC"),
// QStringLiteral("32U ND"),
// QStringLiteral("32U PB"),
// QStringLiteral("32U PC"),
// QStringLiteral("32U PD")
// });
int initialSize = 5000;
// Result: ONE MGRS-Square
QVector<LtsCoordTrans::UtmCoordinate> squares1 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, initialSize, initialSize);
QVERIFY2(squares1.size() == 1, "checkAreaGetMgrsSquares failed");
// Result: NINE MGRS-Square
int biggerSize = initialSize + (1*110000);
QVector<LtsCoordTrans::UtmCoordinate> squares9 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, biggerSize, biggerSize);
// qDebug() << "squares9 '" << squares9.size();
QVERIFY2(squares9.size() == 9, "checkAreaGetMgrsSquares biggerSize failed");
int muchBiggerSize = initialSize + (3*110000);
QVector<LtsCoordTrans::UtmCoordinate> square33 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, muchBiggerSize, muchBiggerSize);
// qDebug() << "square33 '" << square33.size();
QVERIFY2(square33.size() == 33, "checkAreaGetMgrsSquares muchBiggerSize failed");
QStringList string1;
string1.reserve(squares1.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares1))
string1.append(item.mgrs(0));
QStringList string9;
string9.reserve(squares9.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(squares9))
string9.append(item.mgrs(0));
QStringList string33;
string33.reserve(square33.size());
for (const LtsCoordTrans::UtmCoordinate &item : qAsConst(square33))
string33.append(item.mgrs(0));
{
QStringList surroundingStrings;
surroundingStrings.reserve(squares9.size());
for (const QString &str : qAsConst(string9))
if (!string1.contains(str))
surroundingStrings.append(str);
QVERIFY2(surroundingStrings.size() == 8, "checkAreaGetMgrsSquares squaresDiff failed");
}
{
QStringList surroundingStrings;
surroundingStrings.reserve(string33.size());
for (const QString &str : qAsConst(string33))
if (!string1.contains(str))
surroundingStrings.append(str);
QVERIFY2(surroundingStrings.size() == 32, "checkAreaGetMgrsSquares squaresDiff failed");
}
}
void coordtrans_test::checkArea(const QString &base,
int xCenter, int yCenter, int width, int height,
const QStringList &coords)
{
qDebug() << "checkArea : base: " << base;
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
QStringList src;
src.reserve(squares.size());
for (const auto &utm : squares)
src += utm.mgrs(0);
src.sort();
qDebug() << "src: '" << src << "' expected: '" << coords << "'";
QCOMPARE(src, coords);
}
+38
View File
@@ -0,0 +1,38 @@
/**
* Unit test for ltscoordtrans
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <QObject>
#include <QStringList>
class coordtrans_test : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void testFromGeographic();
void testMgrsFromGeographic();
void testRelativeMetricalOffet_data();
void testRelativeMetricalOffet();
void testAddMarginToArea();
void testIncreaseRect();
void testIncreaseRect2();
void testGeographicFromMgrs();
void testMgrsSquares();
void testMgrsSquaresWithSurrounding();
private:
void checkArea(const QString &base,
int xCenter, int yCenter, int width, int height,
const QStringList &coords);
};
+203
View File
@@ -0,0 +1,203 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include "lts_coord_widget.h"
#include "lts_coord_widget_p.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <QToolButton>
namespace LtsCoordWidget
{
LtsCoordWidget::LtsCoordWidget(DisplayType dt, QWidget *parent)
: QWidget(parent)
, m_displayType(dt)
{
setFocusPolicy(Qt::StrongFocus);
rebuildUi();
}
void LtsCoordWidget::setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue)
{
if (m_coordinate != geoCoord)
emit coordinateChanged(geoCoord);
if (bUseAsDefaultValue)
m_defaultCoordinate = geoCoord;
if (m_inputWidgetHandler)
m_inputWidgetHandler->setCoordinate(geoCoord);
}
void LtsCoordWidget::clear()
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->clear();
}
void LtsCoordWidget::setCoordinate(double lon, double lat, bool bUseAsDefaultValue)
{
setCoordinate(QPointF(lon, lat), bUseAsDefaultValue);
}
QPointF LtsCoordWidget::coordinate() const
{
if (!m_inputWidgetHandler)
return QPointF();
return m_inputWidgetHandler->coordinate();
}
LtsCoordWidget::DisplayType LtsCoordWidget::displayType() const
{
return m_displayType;
}
void LtsCoordWidget::setDisplayType(LtsCoordWidget::DisplayType dt)
{
if (m_displayType == dt)
return;
m_displayType = dt;
rebuildUi();
}
bool LtsCoordWidget::withResetButton() const
{
return m_bWithResetButton;
}
void LtsCoordWidget::setWithResetButton(bool bWith)
{
if (m_bWithResetButton == bWith)
return;
m_bWithResetButton = bWith;
if (m_inputWidgetHandler)
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
}
bool LtsCoordWidget::showToolTip() const
{
return m_bShowToolTip;
}
void LtsCoordWidget::setShowToolTip(bool bShow)
{
if (m_bShowToolTip == bShow)
return;
m_bShowToolTip = bShow;
updateToolTip();
}
bool LtsCoordWidget::mgrsSquareEditable() const
{
return m_bMgrsSquareEditable;
}
void LtsCoordWidget::setMgrsSquareEditable(bool bEditable)
{
if (m_bMgrsSquareEditable == bEditable)
return;
m_bMgrsSquareEditable = bEditable;
if (m_inputWidgetHandler)
m_inputWidgetHandler->setSquaresEditable(bEditable);
}
void LtsCoordWidget::setWithSurroundingMgrsSquares(bool bEnable)
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->setWithSurroundingSquare(bEnable);
}
void LtsCoordWidget::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
{
if (m_inputWidgetHandler)
m_inputWidgetHandler->setMgrsSquares(geoCoords);
}
bool LtsCoordWidget::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip)
updateToolTip();
return QWidget::event(event);
}
void LtsCoordWidget::rebuildUi()
{
// MGRS -> ComboBox with allowed MGRS-Squares + x / y IntEdit with 5 digits
// MGRS7 -> ComboBox with allowed MGRS-Squares + x / y IntEdit with 7 digits
// UTM -> ComboBox with all UTM zones + IntEdit with 5 digits --> TODO
// WGS84 -> lon/lat double inputs
if (m_inputWidgetHandler)
{
m_inputWidgetHandler->cleanup();
delete m_inputWidgetHandler;
delete layout();
}
switch (m_displayType)
{
case DisplayType::MGRS5:
m_inputWidgetHandler = new MgrsEdit(5, this);
break;
case DisplayType::MGRS7:
m_inputWidgetHandler = new MgrsEdit(7, this);
break;
case DisplayType::UTM:
//m_inputWidgetHandler = new MgrsEdit(5, this);
qt_assert("DisplayType::UTM not yet implemented", __FILE__, __LINE__);
break;
case DisplayType::WGS84:
m_inputWidgetHandler = new Wgs84Edit(this);
break;
}
if (!m_inputWidgetHandler)
return;
m_inputWidgetHandler->setSquaresEditable(m_bMgrsSquareEditable);
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
connect(m_inputWidgetHandler->resetButton(), &QToolButton::clicked,
this, &LtsCoordWidget::onResetToDefaultCoordinate);
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::coordinateChanged,
this, &LtsCoordWidget::coordinateChanged);
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::invalidCoordinate,
this, &LtsCoordWidget::invalidCoordinate);
if (!m_defaultCoordinate.isNull())
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
}
void LtsCoordWidget::updateToolTip()
{
QString tt;
if (m_bShowToolTip)
{
const QPointF p(coordinate());
if (!p.isNull())
{
const QString mgrs = LtsCoordTrans::mgrsFromGeographic(p.x(), p.y());
const QPointF p2(p * LtsCoordTrans::rad2deg());
tt = tr("MGRS: %1\nlat/lon: %2/%3").arg(mgrs).arg(p2.y(), 0, 'f', 10).arg(p2.x(), 0, 'f', 10);
}
}
setToolTip(tt);
}
void LtsCoordWidget::onResetToDefaultCoordinate()
{
if (!m_inputWidgetHandler)
return;
if (m_defaultCoordinate.isNull())
m_inputWidgetHandler->clear();
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
}
} // namespace LtsCoordWidget
+90
View File
@@ -0,0 +1,90 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#pragma once
#include <LtsCoordTrans/ltscoordwidget_export.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <QWidget>
class QToolButton;
namespace LtsCoordWidget
{
class LtsCoordInputWidgetHandler;
class LTSCOORDWIDGET_EXPORT LtsCoordWidget : public QWidget
{
Q_OBJECT
public:
enum class DisplayType
{
MGRS5,// display coordinate as MGRS with 5 digits (= m)
MGRS7,// display coordinate as MGRS with 7 digits (= mm)
UTM, // display coordinate as UTM
WGS84, // display coordinate as WGS84 (degree)
MGRS = DisplayType::MGRS5 // display coordinate as MGRS (default precision = MGRS5)
};
Q_ENUM(DisplayType)
Q_PROPERTY(DisplayType displayType READ displayType WRITE setDisplayType)
Q_PROPERTY(bool withResetButton READ withResetButton WRITE setWithResetButton)
Q_PROPERTY(bool showToolTip READ showToolTip WRITE setShowToolTip)
Q_PROPERTY(bool mgrsSquareEditable READ mgrsSquareEditable WRITE setMgrsSquareEditable)
/**
* @brief Create a new LtsCoordWidget with default display type (= MGRS)
*/
explicit LtsCoordWidget(QWidget *parent = nullptr)
: LtsCoordWidget(LtsCoordWidget::DisplayType::MGRS, parent)
{}
/**
* @brief Create a new LtsCoordWidget with given display type
*/
explicit LtsCoordWidget(DisplayType dt, QWidget *parent = nullptr);
QPointF coordinate() const;
DisplayType displayType() const;
bool withResetButton() const;
bool showToolTip() const;
bool mgrsSquareEditable() const;
bool event(QEvent *event) override;
public Q_SLOTS:
/**
* @brief Set the current visible coordinate
* @param geoCoord the coordinate to display (in wgs84, radian)
* @param bUseAsDefaultValue if true, the reset button will be visible and reset to the given value if pressed
*/
void setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue = true);
void setCoordinate(double lon, double lat, bool bUseAsDefaultValue = true);
void setDisplayType(LtsCoordWidget::DisplayType dt);
void setWithResetButton(bool bWith);
void setShowToolTip(bool bShow);
void setMgrsSquareEditable(bool bEditable);
void setWithSurroundingMgrsSquares(bool bEnable);
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords);
void clear();
Q_SIGNALS:
void coordinateChanged(const QPointF &newGeoCoord);
void invalidCoordinate();
private:
void rebuildUi();
void updateToolTip();
private Q_SLOTS:
void onResetToDefaultCoordinate();
private:
bool m_bWithResetButton = false;
bool m_bShowToolTip = true;
bool m_bMgrsSquareEditable = false;
LtsCoordWidget::DisplayType m_displayType = LtsCoordWidget::DisplayType::MGRS;
LtsCoordInputWidgetHandler *m_inputWidgetHandler = nullptr;
QPointF m_coordinate;
QPointF m_defaultCoordinate;
};
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Mgrs</class>
<widget class="QWidget" name="Mgrs">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>322</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="cbxZone"/>
</item>
<item>
<widget class="QLineEdit" name="leEast">
<property name="toolTip">
<string>Pos X</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leNorth">
<property name="toolTip">
<string>Pos Y</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbReset">
<property name="icon">
<iconset>
<normaloff>:/style/arrow_back.svg</normaloff>:/style/arrow_back.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+332
View File
@@ -0,0 +1,332 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include "lts_coord_widget_p.h"
namespace LtsCoordWidget
{
//
// LtsCoordInputWidgetHandler
//
void LtsCoordInputWidgetHandler::onCoordinateDataChanged()
{
const QPointF coord(coordinate());
if (coord.isNull())
{
emit invalidCoordinate();
return;
}
emit coordinateChanged(coord);
}
//
// MgrsEdit
//
MgrsEdit::MgrsEdit(int precision, QWidget *widget)
: LtsCoordInputWidgetHandler(widget)
, m_precision(precision)
, m_ui(new Ui::Mgrs)
{
m_ui->setupUi(widget);
if (m_precision < 0)
m_precision = 0;
if (m_precision > 11)
m_precision = 11;
const int upperLimit = pow(10, m_precision) - 1;
m_ui->leEast->setValidator(new QIntValidator(0, upperLimit, m_ui->leEast));
m_ui->leNorth->setValidator(new QIntValidator(0, upperLimit, m_ui->leNorth));
setDefaultMgrsSquares();
connect(m_ui->cbxZone, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
this, &MgrsEdit::onCoordinateDataChanged);
connect(m_ui->leEast, &QLineEdit::editingFinished,
this, &MgrsEdit::onCoordinateDataChanged);
connect(m_ui->leNorth, &QLineEdit::editingFinished,
this, &MgrsEdit::onCoordinateDataChanged);
}
MgrsEdit::~MgrsEdit()
{
delete m_ui;
}
bool MgrsEdit::isValid()
{
// check number of digits
const QString &east = m_ui->leEast->text();
if (east.size() != m_precision)
return false;
const QString &north = m_ui->leNorth->text();
if (north.size() != m_precision)
return false;
if (m_ui->cbxZone->currentIndex() < 0)
return false;
if (m_ui->cbxZone->currentText().isEmpty())
return false;
return true;
}
void MgrsEdit::setCoordinate(const QPointF &geoCoord)
{
const QString mgrsString = LtsCoordTrans::mgrsFromGeographic(geoCoord.x(), geoCoord.y(), m_precision, false);
if (mgrsString.isEmpty())
{
m_ui->leEast->clear();
m_ui->leNorth->clear();
return;
}
// prevent signal coordinateChanged()
m_ui->leEast->setText(QString());
m_ui->leNorth->setText(QString());
// split up
const QString mgrsSquare = mgrsString.left(3) + QLatin1Char(' ') + mgrsString.mid(3, 2);
QString east = mgrsString.mid(5, m_precision);
QString north = mgrsString.mid(5 + m_precision, m_precision);
int idx = m_ui->cbxZone->findText(mgrsSquare);
if (idx < 0)
{
addMgrsSquare(mgrsSquare);
idx = m_ui->cbxZone->findText(mgrsSquare);
}
m_ui->cbxZone->setCurrentIndex(idx);
m_ui->leEast->setText(east);
m_ui->leNorth->setText(north);
}
static inline QString getMgrsCoord(QLineEdit *le, int prec)
{
QString ret = le->text();
if (ret.isEmpty())
return ret;
while (ret.size() < prec)
ret += QLatin1Char('0');
le->setText(ret);
return ret;
}
QPointF MgrsEdit::coordinate() const
{
const QString east = getMgrsCoord(m_ui->leEast, m_precision);
const QString north = getMgrsCoord(m_ui->leNorth, m_precision);
if (east.isEmpty() || north.isEmpty() || m_ui->cbxZone->currentIndex() < 0)
return QPointF();
const QString mgrsString(m_ui->cbxZone->currentText() + east + north);
return LtsCoordTrans::geographicFromMgrs(mgrsString);
}
QToolButton *MgrsEdit::resetButton()
{
return m_ui->tbReset;
}
void MgrsEdit::cleanup()
{
// we need to cleanup here because the ownership was transfered to parent widget
// within m_ui->setupUi(parent)
delete m_ui->horizontalLayout;
delete m_ui->cbxZone;
delete m_ui->leEast;
delete m_ui->leNorth;
delete m_ui->tbReset;
}
void MgrsEdit::clear()
{
m_ui->cbxZone->clear();
m_ui->leEast->clear();
m_ui->leNorth->clear();
setDefaultMgrsSquares();
}
void MgrsEdit::setSquaresEditable(bool bEditable)
{
m_ui->cbxZone->setEditable(bEditable);
}
void MgrsEdit::setWithSurroundingSquare(bool bEnabled)
{
m_withMgrsSquareOffset = bEnabled;
}
static QStringList defaultMgrsSquares()
{
const QVector<LtsCoordTrans::UtmCoordinate> squares(LtsCoordTrans::defaultMgrsSquares());
QStringList sl;
sl.reserve(squares.size());
for (const LtsCoordTrans::UtmCoordinate &c : squares)
sl += c.mgrs(0);
std::sort(sl.begin(), sl.end());
return sl;
}
static QString preferedMgrsSquare()
{
const LtsCoordTrans::UtmCoordinate c(LtsCoordTrans::preferedMgrsSquare());
return c.mgrs(0); // should match one of the defaultMgrsSquares :-)
}
void MgrsEdit::setDefaultMgrsSquares()
{
m_ui->cbxZone->addItems(defaultMgrsSquares());
m_ui->cbxZone->setCurrentIndex(m_ui->cbxZone->findText(preferedMgrsSquare()));
}
void MgrsEdit::addMgrsSquare(const QString &newMgrsSquare)
{
QStringList sl = defaultMgrsSquares();
sl += newMgrsSquare;
if (m_withMgrsSquareOffset)
{
QStringList squares = utmCoordsToList(LtsCoordTrans::defaultMgrsSquareWithOffsets());
// if square not already in list add it.
for (const auto &sq : qAsConst(squares))
if (!sl.contains(sq))
sl.append(sq);
}
std::sort(sl.begin(), sl.end());
m_ui->cbxZone->clear();
m_ui->cbxZone->addItems(sl);
}
QStringList MgrsEdit::utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords)
{
QStringList list;
list.reserve(coords.count());
for (const auto &utm : coords)
list.append(utm.mgrs(0));
return list;
}
void MgrsEdit::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
{
QStringList sl = utmCoordsToList(geoCoords);
if (sl.isEmpty())
return;
std::sort(sl.begin(), sl.end());
m_ui->cbxZone->clear();
m_ui->cbxZone->addItems(sl);
}
//
// Wgs84Edit
//
Wgs84Edit::Wgs84Edit(QWidget *widget)
: LtsCoordInputWidgetHandler(widget)
, m_ui(new Ui::Wgs84)
{
m_ui->setupUi(widget);
DoubleValidator *v = new DoubleValidator(-180, 180, 9, m_ui->leLon);
v->setNotation(QDoubleValidator::StandardNotation);
m_ui->leLon->setValidator(v);
v = new DoubleValidator(-180, 180, 9, m_ui->leLat);
v->setNotation(QDoubleValidator::StandardNotation);
m_ui->leLat->setValidator(v);
connect(m_ui->leLon, &QLineEdit::editingFinished,
this, &Wgs84Edit::onCoordinateDataChanged);
connect(m_ui->leLat, &QLineEdit::editingFinished,
this, &Wgs84Edit::onCoordinateDataChanged);
}
Wgs84Edit::~Wgs84Edit()
{
delete m_ui;
}
bool Wgs84Edit::isValid()
{
// not tested yet:
return (!m_ui->leLon->text().isEmpty()) && (!m_ui->leLat->text().isEmpty());
}
void Wgs84Edit::setCoordinate(const QPointF &geoCoord)
{
const QPointF coord(geoCoord * LtsCoordTrans::rad2deg());
const QLocale l;
m_ui->leLon->setText(l.toString(coord.x(), 'f', 9));
m_ui->leLat->setText(l.toString(coord.y(), 'f', 9));
}
QPointF Wgs84Edit::coordinate() const
{
QPointF ret;
const QLocale l; // current default locale
// remove default group separator because QDoubleValidator accepts them everywhere...
// maybe we need to derive from QDoubleValidator to prevent this behavior
const QString lonStr = m_ui->leLon->text().remove(l.groupSeparator());
const QString latStr = m_ui->leLat->text().remove(l.groupSeparator());
if (lonStr.isEmpty() || latStr.isEmpty())
return ret;
ret.rx() = l.toDouble(lonStr);
ret.ry() = l.toDouble(latStr);
return ret * LtsCoordTrans::deg2rad();
}
QToolButton *Wgs84Edit::resetButton()
{
return m_ui->tbReset;
}
void Wgs84Edit::cleanup()
{
// we need to cleanup here because the ownership was transfered to parent widget
// within m_ui->setupUi(parent)
delete m_ui->horizontalLayout;
delete m_ui->leLon;
delete m_ui->leLat;
delete m_ui->tbReset;
delete m_ui->lblLon;
delete m_ui->lblLat;
}
void Wgs84Edit::clear()
{
m_ui->leLon->clear();
m_ui->leLat->clear();
}
//
// DoubleValidator
//
DoubleValidator::DoubleValidator(double bottom, double top, int decimals, QObject *parent)
: QDoubleValidator(bottom, top, decimals, parent)
{}
QValidator::State DoubleValidator::validate(QString &input, int &pos) const
{
const QValidator::State ret = QDoubleValidator::validate(input, pos);
if (ret != QValidator::Acceptable)
return ret;
QLocale l;
const QChar gs(l.groupSeparator());
if (gs.isNull())
return ret;
if (input.contains(gs))
return QValidator::Invalid;
return ret;
}
} // namespace LtsCoordWidget
+100
View File
@@ -0,0 +1,100 @@
/**
* @RDE_HEADER@
* An Input Widget for coordinates
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#pragma once
#include <QWidget>
#include <QComboBox>
#include <QVBoxLayout>
#include <LtsCoordTrans/lts_coord_trans.h>
#include "ui_lts_coord_widget_mgrs.h"
#include "ui_lts_coord_widget_wgs84.h"
namespace LtsCoordWidget
{
class LtsCoordInputWidgetHandler : public QObject
{
Q_OBJECT
public:
explicit LtsCoordInputWidgetHandler(QObject *parent)
: QObject(parent)
{}
virtual bool isValid() = 0; // 2024, N.Thoden: has to be tested
virtual void setCoordinate(const QPointF &geoCoord) = 0;
virtual QPointF coordinate() const = 0;
virtual QToolButton *resetButton() = 0;
virtual void cleanup() = 0;
virtual void clear() = 0;
virtual void setSquaresEditable(bool bEditable) = 0;
virtual void setWithSurroundingSquare(bool bEnabled) = 0;
virtual void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) = 0;
protected Q_SLOTS:
void onCoordinateDataChanged();
Q_SIGNALS:
void coordinateChanged(const QPointF &newGeoCoord);
void invalidCoordinate();
};
class MgrsEdit final : public LtsCoordInputWidgetHandler
{
Q_OBJECT
public:
explicit MgrsEdit(int precision, QWidget *widget);
~MgrsEdit();
bool isValid() override;
void setCoordinate(const QPointF &geoCoord) override;
QPointF coordinate() const override;
QToolButton *resetButton() override;
void cleanup() override;
void clear() override;
void setSquaresEditable(bool bEditable) override;
void setWithSurroundingSquare(bool bEnabled) override;
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override;
private:
void setDefaultMgrsSquares();
void addMgrsSquare(const QString &newMgrsSquare);
QStringList utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords);
private:
int m_precision;
bool m_withMgrsSquareOffset=false;
Ui::Mgrs *m_ui;
};
class Wgs84Edit final : public LtsCoordInputWidgetHandler
{
Q_OBJECT
public:
explicit Wgs84Edit(QWidget *widget);
~Wgs84Edit();
bool isValid() override;
void setCoordinate(const QPointF &geoCoord) override;
QPointF coordinate() const override;
QToolButton *resetButton() override;
void cleanup() override;
void clear() override;
void setSquaresEditable(bool bEditable) override { Q_UNUSED(bEditable); }
void setWithSurroundingSquare(bool bEnabled) override { Q_UNUSED(bEnabled); }
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override { Q_UNUSED(geoCoords); }
private:
Ui::Wgs84 *m_ui;
};
class DoubleValidator final : public QDoubleValidator
{
Q_OBJECT
public:
DoubleValidator(double bottom, double top, int decimals, QObject *parent = nullptr);
QValidator::State validate(QString &input, int &pos) const override;
};
}
+122
View File
@@ -0,0 +1,122 @@
/**
* @RDE_HEADER@
* Small test app for ltscoordwidget
* $Id$
* $HeadURL$
* $Author$
*
* Created by Christian Ehrlicher, SLSE, 2016-09-08
*/
#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QVBoxLayout>
#include <QLabel>
#include <LtsCoordTrans/lts_coord_trans.h>
#include "lts_coord_widget.h"
static void updateLabel(const QPointF &coord, QLabel *lbl, int prec)
{
QString mgrs = LtsCoordTrans::mgrsFromGeographic(coord.x(), coord.y(), prec);
QString str = QStringLiteral("mgrs: %1, lon: %2, lat: %3")
.arg(mgrs)
.arg(coord.x() * LtsCoordTrans::rad2deg(), 0, 'f', 10)
.arg(coord.y() * LtsCoordTrans::rad2deg(), 0, 'f', 10);
lbl->setText(str);
}
int main(int argc, char **argv)
{
QApplication app(argc, argv);
LtsCoordTrans::UtmCoordinate base(LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32UMD00")));
QPointF topLeft(base.geographic(+50000, -50000));
QPointF bottomRight(base.geographic(-50000, 50000));
LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), true, true);
QDialog *dialog(new QDialog);
QVBoxLayout *lay(new QVBoxLayout(dialog));
// const QPointF defCoord(QPointF(8.0, 53.0) * LtsCoordTrans::deg2rad());
const QPointF defCoord(QPointF(53.0, 8.0) * LtsCoordTrans::deg2rad());
// MGRS5
LtsCoordWidget::LtsCoordWidget *wMgrs5(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS, dialog));
wMgrs5->setWithResetButton(true);
wMgrs5->setCoordinate(defCoord, true);
lay->addWidget(wMgrs5);
QLabel *lbl5(new QLabel(dialog));
lay->addWidget(lbl5);
QObject::connect(wMgrs5, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
lbl5, [lbl5](const QPointF & coord)
{
updateLabel(coord, lbl5, 5);
});
// MGRS7
LtsCoordWidget::LtsCoordWidget *wMgrs7(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7, dialog));
wMgrs7->setWithResetButton(true);
wMgrs7->setCoordinate(defCoord, true);
lay->addWidget(wMgrs7);
QLabel *lbl7(new QLabel(dialog));
lay->addWidget(lbl7);
QObject::connect(wMgrs7, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
wMgrs7, [lbl7](const QPointF & coord)
{
updateLabel(coord, lbl7, 7);
});
//WGS 84 degree
LtsCoordWidget::LtsCoordWidget *w84(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
w84->setWithResetButton(true);
w84->setCoordinate(defCoord, true);
lay->addWidget(w84);
QLabel *lbl84(new QLabel(dialog));
lay->addWidget(lbl84);
QObject::connect(w84, &LtsCoordWidget::LtsCoordWidget::coordinateChanged,
w84, [lbl84](const QPointF & coord)
{
updateLabel(coord, lbl84, 11);
});
// Test live-change
QHBoxLayout *hLay = new QHBoxLayout;
lay->addLayout(hLay);
LtsCoordWidget::LtsCoordWidget *wChange(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
wChange->setWithResetButton(true);
wChange->setCoordinate(defCoord, true);
hLay->addWidget(wChange);
QPushButton *pbReset(new QPushButton(QStringLiteral("With Reset"), dialog));
hLay->addWidget(pbReset);
QObject::connect(pbReset, &QPushButton::clicked,
wChange, [wChange]() { wChange->setWithResetButton(!wChange->withResetButton());});
QPushButton *pbNext(new QPushButton(QStringLiteral("Next Type"), dialog));
hLay->addWidget(pbNext);
QObject::connect(pbNext, &QPushButton::clicked,
wChange, [wChange]()
{
LtsCoordWidget::LtsCoordWidget::DisplayType dt = wChange->displayType();
switch (dt)
{
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7; break;
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84; break;
case LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84:
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5; break;
default:
break;
}
wChange->setDisplayType(dt);
});
QPushButton *pb(new QPushButton(QStringLiteral("Quit"), dialog));
lay->addWidget(pb);
QObject::connect(pb, &QPushButton::clicked, qApp, QCoreApplication::quit);
dialog->show();
int ret = app.exec();
delete dialog;
return ret;
}
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Wgs84</class>
<widget class="QWidget" name="Wgs84">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>293</width>
<height>32</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="lblLat">
<property name="text">
<string>Lat:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leLat"/>
</item>
<item>
<widget class="QLabel" name="lblLon">
<property name="text">
<string>Lon:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leLon"/>
</item>
<item>
<widget class="QToolButton" name="tbReset">
<property name="icon">
<iconset>
<normaloff>icon:arrow_back.svg</normaloff>icon:arrow_back.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+49
View File
@@ -0,0 +1,49 @@
/**
* Convert a lon/lat coordinate to mgrs
* $Id$
* $HeadURL$
* $Author$
*/
#include <GeographicLib/UTMUPS.hpp>
#include <GeographicLib/MGRS.hpp>
#include <string>
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <mgrs>\n", argv[0]);
return -1;
}
std::string mgrs = argv[1];
mgrs.erase(remove_if(mgrs.begin(), mgrs.end(), isspace), mgrs.end());
int prec;
double lonDeg, latDeg;
try
{
int zone;
bool northp;
double x, y;
GeographicLib::MGRS::Reverse(mgrs, zone, northp, x, y, prec, true);
GeographicLib::UTMUPS::Reverse(zone, northp, x, y, latDeg, lonDeg);
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
}
catch (...)
{
fprintf(stderr, "Error converting %s to Lon/Lat coords\n", mgrs.c_str());
return -2;
}
std::string part1, part2, part3, part4;
part1 = mgrs.substr(0, 3);
part2 = mgrs.substr(3, 2);
part3 = mgrs.substr(5, prec);
part4 = mgrs.substr(5 + prec, prec);
fprintf(stdout, "%s %s %s %s = %13.10f, %13.10f ( %13.10f, %13.10f )\n",
part1.c_str(), part2.c_str(), part3.c_str(), part4.c_str(), lonDeg, latDeg, lonDeg / 180. * M_PI, latDeg / 180. * M_PI);
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
#
# $Id$
# $HeadURL$
# $Author$
#
NGL_ADD_LIBRARY2(WS SV HLA LtsCoordinate SHARED
lts_coordinate.h
lts_coordinate.cpp
lts_coord_vector.h
lts_coord_vector.cpp
)
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordinate
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsCoordinate
PRIVATE
LtsCoordTrans
PUBLIC
ddscore
LtsUtilsCoreHeaderOnly
Qt5::Core
)
ADD_SUBDIRECTORY(UnitTest_Coordinate)
@@ -0,0 +1,30 @@
#
# @RDE_HEADER@
# $Id$
# $HeadURL$
# $Author$
#
SET(UnitTestSources
lts_coordinate_unit_test.cpp
lts_coordinate_unit_test.h
)
IF(BUILD_TESTING)
ADD_EXECUTABLE(commonUnitTestCoordinate ${LinkedSources} ${UnitTestSources} )
TARGET_LINK_LIBRARIES(commonUnitTestCoordinate
NGLLogger
nglcoreapplication
LtsCoordinate
LtsElevation
LtsCoordTrans
situationMapCommon
situationMapElevationMap
Qt5::Core
Qt5::Gui
Qt5::Test)
ADD_TEST(NAME commonUnitTestCoordinate COMMAND $<TARGET_FILE:commonUnitTestCoordinate>)
ENDIF()
@@ -0,0 +1,976 @@
/**
* @RDE_HEADER@
* Unittest
* $Id$
* $HeadURL$
* $Author$
*/
#include "lts_coordinate_unit_test.h"
#include <LtsCoordTrans/lts_coord_trans.h>
#include <LtsCoordinate/lts_coordinate.h>
#include <LtsCoordinate/lts_coord_vector.h>
#include <ElevationMap/elevation_map_manager.h>
#include <nglapplication/nglcoreapplication.h>
#include <NGLLogger/NGLLoggerBootstrap.h>
#include <QtTest/QTest>
Q_DECLARE_METATYPE(LtsUtils::QPoint3D);
#include <iostream>
#include <iomanip>
static const int s_id{4711};
static LtsElevation::LtsElevationManager *s_eleMgr{nullptr};
//------------------------------------------------------------------------------
// class MyLtsElevationManagerBootStrap
//------------------------------------------------------------------------------
// hack: do not use dtcs.
// Use this class to get access to ElevationMapManager::init();
class MyLtsElevationManagerBootstrap : public LtsElevation::LtsElevationManager
{
using BaseClass = LtsElevation::LtsElevationManager;
public:
static LtsElevationManager *createInstanceUnitTest(int id, const QStringList &filePaths_rough_to_fine)
{
return BaseClass::createInstanceUnitTest(id, filePaths_rough_to_fine);
}
};
//------------------------------------------------------------------------------
// class UnitTestCoordinate
//------------------------------------------------------------------------------
UnitTestCoordinate::UnitTestCoordinate()
: m_logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("UnitTestCoordinate"))
{}
void UnitTestCoordinate::initTestCase()
{
// ll /usr/share/arcgis/resources/elevation/
QString filePath(QStringLiteral("/usr/share/arcgis/resources/elevation/Altmark_dt1.tif"));
s_eleMgr = MyLtsElevationManagerBootstrap::createInstanceUnitTest(s_id, {filePath});
const bool bIsValid = s_eleMgr->isValid();
std::cerr << "myBootstrapMgr->isValid() 1 = " << bIsValid << std::endl;
// we have to wait for loading to be finished:
{
s_eleMgr->waitForInitialization();
const bool bIsValid = s_eleMgr->isValid();
std::cerr << "myBootstrapMgr->isValid() 2 = " << bIsValid << std::endl;
}
QVERIFY2(s_eleMgr , "static LtsElevationManager does not exist");
//
// set elevationCbFunc at LtsCoordinate
// using elevationCbFunc = std::function<double(double, double, double, bool*)>;
using namespace std::placeholders;
LtsCoordTrans::LtsCoordinate::elevationCbFunc elevationCB = std::bind(&LtsElevation::LtsElevationManager::elevation,
s_eleMgr, _1, _2, _3, _4);
LtsCoordTrans::LtsCoordinate::setElevationCallback(elevationCB);
QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed");
}
void UnitTestCoordinate::init()
{
QVERIFY2(s_eleMgr, "static LtsElevationManager does not exist");
QVERIFY2(LtsCoordTrans::LtsCoordinate::hasElevationCallback() , "setElevationCallback failed");
}
void UnitTestCoordinate::test_NaN()
{
bool equal = qFuzzyCompare(std::nan(""), std::nan(""));
NOTICE_LOG(m_logger) << " qFuzzyCompare(NaN, NaN) = " << equal;
// surprise: equal is false:
QVERIFY2(equal == false, "qFuzzyCompare(NaN, NaN) failed");
}
void UnitTestCoordinate::test_coordinate_simple1()
{
LtsCoordTrans::LtsCoordinate ltsCoord;
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed");
QVERIFY2(std::isnan( geoPos.z() ) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtmPointF();
const QPointF &utm2 = ltsCoord.toUtm<QPointF>();
const LtsCoordTrans::UtmCoordinate3D &utm3D = ltsCoord.toUtmCoordinate3D();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm2;
NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utm3D;
QVERIFY2(qFuzzyCompare(utm3D.x, 166021.44308053952), "data no equal");
QVERIFY2(qFuzzyCompare(utm3D.y, 0.0 ), "data no equal");
QVERIFY2( utm3D.zone == 31 , "data no equal"); // 0.0, 0.0 -> Greenwhich -> zone 31
QVERIFY2(utm == utm2 , "data no equal");
const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2;
QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed");
QVERIFY2(std::isnan( geoPos2.z() ) , "geoPosition2 failed");
}
void UnitTestCoordinate::test_coordinate_simple2()
{
Core::GeoPosition defaultGeoPos;
LtsCoordTrans::LtsCoordinate ltsCoord(defaultGeoPos);
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyIsNull(geoPos.lon()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.lat()) , "geoPosition failed");
QVERIFY2(qFuzzyIsNull(geoPos.z() ) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
// ist geoPosition NACH .toUtm() unverändert?
const Core::GeoPosition &geoPos2 = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition2 = " << geoPos2;
QVERIFY2(qFuzzyIsNull(geoPos2.lon()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.lat()) , "geoPosition2 failed");
QVERIFY2(qFuzzyIsNull(geoPos2.z() ) , "geoPosition2 failed");
}
void UnitTestCoordinate::test_coordinate_simple3()
{
static const Core::GeoPosition s_invalidPos(std::nan(""), std::nan(""), std::nan(""));
// check NaN
LtsCoordTrans::LtsCoordinate ltsCoord(s_invalidPos);
QVERIFY2(ltsCoord.isValid() == false, "isValid failed");
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(std::isnan(geoPos.lon()) , "geoPosition failed");
QVERIFY2(std::isnan(geoPos.lat()) , "geoPosition failed");
QVERIFY2(std::isnan(geoPos.z()) , "geoPosition failed");
const QPointF &utm = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmCoordinateAsPointF = " << utm;
QVERIFY2(utm.isNull() , "utm failed");
}
void UnitTestCoordinate::test_coordinate_simple4()
{
LtsCoordTrans::LtsCoordinate::resetConversionCounter();
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed");
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const double zValue = 2.3;
const LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); // explicit zValue
// How often the expensive conversion to utm takes place?
const LtsCoordTrans::UtmCoordinate &utmCoord_FirstTry = ltsCoord.utmCoordinate();
Q_UNUSED(utmCoord_FirstTry);
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
const LtsCoordTrans::UtmCoordinate &utmCoord_SecondTry = ltsCoord.utmCoordinate();
Q_UNUSED(utmCoord_SecondTry);
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
}
void UnitTestCoordinate::test_coordinate_fromUtm()
{
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const double zValue = 2.3;
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
// utm -> geo -> ltsCoord
LtsCoordTrans::LtsCoordinate ltsCoord(utmIn.geographic(), zValue); // explicit zValue
const LtsCoordTrans::UtmCoordinate &utmCoord = ltsCoord.utmCoordinate();
NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord;
NOTICE_LOG(m_logger) << " utmCoord = " << utmCoord.mgrs();
QVERIFY2(utmCoord.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed");
// ltsCoord back to geoPos
const Core::GeoPosition &geoPos = ltsCoord.geoPosition();
NOTICE_LOG(m_logger) << " geoPosition = " << geoPos;
QVERIFY2(qFuzzyCompare(geoPos.lon(), 0.200706392814992), "geoPosition failed");
QVERIFY2(qFuzzyCompare(geoPos.lat(), 0.914792952478694), "geoPosition failed");
QVERIFY2(qFuzzyCompare(geoPos.z() , zValue ) , "geoPosition failed");
// ltsCoord back to utm
const QPointF &utmP2D = ltsCoord.toUtm<QPointF>();
NOTICE_LOG(m_logger) << " utmP2D = " << utmP2D;
QVERIFY2(qFuzzyCompare(utmP2D.x(), 670000.5), "utm1 failed");
QVERIFY2(qFuzzyCompare(utmP2D.y(), 5810000.5), "utm2 failed");
const LtsUtils::QPoint3D &utmP3D = ltsCoord.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.x(), 670000.5), "utm1 failed");
QVERIFY2(qFuzzyCompare(utmP3D.y(), 5810000.5), "utm2 failed");
QVERIFY2(qFuzzyCompare(utmP3D.z() , zValue ), "utm3 failed");
const LtsCoordTrans::UtmCoordinate3D &utmCoord3D = ltsCoord.toUtmCoordinate3D();
NOTICE_LOG(m_logger) << " UtmCoordinate3D = " << utmCoord3D;
QVERIFY2(qFuzzyCompare(utmCoord3D.x, 670000.5), "utmCoord3D failed");
QVERIFY2(qFuzzyCompare(utmCoord3D.y, 5810000.5), "utmCoord3D failed");
QVERIFY2(qFuzzyCompare(utmCoord3D.z(), zValue ), "utmCoord3D failed");
// check downcast:
const LtsCoordTrans::UtmCoordinate &utmCoord2D = utmCoord3D;
NOTICE_LOG(m_logger) << " utmCoord2D = " << utmCoord2D;
QVERIFY2(qFuzzyCompare(utmCoord2D.x, 670000.5), "utmCoord2D failed");
QVERIFY2(qFuzzyCompare(utmCoord2D.y, 5810000.5), "utmCoord2D failed");
// to mgrs
const LtsCoordTrans::UtmCoordinate &utm = ltsCoord.utmCoordinate();
QVERIFY2(utm.mgrs() == QStringLiteral("32U PD 70000 10000") , "utmCoordinate failed");
}
void UnitTestCoordinate::test_coordinate_fromUtm2()
{
const LtsCoordTrans::UtmCoordinate &utmInZone32 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 10000 00000"));
const double zValue = 2.3;
NOTICE_LOG(m_logger) << " utmInZone32 = " << utmInZone32;
const QPointF &utmInDefQPointF = utmInZone32.geographic();
const int zone31 = 31;
const LtsCoordTrans::UtmCoordinate &utmInZone31 = LtsCoordTrans::UtmCoordinate::fromGeographic(utmInDefQPointF, zone31);
NOTICE_LOG(m_logger) << " utmInZone31 = " << utmInZone31;
NOTICE_LOG(m_logger) << " utmInZone31.geographic() = " << utmInZone31.geographic();
NOTICE_LOG(m_logger) << " utmInZone32.geographic() = " << utmInZone32.geographic();
QVERIFY2(utmInZone31.geographic() == utmInZone32.geographic(), "geographic failed");
{
// From different zones via geographic
LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic(), zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist1 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
{
// From different zones via geographic
LtsCoordTrans::LtsCoordinate ltsFromUtm31(utmInZone31.geographic() , zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(1, 0), zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist2 = " << dist;
QVERIFY2(fabs(dist-1) < 0.00001, "distance3D"); // -5.82077e-11
}
{
// From different zones via fromUtmCoordinate
// From different zones via fromUtmCoordinate
// From different zones via fromUtmCoordinate
LtsCoordTrans::LtsCoordinate ltsFromUtm31 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone31, zValue); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm32 = LtsCoordTrans::LtsCoordinate::fromUtmCoordinate(utmInZone32, zValue); // explicit zValue
double dist = ltsFromUtm31.distanceUtm3D(ltsFromUtm32);
NOTICE_LOG(m_logger) << " dist3 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
{
LtsCoordTrans::LtsCoordinate ltsFromUtm32(utmInZone32.geographic(), zValue); // explicit zValue
const QPointF &utmP2D_def = ltsFromUtm32.toUtm<QPointF>();
const QPointF &utmP2D_31 = ltsFromUtm32.toUtm<QPointF>(31);
NOTICE_LOG(m_logger) << " utmP2D_def = " << utmP2D_def;
NOTICE_LOG(m_logger) << " utmP2D_31 = " << utmP2D_31;
LtsCoordTrans::LtsCoordinate lts1 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_def, 32, 0.0);
LtsCoordTrans::LtsCoordinate lts2 = LtsCoordTrans::LtsCoordinate::fromUtm(utmP2D_31, 31, 0.0);
NOTICE_LOG(m_logger) << " lts1 = " << lts1;
NOTICE_LOG(m_logger) << " lts2 = " << lts2;
double dist = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist4 = " << dist;
QVERIFY2(qFuzzyCompare(dist, 0), "distance3D");
}
}
void UnitTestCoordinate::test_coordinate_elevationAt()
{
const double zValue = 1.23;
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), zValue); // explicit zValue
NOTICE_LOG(m_logger) << " ltsFromUtm1 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed");
// Note: elevationAt will NOT modify ltsFromUtm !!!
const double ele = ltsFromUtm.elevationAt();
QVERIFY2(qFuzzyCompare(ele, 75), "elevationAt failed");
NOTICE_LOG(m_logger) << " ltsFromUtm2 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), zValue) , "geoPosition failed");
// If you like to modify ltsFromUtm: use setZValueAccordingElevation!!!
ltsFromUtm.setZValueAccordingElevation();
NOTICE_LOG(m_logger) << " ltsFromUtm3 = " << ltsFromUtm;
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), 75) , "setZValueAccordingElevation failed");
// QPoint3D
const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D_a = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.z(), 75 ) , "utmCoordinateAsPoint3D failed");
{
// try static:
const double ele3 = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm);
NOTICE_LOG(m_logger) << " ele3 = " << ele3;
// has to be identical to ele
QVERIFY2(qFuzzyCompare(ele, ele3 ), "LtsCoordinate::elevationAt failed");
}
//
// _change_ ltsFromUtm to reset the cached elevation:
// LtsCoordinate::clearElevation()
//
// move some meters
LtsCoordTrans::UtmCoordinate utmMoved = ltsFromUtm.utmCoordinate();
utmMoved.x += 11;
utmMoved.y += 22;
const int zValueNew = 123;
ltsFromUtm = LtsCoordTrans::LtsCoordinate(utmMoved.geographic(), zValueNew);
NOTICE_LOG(m_logger) << " ltsFromUtm4 = " << ltsFromUtm;
// call elevationAt a 2nd time:
const double ele2 = ltsFromUtm.elevationAt();
NOTICE_LOG(m_logger) << " ltsFromUtm5 = " << ltsFromUtm;
QVERIFY2(ele2 != 75 , "setZValueAccordingElevation failed");
}
void UnitTestCoordinate::test_coordinate_elevationAt_isCovered()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U MD 10000 10000"));
LtsCoordTrans::LtsCoordinate ltsFromUtm1(utmIn1.geographic(), 11); // explicit zValue
LtsCoordTrans::LtsCoordinate ltsFromUtm2(utmIn2.geographic(), 11); // explicit zValue
{
bool bIsCovered = false;
double ele = ltsFromUtm1.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt1a failed");
// retrieve 2nd:
bIsCovered = false;
ele = ltsFromUtm1.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt1b failed");
// static version:
bIsCovered = false;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt2a failed");
// retrieve 2nd:
bIsCovered = false;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm1, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == true , "elevationAt2b failed");
}
{
bool bIsCovered = true;
double ele = ltsFromUtm2.elevationAt(-123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == false , "elevationAt2 failed");
// static version:
bIsCovered = true;
ele = LtsCoordTrans::LtsCoordinate::elevationAt(ltsFromUtm2, -123, &bIsCovered);
Q_UNUSED(ele);
QVERIFY2(bIsCovered == false , "elevationAt1 failed");
}
}
void UnitTestCoordinate::test_coordinate_elevation_data()
{
QTest::addColumn<QString>("mgrs");
QTest::addColumn<double>("elevationIn");
QTest::addColumn<double>("elevationRef");
// Altmark_dt1.tif:
QTest::newRow("elevation 10 ") << QStringLiteral("32 U PD 70000 10000") << std::nan("") << 75.0;
QTest::newRow("elevation 11 ") << QStringLiteral("32 U PD 70000 10000") << 0.0 << 75.0;
QTest::newRow("elevation 20 ") << QStringLiteral("32 U PD 72000 13000") << std::nan("") << 80.0;
QTest::newRow("elevation 21 ") << QStringLiteral("32 U PD 72000 13000") << 0.340 << 80.0;
}
void UnitTestCoordinate::test_coordinate_elevation()
{
QFETCH(QString, mgrs);
QFETCH(double, elevationIn);
QFETCH(double, elevationRef);
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
NOTICE_LOG(m_logger) << " utmIn = " << utmIn;
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic(), elevationIn); // explicit zValue
NOTICE_LOG(m_logger) << " ltsFromUtm = " << ltsFromUtm;
// before
NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition();
ltsFromUtm.replaceZValueByElevationIfValue(elevationIn);
// after
NOTICE_LOG(m_logger) << " geoPosition = " << ltsFromUtm.geoPosition();
QVERIFY2(qFuzzyCompare(ltsFromUtm.geoPosition().z(), elevationRef), "elevation failed");
// QPoint3D
const LtsUtils::QPoint3D &utmP3D = ltsFromUtm.toUtm<LtsUtils::QPoint3D>();
NOTICE_LOG(m_logger) << " utmP3D = " << utmP3D;
QVERIFY2(qFuzzyCompare(utmP3D.z(), elevationRef) , "utmCoordinateAsPoint3D failed");
}
void UnitTestCoordinate::test_coordinate_mgrs()
{
const LtsCoordTrans::UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
NOTICE_LOG(m_logger) << " utmIn = " << utmIn.mgrs();
LtsCoordTrans::LtsCoordinate ltsFromUtm(utmIn.geographic());
{
const QString asMgrs = ltsFromUtm.mgrs();
NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs;
QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000")) == 0, "UtmCoordinate::mgrs failed");
}
{
const QString asMgrs = ltsFromUtm.mgrsWithZ();
NOTICE_LOG(m_logger) << " asMgrs = " << asMgrs;
QVERIFY2(asMgrs.compare(QStringLiteral("32U PD 70000 10000 z=75.00")) == 0, "UtmCoordinate::mgrs failed");
}
{
const LtsCoordTrans::LtsCoordinate coord(0.200706392814992, 0.914792952478694, 123);
// 1st: write just the already available member....
// Note: m_utm2D is '0'
const QString str1 = coord.toString();
NOTICE_LOG(m_logger) << " str1 cheap = " << str1;
QVERIFY2(str1.compare(QStringLiteral("LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 0, m_utm2D.y 0, m_utm2D.zone -1, elevation nan)")) == 0, "toString failed");
// 2nd: write more of less complete set of member
// means:
// a) calculate utmCoordinateInternal(); // ensure m_utm2D
// b) calculate elevationAt(); // ensure m_elevation
const bool fullAndExpensive = true;
const QString str2 = coord.toString(fullAndExpensive);
NOTICE_LOG(m_logger) << " str2 expensive = " << str2;
QVERIFY2(str2.compare(QStringLiteral("LtsCoordinate(m_geo=[[lon: 0.200706392814992, lat: 0.914792952478694]z: 123], m_utm2D.x 670000.499999998, m_utm2D.y 5810000.5, m_utm2D.zone 32, m_utm2D.mgrs '32U PD 70000 10000', elevation 75.000)")) == 0, "toString failed");
}
}
void UnitTestCoordinate::test_coordinate_distance()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70010 10100"));
NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1;
NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2;
QVERIFY2(qFuzzyCompare((utmIn2.x - utmIn1.x), 10) , "coord failed");
QVERIFY2(qFuzzyCompare((utmIn2.y - utmIn1.y), 100) , "coord failed");
const LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 11); // explicit zValue
const LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 111); // explicit zValue
{
double dist2D = lts1.distanceUtm2D(lts2);
NOTICE_LOG(m_logger) << " dist2D = " << dist2D;
QVERIFY2(std::fabs(dist2D-100.499) < 0.01, "distanceUtm2D failed");
}
{
double dist3D = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist3D = " << dist3D;
QVERIFY2(std::fabs(dist3D-141.774) < 0.01, "distanceUtm3D failed");
}
}
void UnitTestCoordinate::test_coordinate_distanceDiffZones()
{
const LtsCoordTrans::UtmCoordinate &utmIn1 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("31 U FR 99999 00000"));
const LtsCoordTrans::UtmCoordinate &utmIn2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LA 00000 00000"));
NOTICE_LOG(m_logger) << " XXutmIn1 = " << utmIn1;
NOTICE_LOG(m_logger) << " XXutmIn2 = " << utmIn2;
// LtsCoordTrans::LtsCoordinate lts1(utmIn1, 0);
// LtsCoordTrans::LtsCoordinate lts2(utmIn2, 0);
LtsCoordTrans::LtsCoordinate lts1(utmIn1.geographic(), 0);
LtsCoordTrans::LtsCoordinate lts2(utmIn2.geographic(), 0);
NOTICE_LOG(m_logger) << " lts1 = " << lts1;
NOTICE_LOG(m_logger) << " lts2 = " << lts2;
double dist = lts1.distanceUtm3D(lts2);
NOTICE_LOG(m_logger) << " dist = " << dist;
QVERIFY2(std::fabs(dist-33382.7) < 0.1, "distance3D failed");
}
void UnitTestCoordinate::test_squareInDifferentZone()
{
const LtsCoordTrans::UtmCoordinate &utm_bl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::UtmCoordinate &utm_br = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000"));
const LtsCoordTrans::UtmCoordinate &utm_tl = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000"));
const LtsCoordTrans::UtmCoordinate &utm_tr = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 11000"));
const LtsCoordTrans::LtsCoordinate lts_bl(utm_bl.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_br(utm_br.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_tl(utm_tl.geographic(), 0);
const LtsCoordTrans::LtsCoordinate lts_tr(utm_tr.geographic(), 0);
{
// check square and diagonal:
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_br), 1000.0), "distance3D 1 failed");
QVERIFY2(qFuzzyCompare(lts_tl.distanceUtm2D(lts_tr), 1000.0), "distance3D 2 failed");
QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tr), 1000.0), "distance3D 3 failed");
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tl), 1000.0), "distance3D 4 failed");
// check diagonal:
QVERIFY2(qFuzzyCompare(lts_bl.distanceUtm2D(lts_tr), 1414.213562373177), "distance3D 5 failed");
QVERIFY2(qFuzzyCompare(lts_br.distanceUtm2D(lts_tl), 1414.213562373177), "distance3D 6 failed");
}
// old approach:
// calculate in a different utm zone:
{
const LtsCoordTrans::UtmCoordinate &utm31_bl = LtsCoordTrans::UtmCoordinate(31, utm_bl.x, utm_bl.y);
const LtsCoordTrans::UtmCoordinate &utm31_br = LtsCoordTrans::UtmCoordinate(31, utm_br.x, utm_br.y);
const LtsCoordTrans::UtmCoordinate &utm31_tl = LtsCoordTrans::UtmCoordinate(31, utm_tl.x, utm_tl.y);
const LtsCoordTrans::UtmCoordinate &utm31_tr = LtsCoordTrans::UtmCoordinate(31, utm_tr.x, utm_tr.y);
// check square and diagonal:
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_br), 1000.0), "LtsCoordTrans::distance 1 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_tl, utm31_tr), 1000.0), "LtsCoordTrans::distance 2 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tr), 1000.0), "LtsCoordTrans::distance 3 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tl), 1000.0), "LtsCoordTrans::distance 4 failed");
// check diagonal:
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_bl, utm31_tr), 1414.213562373095), "LtsCoordTrans::distance 5 failed");
QVERIFY2(qFuzzyCompare(LtsCoordTrans::distance(utm31_br, utm31_tl), 1414.213562373095), "LtsCoordTrans::distance 6 failed");
}
// Fazit:
// Für das in Zone 32 erstellte Quadrat wird für beide Diagonalen ein identischer Abstand ermittelt.
// Wird die Berechnung in einer anderen Zone (hier 31) durchgeführt, gilt dasselbe.
// Der absolute Werte differiert nur marginal und dürfte eher auf Rechenungenauigkeiten zurückzuführen sein.
// Zone 32: 1414.213562373177
// Zone 32: 1414.213562373095
// ^
}
void UnitTestCoordinate::test_coordinate_angle()
{
// MGRS: 32 U PD EAST NORTH
// MGRS: 32 U PD X Y
const LtsCoordTrans::UtmCoordinate &utm_ul = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordTrans::LtsCoordinate lts_ul(utm_ul.geographic(), 0);
{
// 0 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 11000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 0.0), "angleUtm2D 1 failed");
}
{
// 3 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 10000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 90.0), "angleUtm2D 1 failed");
}
{
// 6 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 09000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 180.0), "angleUtm2D 1 failed");
}
{
// 9 Uhr
const LtsCoordTrans::UtmCoordinate &utm_2 = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 69000 10000"));
const LtsCoordTrans::LtsCoordinate lts_2(utm_2.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_2);
QVERIFY2(qFuzzyCompare(angle, 270.0), "angleUtm2D 1 failed");
}
// ~1 Uhr
{
const LtsCoordTrans::UtmCoordinate &utm_or = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 71000 12000"));
const LtsCoordTrans::LtsCoordinate lts_or(utm_or.geographic(), 0);
double angle = LtsCoordTrans::LtsCoordinate::angleDegreeUtm2D(lts_ul, lts_or);
// NOTICE_LOG(m_logger) << " angle = " << std::setprecision(20) << angle;
QVERIFY2(qFuzzyCompare(angle, 26.565051177056659526), "angleUtm2D 1 failed");
QVERIFY2((angle-26.5651 < 0.001), "angleUtm2D 1 failed");
}
}
void UnitTestCoordinate::test_coordinate_cont()
{
std::vector<Core::GeoPosition> stdVec;
stdVec.push_back(Core::GeoPosition(0.1, 0.11, 1));
stdVec.push_back(Core::GeoPosition(0.2, 0.22, 2));
stdVec.push_back(Core::GeoPosition(0.3, 0.33, 3));
const QVector<LtsCoordTrans::LtsCoordinate> &newCont = LtsCoordTrans::convert<QVector<LtsCoordTrans::LtsCoordinate>>(stdVec);
NOTICE_LOG(m_logger) << " newCont.size() = " << newCont.size();
QVERIFY2(stdVec.size() == static_cast<size_t>(newCont.size()), "convert failed");
}
using namespace LtsCoordTrans;
class ParticpantPositionStore
{
public:
ParticpantPositionStore() = default;
~ParticpantPositionStore() = default;
void setGeoPosition(const Core::GeoPosition &newGeoPos)
{
m_ltsCoord = LtsCoordinate(newGeoPos);
m_ltsCoord_NoImplicitCopy.updateGeoPosition(newGeoPos);
}
// simple, but coord ist copied
// Any calls to methods which internal carry out
// the (expensive) conversions like utmCoordinate(), elevationAt() are executed at the copy!
// The ParticpantPositionStore itself does _not_ benefit!
LtsCoordinate getLtsCoord_copy()
{
return m_ltsCoord;
}
// return by const reference:
// by doing: 'Class(const Class &) = delete;'
const LtsCoordinateNoImplicitCopy& getLtsCoord_noCopy() // return just a const ref of internal data. No copying! Fine :-)
{
return m_ltsCoord_NoImplicitCopy;
}
// compiler error because copying is requested
// LtsCoordinateNoImplicitCopy getLtsCoord_noCopy()
// {
// return m_ltsCoord_NoImplicitCopy; // error: no matching function for call to ‘LtsCoordTrans::LtsCoordinateNoImplicitCopy::LtsCoordinateNoImplicitCopy(LtsCoordTrans::LtsCoordinateNoImplicitCopy&)’
// }
LtsCoordinate m_ltsCoord;
LtsCoordinateNoImplicitCopy m_ltsCoord_NoImplicitCopy;
};
void UnitTestCoordinate::test_coordinate_avoid_copy()
{
LtsCoordTrans::LtsCoordinate::resetConversionCounter();
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 0, "getConversionCounter failed");
ParticpantPositionStore store;
//
// WRITE: set data
//
const UtmCoordinate &utmIn = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U PD 70000 10000"));
const LtsCoordinate ltsIn(utmIn.geographic(), 11);
store.getLtsCoord_noCopy().updateGeoPosition(ltsIn.geoPosition());
//
// READ
//
// advise:
// get data (and benefit from cache)
const LtsCoordinateNoImplicitCopy &justReference = store.getLtsCoord_noCopy();
NOTICE_LOG(m_logger) << " justReference = " << justReference.mgrs(); // operator of baseclass suits
const LtsCoordTrans::UtmCoordinate &convertedToUtm = justReference.utmCoordinate();
NOTICE_LOG(m_logger) << " convertedToUtm = " << convertedToUtm;
QVERIFY2(qFuzzyCompare(convertedToUtm.x, 670000.5 ), "convert failed");
QVERIFY2(qFuzzyCompare(convertedToUtm.y, 5810000.5), "convert failed");
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
//
// Check how often conversion takes place:
//
const LtsCoordTrans::UtmCoordinate &convertedToUtmSecondTry = justReference.utmCoordinate();
Q_UNUSED(convertedToUtmSecondTry)
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
// push to vector...
std::vector<LtsCoordinateNoImplicitCopy> myVec;
myVec.push_back(justReference);
// retrieve from vector...
const LtsCoordTrans::LtsCoordinateNoImplicitCopy &retrievedUtmRef = myVec.front();
NOTICE_LOG(m_logger) << " retrievedUtm = " << retrievedUtmRef.mgrs(); // operator of baseclass suits
// Of course, values have to be identical:
QVERIFY2(justReference.mgrs() == retrievedUtmRef.mgrs(), "getConversionCounter failed");
// again, copying is not allowd:
// LtsCoordTrans::LtsCoordinateNoImplicitCopy copy = myVec.front();
// final check:
QVERIFY2(LtsCoordTrans::LtsCoordinate::getConversionCounter() == 1, "getConversionCounter failed");
// error: no matching function for call to ‘LtsCoordTrans::LtsCoordinateNoImplicitCopy::LtsCoordinateNoImplicitCopy(const LtsCoordTrans::LtsCoordinateNoImplicitCopy&)’
// LtsCoordinateNoImplicitCopy unwantedCopy1 = store.getLtsCoord_noCopy();
// const LtsCoordinateNoImplicitCopy unwantedCopy2 = store.getLtsCoord_noCopy();
}
//
// helper for test_references
//
template <typename T>
void removeItem(T& map, int key) { map.erase(key); }
// template specialization
template <>
void removeItem(QMap<int, int>& map, int key) { map.remove(key); }
// template specialization
template <>
void removeItem(QHash<int, int>& map, int key) { map.remove(key); }
template <typename T>
void test_references(T& map)
{
for (int i=2; i<10; ++i)
map[i] = i;
const int &ref2 = map[2];
const int &ref3 = map[3];
const int &ref4 = map[4];
const int &ref5 = map[5];
const int &ref6 = map[6];
const int &ref7 = map[7];
const int &ref8 = map[8];
QVERIFY2(ref2 == 2, "ref failed");
QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
// removing:
removeItem(map, 1);
removeItem(map, 3);
removeItem(map, 5);
{
QVERIFY2(ref2 == 2, "ref failed");
// QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
// QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
}
// adding
map[0] = 0;
map[1] = 1;
const int &ref0 = map[0];
const int &ref1 = map[1];
{
QVERIFY2(ref0 == 0, "ref failed");
QVERIFY2(ref1 == 1, "ref failed");
// QVERIFY2(ref1 == 1, "ref failed");
QVERIFY2(ref2 == 2, "ref failed");
// QVERIFY2(ref3 == 3, "ref failed");
QVERIFY2(ref4 == 4, "ref failed");
// QVERIFY2(ref5 == 5, "ref failed");
QVERIFY2(ref6 == 6, "ref failed");
QVERIFY2(ref7 == 7, "ref failed");
QVERIFY2(ref8 == 8, "ref failed");
}
}
void UnitTestCoordinate::test_container_references()
{
// LtsCoordVector is using std::unordered_map.
// Advantage over map is the signature of operator[].
// copy from lts_coord_vector.h
// prefer std::unordered_map, since 'T& operator[]' is used also for const methods
// like toUtmStd(int zone) const
// ^^^^^
// To be really sure:
// check, if ref to member are valid after removing and adding:
{
std::unordered_map<int, int> std_unordered_map;
test_references(std_unordered_map);
}
{
std::map<int, int> std_map;
test_references(std_map);
}
{
QMap<int, int> Qmap;
test_references(Qmap);
}
{
QHash<int, int> Qhash;
test_references(Qhash);
}
}
//
// LtsCoordVector
//
void UnitTestCoordinate::test_coordVector()
{
std::vector<Core::GeoPosition> geoVec;
{
// choose coord which could be convertet to zone 32 als well as zone 31:
QPointF utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic();
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 1.1) );
utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(1, 0);
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 2.2) );
utmPnt = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32 U LB 00001 50001")).geographic(2, 0);
geoVec.push_back( Core::GeoPosition(utmPnt.x(), utmPnt.y(), 3.3) );
}
const LtsCoordVector coordVec(geoVec);
QVERIFY2(coordVec.size() == 3, "size failed");
int cnt = LtsCoordTrans::getConversionCounter_UTMUPS_Forward();
{ // 1st conversion to zone 32
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(32);
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[0].mgrs();
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[1].mgrs();
// NOTICE_LOG(m_logger) << " utmVec = " << utmVec[2].mgrs();
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 3, "getConversionCounterUTMUPS_Forward failed");
}
{ // 1st conversion to zone 31
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(31);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 31
const std::vector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toStdUtmCoord(31);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
//
// QVector is supported also:
// Note: toUtmQVector benefits from conversion to toUtmStd, so no additional conversion :-)
//
{ // 1st conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(utmVec.front().mgrs() == QStringLiteral("32U LB 00001 50001"), "mgrs failed");
QVERIFY2(utmVec.back().mgrs() == QStringLiteral("32U LB 00003 50001"), "mgrs failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
//
// QVector<QPointF> is supported also:
// Note: toUtmQVector benefits from conversion to toUtmStd, so no additional conversion :-)
//
{ // 1st conversion to zone 32
const QVector<QPointF> &utmVec = coordVec.toQVectorQPointF(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
{ // 2nd conversion to zone 32
const QVector<LtsCoordTrans::UtmCoordinate> &utmVec = coordVec.toQVectorUtmCoord(32);
QVERIFY2(utmVec.size() == 3, "size failed");
QVERIFY2(LtsCoordTrans::getConversionCounter_UTMUPS_Forward() - cnt == 6, "getConversionCounterUTMUPS_Forward failed");
}
}
// main
// main
// main
int main(int argc, char *argv[])
{
NGLCoreApplication app(argc, argv);
app.createLogger("UnitTestCoordinate", "log4cxxPositionUnitTest.props");
UnitTestCoordinate tc;
int ret = QTest::qExec(&tc, argc, argv);
// cleanup:
if (s_eleMgr)
{
s_eleMgr->cleanupAll();
}
return ret;
}
@@ -0,0 +1,85 @@
/**
* @RDE_HEADER@
* Unittest
* $Id$
* $HeadURL$
* $Author$
*/
#pragma once
#include <LtsElevation/lts_elevation_manager.h>
#include <ElevationMap/elevation_map_manager.h>
#include <QCoreApplication>
#include <QtTest/QTest>
#include <NGLLogger/INGLLogger.h>
using LOGGER_T = NGL::INGLLogger&;
class UnitTestCoordinate : public QObject
{
Q_OBJECT
public:
UnitTestCoordinate();
private Q_SLOTS:
//
// TestCases
//
// void initTestCase_data(); // will be called to create a global test data table.
void initTestCase(); // will be called before the first test function is executed.
void init(); // will be called before each test function is executed.
// void cleanup(); // will be called after each test function is executed.
void test_NaN();
void test_coordinate_simple1();
void test_coordinate_simple2();
void test_coordinate_simple3();
void test_coordinate_simple4();
void test_coordinate_fromUtm();
void test_coordinate_fromUtm2();
void test_coordinate_elevationAt();
void test_coordinate_elevationAt_isCovered();
void test_coordinate_elevation_data();
void test_coordinate_elevation();
void test_coordinate_mgrs();
void test_coordinate_distance();
void test_coordinate_distanceDiffZones();
void test_squareInDifferentZone();
void test_coordinate_angle();
void test_coordinate_cont();
void test_coordinate_avoid_copy();
// LtsCoordVector
void test_container_references();
void test_coordVector();
// void cleanup();
private:
LOGGER_T m_logger;
};
+96
View File
@@ -0,0 +1,96 @@
/**
* @RDE_HEADER@
* LtsCoordVector
* $Id$
* $HeadURL$
* $Author$
* Created by Norbert Thoden, ESVG, 2025-01-06
*/
#include "lts_coord_vector.h"
namespace LtsCoordTrans
{
LtsCoordVector::LtsCoordVector(const std::vector<Core::GeoPosition> &geo_coordinates)
: m_geo_coordinates(geo_coordinates)
{
}
const std::vector<UtmCoordinate> &LtsCoordVector::toStdUtmCoord(int zone) const
{
convertGeoToStdUtm(zone);
return m_utmMapStd[zone];
}
const QVector<UtmCoordinate> &LtsCoordVector::toQVectorUtmCoord(int zone) const
{
convertGeoToQVectorUtm(zone);
return m_utmMapQVector[zone];
}
const QVector<QPointF> &LtsCoordVector::toQVectorQPointF(int zone) const
{
convertGeoToQVectorQPointF(zone);
return m_utmMapQVectorQPointF[zone];
}
void LtsCoordVector::convertGeoToStdUtm(int zone) const
{
const auto it = m_utmMapStd.find(zone);
if (it != m_utmMapStd.end())
return;
convertGeoToUtmInternal(zone, m_utmMapStd[zone]); // do real conversion
}
void LtsCoordVector::convertGeoToQVectorUtm(int zone) const
{
const auto it = m_utmMapQVector.find(zone);
if (it != m_utmMapQVector.end())
return;
// approach 1:
// convertGeoToUtmInternal(zone, m_utmMapQVector[zone]);
// approach 2:
convertGeoToStdUtm(zone);
QVector<LtsCoordTrans::UtmCoordinate> &utmVectorRef = m_utmMapQVector[zone];
utmVectorRef.clear();
utmVectorRef.reserve(m_geo_coordinates.size());
for (const auto &utmCoord : m_utmMapStd[zone])
utmVectorRef.push_back(utmCoord);
}
void LtsCoordVector::convertGeoToQVectorQPointF(int zone) const
{
const auto it = m_utmMapQVectorQPointF.find(zone);
if (it != m_utmMapQVectorQPointF.end())
return;
convertGeoToStdUtm(zone);
QVector<QPointF> &QVectorQPointFRef = m_utmMapQVectorQPointF[zone];
QVectorQPointFRef.clear();
QVectorQPointFRef.reserve(m_geo_coordinates.size());
for (const auto &utmCoord : m_utmMapStd[zone])
QVectorQPointFRef.push_back(QPointF(utmCoord.x, utmCoord.y));
}
//------------------------------------------------------------------------------
// class LtsCoordinate
//------------------------------------------------------------------------------
// output operator:
//std::ostream &operator<<(std::ostream &stream, const LtsCoordVector &value)
//{
// stream << value.toString().toStdString();
// return stream;
//}
} // namespace LtsCoordTrans
+92
View File
@@ -0,0 +1,92 @@
/**
* @RDE_HEADER@
* LtsCoordVector
* $Id$
* $HeadURL$
* $Author$
* Created by Norbert Thoden, ESVG, 2025-01-06
*/
#pragma once
#include <LtsCoordinate/ltscoordinate_export.h>
#include <Core/core.hpp> // Core::GeoPosition
#include <LtsCoordTrans/lts_coord_trans.h> // UtmCoordinate
#include <unordered_map>
#include <QPointF>
#include <QVector>
#include <QDebug>
#include <iosfwd>
#include <vector>
namespace LtsCoordTrans
{
/*!
* \brief The LtsCoordVector class caches conversion results of vector<Core::GeoPosition> to vector<LtsCoordTrans::UtmCoordinate>
* \section secMotivation Motivation
* Especially in ISM there are several situations where this kind of conversion is executed several times and caching could be helpful.
*
* Please see also the UnitTest UnitTestCoordinate.
*/
class LTSCOORDINATE_EXPORT LtsCoordVector
{
public:
/*! \name Creation */
//@{
explicit LtsCoordVector(const std::vector<Core::GeoPosition> &geo_coordinates);
/*! \name Access, part 1 - without Conversion*/
//@{
int size() const { return m_geo_coordinates.size() ; }
const std::vector<Core::GeoPosition> &geoPosition() const { return m_geo_coordinates; }
//@}
/*! \name Access, part 2 - with Conversion */
//@{
const std::vector<LtsCoordTrans::UtmCoordinate> &toStdUtmCoord(int zone) const;
const QVector<LtsCoordTrans::UtmCoordinate> &toQVectorUtmCoord(int zone) const;
const QVector<QPointF> &toQVectorQPointF(int zone) const;
//@}
protected:
void convertGeoToStdUtm(int zone) const;
void convertGeoToQVectorUtm(int zone) const;
void convertGeoToQVectorQPointF(int zone) const;
template<typename T>
void convertGeoToUtmInternal(int zone, T &utmVectorRef) const
{
// do real conversion:
utmVectorRef.clear();
utmVectorRef.reserve(m_geo_coordinates.size());
for (const auto &geoCoord : qAsConst(m_geo_coordinates))
utmVectorRef.push_back(LtsCoordTrans::UtmCoordinate::fromGeographic(geoCoord.lon(), geoCoord.lat(), zone));
}
protected:
std::vector<Core::GeoPosition> m_geo_coordinates;
// prefer std::unordered_map, since 'T& operator[]' is used also for const methods
// like toUtmStd(int zone) const
// ^^^^^
mutable std::unordered_map<int, std::vector<LtsCoordTrans::UtmCoordinate>> m_utmMapStd; // zone, vector<>
mutable std::unordered_map<int, QVector<LtsCoordTrans::UtmCoordinate>> m_utmMapQVector; // zone, vector<>
mutable std::unordered_map<int, QVector<QPointF>> m_utmMapQVectorQPointF; // zone, vector<>
};
// output operator:
//LTSCOORDINATE_EXPORT std::ostream &operator<< (std::ostream &stream,
// const LtsCoordVector &value);
} // namespace LtsCoordTrans
Loaded 100 of 16245 files, more files were not shown because too many files have changed in this diff. Show more