Revision control

Copy as Markdown

// Copyright (C) 2023 basysKom GmbH, opensource@basyskom.com
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "util.h"
#include "datatypefilewriter.h"
#include "enumeratedtype.h"
#include "enumeratedvalue.h"
#include "field.h"
#include "stringidentifier.h"
#include "structuredtype.h"
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qset.h>
using namespace Qt::Literals::StringLiterals;
DataTypeFileWriter::DataTypeFileWriter(const QString &path,
const QString &prefix,
const QString &header)
: m_path(path)
, m_prefix(prefix)
, m_header(header)
{}
void DataTypeFileWriter::visit(XmlElement *xmlElement)
{
Q_UNUSED(xmlElement);
}
void DataTypeFileWriter::visit(EnumeratedType *enumeratedType)
{
bool isPrecodedType = false;
for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) {
if (precodedType.contains(enumeratedType->name())) {
isPrecodedType = true;
break;
}
}
if (!isPrecodedType) {
m_generateMapping.append(enumeratedType);
}
}
void DataTypeFileWriter::visit(EnumeratedValue *enumeratedValue)
{
Q_UNUSED(enumeratedValue);
}
void DataTypeFileWriter::visit(Field *field)
{
Q_UNUSED(field);
}
void DataTypeFileWriter::visit(Import *import)
{
Q_UNUSED(import);
}
void DataTypeFileWriter::visit(StructuredType *structuredType)
{
if (!structuredType->fields().empty()) {
bool isPrecodedType = false;
for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) {
if (precodedType.contains(structuredType->name())) {
isPrecodedType = true;
break;
}
}
if (!isPrecodedType) {
m_generateMapping.append(structuredType);
}
}
}
void DataTypeFileWriter::visit(TypeDictionary *typeDictionary)
{
Q_UNUSED(typeDictionary);
}
void DataTypeFileWriter::writeLicenseHeader(QTextStream &output)
{
if (!m_header.isEmpty())
output << m_header << Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCpp(const StructuredType *structuredType,
QTextStream &output)
{
if (!m_generatedStructuredTypeFilenames.contains(
u"%1%2"_s.arg(m_prefix, structuredType->name())))
m_generatedStructuredTypeFilenames.push_back(
u"%1%2"_s.arg(m_prefix, structuredType->name()));
output << "#include \"" << m_prefix.toLower() << structuredType->name().toLower() << ".h\"";
QList<QString> mutualIncludes;
const auto typeFields = structuredType->fields();
for (const auto field : typeFields) {
const auto typeName = field->typeNameSecondPart();
for (const auto &type : std::as_const(m_generateMapping)) {
StructuredType *tempStructuredType = dynamic_cast<StructuredType *>(type);
if (tempStructuredType) {
if (tempStructuredType == structuredType)
continue;
const auto tempFields = tempStructuredType->fields();
for (const auto &tempField : tempFields) {
if (typeName == tempStructuredType->name()
&& tempField->typeNameSecondPart() == structuredType->name()) {
if (!mutualIncludes.contains(
u"#include \"%1%2.h\"\n"_s
.arg(m_prefix.toLower(), tempStructuredType->name().toLower())))
mutualIncludes.push_back(
u"#include \"%1%2.h\"\n"_s
.arg(m_prefix.toLower(), tempStructuredType->name().toLower()));
}
}
}
}
}
if (!mutualIncludes.isEmpty()) {
output << Util::lineBreak();
std::sort(mutualIncludes.begin(), mutualIncludes.end());
for (const auto &include : mutualIncludes) {
output << include;
}
}
if (structuredType->recursive()) {
output << Util::lineBreak();
output << "#include <optional>";
output << Util::lineBreak();
}
output << Util::lineBreak(2);
writeStructuredTypeCppClassComment(structuredType, output);
writeStructuredTypeCppDataClass(structuredType, output);
writeStructuredTypeCppConstructors(structuredType, output);
writeStructuredTypeCppOperatorAssignment(structuredType, output);
writeStructuredTypeCppOperatorEquality(structuredType, output);
writeStructuredTypeCppQVariant(structuredType, output);
writeStructuredTypeCppDestructor(structuredType, output);
writeStructuredTypeCppGetterSetter(structuredType, output);
writeStructuredTypeCppDebug(structuredType, output);
}
void DataTypeFileWriter::writeStructuredTypeCppClassComment(const StructuredType *structuredType,
QTextStream &output)
{
output << "/*!"
<< Util::lineBreak();
output << Util::indent(1) << "\\class " << m_prefix << structuredType->name() << Util::lineBreak();
output << Util::indent(1) << "\\brief the OPC UA " << structuredType->name() << "."
<< Util::lineBreak();
output << "*/"
<< Util::lineBreak();
}
void DataTypeFileWriter::writeStructuredTypeCppDataClass(const StructuredType *structuredType,
QTextStream &output)
{
output << "class " << m_prefix << structuredType->name() << "Data : public QSharedData"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << "public:"
<< Util::lineBreak();
writeStructuredTypeCppDataClassMember(structuredType, output);
output << "};"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppDataClassMember(const StructuredType *structuredType,
QTextStream &output)
{
const auto typeFields = structuredType->fields();
QList<Field *> unionMember;
if (structuredType->hasUnion()) {
for (const auto &possibleUnionMember : typeFields) {
for (const auto &field : typeFields) {
if (field->isUnion()) {
if (field->switchField() == possibleUnionMember->name()
&& !unionMember.contains(possibleUnionMember))
unionMember.push_back(possibleUnionMember);
}
}
}
}
QList<Field *> arrayLengthfields;
for (const auto &possibleArrayLengthField : typeFields) {
for (const auto &field : typeFields) {
if (possibleArrayLengthField->name() == field->lengthField()
&& !arrayLengthfields.contains(possibleArrayLengthField))
arrayLengthfields.push_back(possibleArrayLengthField);
}
}
QList<Field *> switchFields;
if (structuredType->hasSwitchfield()) {
for (const auto &possibleOptionalMember : typeFields) {
for (const auto &field : typeFields) {
if (!possibleOptionalMember->switchField().isEmpty() && possibleOptionalMember->switchField() == field->name())
switchFields.push_back(field);
}
}
}
for (const auto &field : typeFields) {
if (field->isInStructuredTypeBitMask() && !switchFields.contains(field))
continue;
bool isEnumeration = false;
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
if (enumeratedType->name() == field->typeName().split(':'_L1).at(1)) {
isEnumeration = true;
field->setIsEnum(true);
}
}
bool isPreCoded = false;
if (!arrayLengthfields.contains(field)) {
if (!unionMember.contains(field)) {
output << Util::indent(1);
if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) {
if (!field->needContainer()) {
output << StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< " ";
} else {
output << "QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< "> ";
}
} else {
const auto typeName = field->typeNameSecondPart();
for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) {
if (preCodedType.contains(typeName)) {
isPreCoded = true;
if (field->needContainer())
output << "QList<";
if (preCodedType.deEncoderName().isEmpty())
output << preCodedType.className();
else
output << preCodedType.deEncoderName();
if (field->needContainer())
output << ">";
output << " ";
break;
}
}
if (!isPreCoded) {
if (field->needContainer())
output << "QList<" << m_prefix << typeName << "> ";
else if (field->recursive())
output << "std::optional<" << m_prefix << typeName << "> ";
else if (isEnumeration)
output << m_prefix << "::" << typeName << " ";
else
output << m_prefix << typeName << " ";
}
}
const auto lowerCaseFieldName = field->lowerFirstName();
output << lowerCaseFieldName;
if (!field->needContainer()) {
if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) {
if (field->typeName().contains(StringIdentifier::integerIdentifier)
|| field->typeName().contains(StringIdentifier::doubleIdentifier)
|| field->typeName().contains(StringIdentifier::floatIdentifier)
|| field->typeName().contains(StringIdentifier::byteIdentifier)
|| field->typeName().contains(StringIdentifier::sbyteIdentifier))
output << " {0}";
else if (field->typeName().contains(StringIdentifier::booleanIdentifier)
|| field->typeName().contains(StringIdentifier::bitIdentifier))
output << " {false}";
} else if (field->isEnum()) {
const auto enumType = std::find_if(m_enumeratedTypes.constBegin(), m_enumeratedTypes.constEnd(),
[field](EnumeratedType *e) { return e->name() == field->typeNameSecondPart(); });
const auto firstValueName = enumType != m_enumeratedTypes.constEnd() && !(*enumType)->values().empty()
? (*enumType)->values().first()->name() : u"Unknown"_s;
if (!firstValueName.isEmpty())
output << " {" << m_prefix << "::" << field->typeNameSecondPart() << "::" << firstValueName << "}";
else
output << " {}";
} else {
if (field->typeName().contains(StringIdentifier::uaStatusCodeIdentifier))
output << " {QOpcUa::UaStatusCode::Good}";
}
}
output << ";"
<< Util::lineBreak();
} else {
const auto fieldNameLowerCase = field->lowerFirstName();
output << Util::indent(1) << m_prefix << structuredType->name() << "::" << field->name()
<< " " << fieldNameLowerCase << " = " << m_prefix << structuredType->name()
<< "::" << field->name() << "::"
<< "None";
output << ";"
<< Util::lineBreak();
}
}
}
}
void DataTypeFileWriter::writeStructuredTypeCppConstructors(const StructuredType *structuredType,
QTextStream &output)
{
output << m_prefix << structuredType->name() << "::" << m_prefix << structuredType->name()
<< "()"
<< Util::lineBreak();
output << " : data(new " << m_prefix << structuredType->name() << "Data)\n";
output << "{"
<< Util::lineBreak();
output << "}"
<< Util::lineBreak(2);
output << "/*!"
<< Util::lineBreak();
output << " Constructs a " << structuredType->name() << " from \\a rhs"
<< Util::lineBreak();
output << "*/"
<< Util::lineBreak();
output << m_prefix << structuredType->name() << "::" << m_prefix << structuredType->name()
<< "(const " << m_prefix << structuredType->name() << " &rhs)"
<< Util::lineBreak();
output << " : data(rhs.data)"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppOperatorAssignment(
const StructuredType *structuredType, QTextStream &output)
{
output << "/*!"
<< Util::lineBreak();
output << " Sets the values from \\a rhs in this " << structuredType->name() << Util::lineBreak();
output << "*/"
<< Util::lineBreak();
output << m_prefix << structuredType->name() << " &" << m_prefix << structuredType->name()
<< "::operator=(const " << m_prefix << structuredType->name() << " &rhs)"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << Util::indent(1) << "if (this != &rhs)"
<< Util::lineBreak();
output << Util::indent(2) << "data.operator=(rhs.data);"
<< Util::lineBreak();
output << Util::indent(1) << "return *this;"
<< Util::lineBreak();
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppOperatorEquality(const StructuredType *structuredType,
QTextStream &output)
{
output << "/*!"
<< Util::lineBreak();
output << Util::indent(1) << "Returns \\c true if this " << structuredType->name()
<< " has the same value as \\a rhs"
<< Util::lineBreak();
output << "*/"
<< Util::lineBreak();
output << "bool " << m_prefix << structuredType->name() << "::operator==(const " << m_prefix
<< structuredType->name() << " &rhs) const"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
QList<Field *> unionSwitchfield;
QList<Field *> arrayLengthfields;
const auto typeFields = structuredType->fields();
for (const auto &possibleMember : typeFields) {
for (const auto &field : typeFields) {
if (possibleMember->name() == field->lengthField()
&& !arrayLengthfields.contains(possibleMember))
arrayLengthfields.push_back(possibleMember);
if (possibleMember->name() == field->switchField()
&& !unionSwitchfield.contains(possibleMember) && field->isUnion())
unionSwitchfield.push_back(possibleMember);
}
}
if (!unionSwitchfield.isEmpty()) {
const auto switchField = unionSwitchfield.first()->lowerFirstName();
output << Util::indent(1) << "if (data->" << switchField << " != " << " rhs.data->" << switchField << ")" << Util::lineBreak();
output << Util::indent(2) << "return false;" << Util::lineBreak(2);
}
if (structuredType->containsBitMask()) {
for (const auto &field : typeFields) {
if (!field->switchField().isEmpty() && !arrayLengthfields.contains(field)) {
const auto switchField = Util::lowerFirstLetter(field->switchField());
output << Util::indent(1) << "if (data->" << switchField << " != rhs.data->" << switchField << " || (data->" << switchField << " && ";
output << "!(data->" << field->lowerFirstName() << " == rhs.data->" << field->lowerFirstName() << ")";
output << "))";
output << Util::lineBreak() << Util::indent(2) << "return false;" << Util::lineBreak();
}
}
output << Util::lineBreak();
}
auto counterGeneratedTypes = 0;
if (structuredType->hasUnion()) {
for (const auto &field : typeFields) {
if (!arrayLengthfields.contains(field) && !unionSwitchfield.contains(field) && !field->isInStructuredTypeBitMask()) {
if (structuredType->containsBitMask() && !field->switchField().isEmpty())
continue;
output << Util::indent(1) << "if (static_cast<qint32>(data->" << unionSwitchfield.first()->lowerFirstName() << ") == " << field->switchValue() << " && ";
output << "!(data->" << field->lowerFirstName() << " == rhs.data->" << field->lowerFirstName() << ")";
output << ")" << Util::lineBreak();
output << Util::indent(2) << "return false;" << Util::lineBreak(2);
}
}
output << Util::indent(1) << "return true;" << Util::lineBreak();
} else {
output << Util::indent(1) << "return ";
for (const auto &field : typeFields) {
if (!arrayLengthfields.contains(field) && !unionSwitchfield.contains(field) && !field->isInStructuredTypeBitMask()) {
if (structuredType->containsBitMask() && !field->switchField().isEmpty())
continue;
const auto memberName = field->lowerFirstName();
if (counterGeneratedTypes != 0)
output << Util::lineBreak() << Util::indent(2) << " && ";
output << "data->" << memberName << " == rhs.data->" << memberName;
counterGeneratedTypes++;
}
}
output << ";" << Util::lineBreak();
}
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppQVariant(const StructuredType *structuredType,
QTextStream &output)
{
output << "/*!"
<< Util::lineBreak();
output << " Converts this " << structuredType->name() << " to \\l QVariant"
<< Util::lineBreak();
output << "*/"
<< Util::lineBreak();
output << m_prefix << structuredType->name() << "::operator QVariant() const"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << Util::indent(1) << "return QVariant::fromValue(*this);"
<< Util::lineBreak();
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppDestructor(const StructuredType *structuredType,
QTextStream &output)
{
output << m_prefix << structuredType->name() << "::~" << m_prefix << structuredType->name()
<< "()"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppGetterSetter(const StructuredType *structuredType,
QTextStream &output)
{
writeStructuredTypeCppGetter(structuredType, output);
}
void DataTypeFileWriter::writeStructuredTypeCppGetter(const StructuredType *structuredType,
QTextStream &output)
{
QList<Field *> unionMember;
const auto typeFields = structuredType->fields();
if (structuredType->hasUnion()) {
for (const auto &possibleUnionMember : typeFields) {
for (const auto &field : typeFields) {
if (field->isUnion()) {
if (field->switchField() == possibleUnionMember->name()
&& !unionMember.contains(possibleUnionMember))
unionMember.push_back(possibleUnionMember);
}
}
}
}
QList<Field *> arrayLengthfields;
for (const auto &possibleArrayLengthField : typeFields) {
for (const auto &field : typeFields) {
if (possibleArrayLengthField->name() == field->lengthField()
&& !arrayLengthfields.contains(possibleArrayLengthField))
arrayLengthfields.push_back(possibleArrayLengthField);
}
}
QList<Field *> switchFields;
if (structuredType->hasSwitchfield()) {
for (const auto &possibleOptionalMember : typeFields) {
for (const auto &field : typeFields) {
if (!possibleOptionalMember->switchField().isEmpty() && possibleOptionalMember->switchField() == field->name())
switchFields.push_back(field);
}
}
}
for (const auto &field : typeFields) {
if (!arrayLengthfields.contains(field) && !(field->isInStructuredTypeBitMask() && !switchFields.contains(field))) {
const auto tmpFunctionName = field->lowerFirstName();
output << "/*!\n";
output << Util::indent(1) << "Returns the field " << field->name() << " of this " << structuredType->name() << " object"
<< Util::lineBreak();
output << "*/\n";
const auto typeName = field->typeNameSecondPart();
if (!unionMember.contains(field)) {
if (field->needContainer())
output << "QList<";
if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) {
output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName());
} else {
bool isPreCoded = false;
for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) {
if (preCodedType.contains(typeName)) {
isPreCoded = true;
if (preCodedType.deEncoderName().isEmpty())
output << preCodedType.className();
else
output << preCodedType.deEncoderName();
break;
}
}
if (!isPreCoded) {
bool isEnum = false;
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
if (enumeratedType->name() == field->typeName().split(':'_L1).at(1))
isEnum = true;
}
if (isEnum)
output << m_prefix << "::" << field->typeName().split(':'_L1).at(1);
else
output << m_prefix << typeName;
}
}
if (field->needContainer())
output << ">";
output << " ";
} else {
output << m_prefix << structuredType->name() << "::" << field->name() << " ";
}
output << m_prefix << structuredType->name() << "::" << tmpFunctionName << "() const"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
if (!field->isUnion()) {
if (field->recursive())
if (field->needContainer()) {
output << Util::indent(1) << "return data->" << tmpFunctionName << ";"
<< Util::lineBreak();
} else {
output << Util::indent(1) << "return data->" << tmpFunctionName << ".value_or(" << m_prefix
<< field->typeName().split(':'_L1).at(1) << "()"
<< ");"
<< Util::lineBreak();
}
else
output << Util::indent(1) << "return data->" << tmpFunctionName << ";"
<< Util::lineBreak();
} else {
const auto switchfieldToLower = Util::lowerFirstLetter(field->switchField());
output << Util::indent(1) << "if (data->" << switchfieldToLower << " == " << m_prefix
<< structuredType->name() << "::" << field->switchField()
<< "::" << field->name() << ")"
<< Util::lineBreak();
output << Util::indent(2) << "return data->" << tmpFunctionName << ";"
<< Util::lineBreak();
output << Util::indent(1) << "else"
<< Util::lineBreak();
output << Util::indent(2) << "return {};" << Util::lineBreak();
}
output << "}"
<< Util::lineBreak(2);
if (!unionMember.contains(field))
writeStructuredTypeCppSetter(field, structuredType, output);
}
}
}
void DataTypeFileWriter::writeStructuredTypeCppSetter(const Field *field,
const StructuredType *structuredType,
QTextStream &output)
{
output << "/*!"
<< Util::lineBreak();
output << Util::indent(1) << "Sets the field " << field->name() << " of this " << structuredType->name() << " object to \\a "
<< field->lowerFirstName() << Util::lineBreak();
output << "*/"
<< Util::lineBreak();
output << "void " << m_prefix << structuredType->name() << "::set" << field->name() << "(";
const auto tmpFunctionName = field->lowerFirstName();
if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) {
if (field->typeName().contains(StringIdentifier::booleanIdentifier)
|| field->typeName().contains(StringIdentifier::integerIdentifier)
|| field->typeName().contains(StringIdentifier::floatIdentifier)
|| field->typeName().contains(StringIdentifier::doubleIdentifier)) {
if (field->needContainer())
output << "QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(field->typeName())
<< "> ";
else
output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName())
<< " ";
} else {
output << "const ";
if (field->needContainer())
output << "QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(field->typeName())
<< "> &";
else
output << StringIdentifier::typeNameDataTypeConverter.value(field->typeName())
<< " &";
}
} else {
const auto typeName = field->typeNameSecondPart();
bool isPreCoded = false;
for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) {
if (preCodedType.contains(typeName)) {
isPreCoded = true;
output << "const ";
if (field->needContainer())
output << "QList<";
if (preCodedType.deEncoderName().isEmpty())
output << preCodedType.className();
else
output << preCodedType.deEncoderName();
if (field->needContainer())
output << ">";
output << " &";
break;
}
}
if (!isPreCoded) {
bool isEnum = false;
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
if (enumeratedType->name() == field->typeName().split(':'_L1).at(1))
isEnum = true;
}
output << "const ";
if (field->needContainer())
output << "QList <" << m_prefix << typeName << "> &";
else if (isEnum)
output << m_prefix << "::" << field->typeName().split(':'_L1).at(1) << " &";
else
output << m_prefix << typeName << " &";
}
}
output << tmpFunctionName << ")"
<< Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << Util::indent(1) << "data->" << tmpFunctionName << " = " << tmpFunctionName << ";"
<< Util::lineBreak();
if (field->isUnion()) {
const auto switchfieldToLower = Util::lowerFirstLetter(field->switchField());
output << Util::indent(1) << "data->" << switchfieldToLower << " = " << m_prefix << structuredType->name()
<< "::" << field->switchField() << "::" << field->name() << ";"
<< Util::lineBreak();
}
output << "}"
<< Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeHeader(const StructuredType *structuredType,
QTextStream &output)
{
output << "#pragma once"
<< Util::lineBreak();
writeStructuredTypeHeaderIncludes(structuredType, output);
output << "class " << m_prefix << structuredType->name() << Util::lineBreak();
output << "{"
<< Util::lineBreak();
output << "public:"
<< Util::lineBreak();
writeStructuredTypeHeaderUnion(structuredType, output);
output << Util::indent(1) << m_prefix << structuredType->name() << "();"
<< Util::lineBreak();
output << Util::indent(1) << m_prefix << structuredType->name() << "(const " << m_prefix
<< structuredType->name() << " &);"
<< Util::lineBreak();
output << Util::indent(1) << m_prefix << structuredType->name() << " &operator=(const " << m_prefix
<< structuredType->name() << " &rhs);"
<< Util::lineBreak();
output << Util::indent(1) << "bool operator==(const " << m_prefix << structuredType->name() << " &rhs) const;"
<< Util::lineBreak();
output << Util::indent(1) << "inline bool operator!=(const " << m_prefix << structuredType->name() << " &rhs) const"
<< Util::lineBreak(1)
<< Util::indent(2) << "{ return !(*this == rhs); }"
<< Util::lineBreak();
output << Util::indent(1) << "operator QVariant() const;"
<< Util::lineBreak();
output << Util::indent(1) << "~" << m_prefix << structuredType->name() << "();"
<< Util::lineBreak(2);
writeStructuredTypeHeaderGetterSetter(structuredType, output);
writeStructuredTypeHeaderDebug(structuredType, output);
output << "private:"
<< Util::lineBreak();
output << Util::indent(1) << "QSharedDataPointer<" << m_prefix << structuredType->name() << "Data> data;"
<< Util::lineBreak();
output << "};"
<< Util::lineBreak(2);
output << "Q_DECLARE_METATYPE(" << m_prefix << structuredType->name() << ")"
<< Util::lineBreak();
}
void DataTypeFileWriter::writeStructuredTypeHeaderIncludes(const StructuredType *structuredType,
QTextStream &output)
{
QList<QString> qtCoreIncludes{u"#include <QSharedData>\n"_s,
u"#include <QVariant>\n"_s};
QList<QString> qtOpcUaIncludes{};
QList<QString> qtClassIncludes;
QList<QString> qtPredifinedClasses;
const auto typeFields = structuredType->fields();
for (const auto &field : typeFields) {
const auto typeName = field->typeNameSecondPart();
QString include;
if (field->typeName().startsWith(u"%1:"_s.arg(StringIdentifier::binaryTypeIdentifier))) {
if (typeName == StringIdentifier::datetimeIdentifier) {
include = u"#include <QDateTime>\n"_s;
} else if (typeName == StringIdentifier::byteStringIdentifier) {
include = u"#include <QByteArray>\n"_s;
} else if (typeName == StringIdentifier::charArrayIdentifier) {
include = u"#include <QString>\n"_s;
} else if (typeName == StringIdentifier::guidIdentifier) {
include = u"#include <QUuid>\n"_s;
}
if (!qtCoreIncludes.contains(include)) {
qtCoreIncludes.push_back(include);
}
} else {
bool isPrecoded = false;
for (const auto &precodedType : StringIdentifier::opcUaPrecodedTypes) {
if (precodedType.contains(typeName)) {
isPrecoded = true;
if (!precodedType.filename().isEmpty()) {
include = u"#include <QtOpcUa/%1>\n"_s
.arg(precodedType.filename());
break;
}
}
}
if (typeName != structuredType->name()) {
if (!isPrecoded) {
bool isEnum = false;
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
if (enumeratedType->name() == typeName)
isEnum = true;
}
if (isEnum) {
if (!qtClassIncludes.contains(
u"#include \"%1enumerations.h\"\n"_s
.arg(m_prefix.toLower())))
qtClassIncludes.push_back(
u"#include \"%1enumerations.h\"\n"_s
.arg(m_prefix.toLower()));
} else {
bool isMutualInclude = false;
for (const auto &type : std::as_const(m_generateMapping)) {
StructuredType *tempStructuredType = dynamic_cast<StructuredType *>(
type);
if (tempStructuredType) {
if (tempStructuredType == structuredType)
continue;
const auto tempFields = tempStructuredType->fields();
for (const auto &tempField : tempFields) {
const auto tempTypeName = tempField->typeNameSecondPart();
if (typeName == tempStructuredType->name()
&& tempTypeName == structuredType->name()) {
isMutualInclude = true;
if (!qtPredifinedClasses.contains(
u"%1%2"_s.arg(m_prefix, tempStructuredType->name())))
qtPredifinedClasses.push_back(
u"%1%2"_s.arg(m_prefix, tempStructuredType->name()));
}
}
}
}
if (!qtClassIncludes.contains(
u"#include \"%1%2.h\"\n"_s
.arg(m_prefix.toLower(), typeName.toLower()))
&& !isMutualInclude
&& typeName != StringIdentifier::xmlElementIdentifier)
qtClassIncludes.push_back(
u"#include \"%1%2.h\"\n"_s
.arg(m_prefix.toLower(), typeName.toLower()));
}
} else {
if (!qtOpcUaIncludes.contains(include)) {
qtOpcUaIncludes.push_back(include);
}
}
}
}
if (field->needContainer()) {
include = u"#include <QList>\n"_s;
if (!qtCoreIncludes.contains(include)) {
qtCoreIncludes.push_back(include);
}
}
}
if (!qtClassIncludes.isEmpty()) {
std::sort(qtClassIncludes.begin(), qtClassIncludes.end());
for (const auto &include : qtClassIncludes) {
output << include;
}
output << Util::lineBreak();
}
std::sort(qtOpcUaIncludes.begin(), qtOpcUaIncludes.end());
for (const auto &include : qtOpcUaIncludes) {
output << include;
}
output << Util::lineBreak();
std::sort(qtCoreIncludes.begin(), qtCoreIncludes.end());
for (const auto &include : qtCoreIncludes) {
output << include;
}
output << Util::lineBreak();
if (!qtPredifinedClasses.isEmpty()) {
std::sort(qtPredifinedClasses.begin(), qtPredifinedClasses.end());
for (const auto &predefinedClass : qtPredifinedClasses) {
output << "class " << predefinedClass << ";"
<< Util::lineBreak();
}
}
output << "class " << m_prefix << structuredType->name() << "Data;"
<< Util::lineBreak();
}
void DataTypeFileWriter::writeStructuredTypeHeaderUnion(const StructuredType *structuredType,
QTextStream &output)
{
if (structuredType->hasUnion()) {
QList<Field *> unions;
QList<Field *> switchFields;
const auto typeFields = structuredType->fields();
for (const auto &field : typeFields) {
if (field->isUnion())
switchFields.push_back(field);
}
for (const auto &switchField : switchFields) {
for (const auto &structuredTypeField : typeFields) {
if (switchField->switchField() == structuredTypeField->name()) {
if (!unions.contains(structuredTypeField))
unions.push_back(structuredTypeField);
}
}
}
QList<Field *> arrayLengthfields;
for (const auto &possibleArrayLengthField : typeFields) {
for (const auto &field : typeFields) {
if (possibleArrayLengthField->name() == field->lengthField()
&& !arrayLengthfields.contains(possibleArrayLengthField))
arrayLengthfields.push_back(possibleArrayLengthField);
}
}
for (const auto &field : unions) {
output << Util::indent(1) << "enum class " << field->name() << " {"
<< Util::lineBreak();
output << Util::indent(2) << "None = 0,\n";
for (int i = 0; i < switchFields.size(); i++) {
const int nextField = i + 1;
if (!arrayLengthfields.contains(switchFields.at(i))) {
if (nextField >= switchFields.size()) {
if (switchFields.at(i)->switchField() == field->name()) {
output << Util::indent(2) << switchFields.at(i)->name() << " = "
<< switchFields.at(i)->switchValue() << Util::lineBreak();
output << Util::indent(1) << "};"
<< Util::lineBreak(2);
}
} else {
if (field->name() == switchFields.at(nextField)->switchField()) {
output << Util::indent(2) << switchFields.at(i)->name() << " = "
<< switchFields.at(i)->switchValue() << ","
<< Util::lineBreak();
} else {
output << Util::indent(2) << switchFields.at(i)->name() << " = "
<< switchFields.at(i)->switchValue() << Util::lineBreak();
output << Util::indent(1) << "};"
<< Util::lineBreak(2);
}
}
}
}
}
}
}
void DataTypeFileWriter::writeStructuredTypeHeaderGetterSetter(const StructuredType *structuredType,
QTextStream &output)
{
QList<Field *> unionMember;
QList<Field *> arrayLengthfields;
QList<Field *> switchFields;
const auto typeFields = structuredType->fields();
for (const auto &possibleMember : typeFields) {
for (const auto &field : typeFields) {
if (possibleMember->name() == field->lengthField()
&& !arrayLengthfields.contains(possibleMember))
arrayLengthfields.push_back(possibleMember);
if (field->isUnion() && field->switchField() == possibleMember->name()
&& !unionMember.contains(possibleMember))
unionMember.push_back(possibleMember);
if (!possibleMember->switchField().isEmpty() && field->name() == possibleMember->switchField())
switchFields.push_back(field);
}
}
for (const auto &field : typeFields) {
if (!arrayLengthfields.contains(field) && !(field->isInStructuredTypeBitMask() && !switchFields.contains(field))) {
const auto typeName = field->typeNameSecondPart();
const auto tmpFunctionName = field->lowerFirstName();
if (!unionMember.contains(field)) {
if (StringIdentifier::typeNameDataTypeConverter.contains(field->typeName())) {
if (field->typeName().contains(StringIdentifier::booleanIdentifier)
|| field->typeName().contains(StringIdentifier::integerIdentifier)
|| field->typeName().contains(StringIdentifier::floatIdentifier)
|| field->typeName().contains(StringIdentifier::doubleIdentifier)) {
if (!field->needContainer()) {
output << Util::indent(1)
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< " " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "("
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< " " << tmpFunctionName << ");"
<< Util::lineBreak(2);
} else {
output << Util::indent(1) << "QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< "> " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< "> " << tmpFunctionName << ");"
<< Util::lineBreak(2);
}
} else {
if (!field->needContainer()) {
output << Util::indent(1)
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< " " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const "
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< " &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
} else {
output << Util::indent(1) << "QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< "> " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const QList<"
<< StringIdentifier::typeNameDataTypeConverter.value(
field->typeName())
<< "> &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
}
}
} else {
bool isPreCoded = false;
for (const auto &preCodedType : StringIdentifier::opcUaPrecodedTypes) {
if (preCodedType.contains(typeName)) {
isPreCoded = true;
if (preCodedType.deEncoderName().isEmpty()) {
output << Util::indent(1);
if (field->needContainer())
output << "QList<";
output << preCodedType.className();
if (field->needContainer())
output << ">";
output << " " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const ";
if (field->needContainer())
output << "QList<";
output << preCodedType.className();
if (field->needContainer())
output << ">";
output << " &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
} else {
output << Util::indent(1);
if (field->needContainer())
output << "QList<";
output << preCodedType.deEncoderName();
if (field->needContainer())
output << ">";
output << " " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const ";
if (field->needContainer())
output << "QList<";
output << preCodedType.deEncoderName();
if (field->needContainer())
output << ">";
output << " &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
}
break;
}
}
if (!isPreCoded) {
if (field->needContainer()) {
output << Util::indent(1) << "QList <" << m_prefix << typeName << "> "
<< tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const QList <"
<< m_prefix << typeName << "> &" << tmpFunctionName
<< ");"
<< Util::lineBreak(2);
} else {
bool isEnum = false;
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
if (enumeratedType->name() == field->typeName().split(':'_L1).at(1))
isEnum = true;
}
if (isEnum) {
output << Util::indent(1) << m_prefix << "::" << typeName << " "
<< tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const " << m_prefix
<< "::" << typeName << " &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
} else {
output << Util::indent(1) << m_prefix << typeName << " "
<< tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::indent(1) << "void set" << field->name() << "(const " << m_prefix
<< typeName << " &" << tmpFunctionName << ");"
<< Util::lineBreak(2);
}
}
}
}
} else { // is union switchfield, create just getter
output << Util::indent(1) << field->name() << " " << tmpFunctionName << "() const;"
<< Util::lineBreak();
output << Util::lineBreak();
}
}
}
}
void DataTypeFileWriter::writeStructuredTypeHeaderDebug(const StructuredType *structuredType, QTextStream &output)
{
output << Util::indent(1) << "friend QDebug operator<<(QDebug debug, const " << m_prefix << structuredType->name() << " &v);";
output << Util::lineBreak(2);
}
void DataTypeFileWriter::writeStructuredTypeCppDebug(const StructuredType *structuredType, QTextStream &output)
{
const auto typeName = u"%1%2"_s.arg(m_prefix, structuredType->name());
output << "/*!" << Util::lineBreak();
output << Util::indent(1) << "Prints the field values of object \\a v to \\a debug" << Util::lineBreak();
output << "*/" << Util::lineBreak();
output << "QDebug operator<<(QDebug debug, const " << typeName << " &v)" << Util::lineBreak();
output << "{" << Util::lineBreak();
output << Util::indent(1) << "QDebugStateSaver saver(debug);" << Util::lineBreak();
output << Util::indent(1) << "debug.nospace().noquote();" << Util::lineBreak(2);
output << Util::indent(1) << "debug << \"" << typeName << " (\";" << Util::lineBreak(2);
if (!structuredType->hasUnion() && structuredType->hasSwitchfield())
output << Util::indent(1) << "bool firstFieldPrinted = false;" << Util::lineBreak(2);
bool isFirst = true;
QSet<Field *> lengthFields;
const auto typeFields = structuredType->fields();
for (const auto &field : typeFields) {
for (const auto &innerField : typeFields) {
if (!field->lengthField().isEmpty() && field->lengthField() == innerField->name())
lengthFields.insert(innerField);
}
}
bool parameterUsed = false;
for (const auto &field : typeFields) {
if (structuredType->hasUnion() && field == typeFields.constFirst())
continue;
if (field->isInStructuredTypeBitMask())
continue;
if (lengthFields.contains(field))
continue;
if (structuredType->hasUnion())
output << Util::indent(1) << "if (static_cast<quint32>(v." << Util::lowerFirstLetter(field->switchField()) << "()) == " << field->switchValue() << ") {" << Util::lineBreak();
else if (!field->switchField().isEmpty())
output << Util::indent(1) << "if (v." << Util::lowerFirstLetter(field->switchField()) << "()) {" << Util::lineBreak();
int indentOffset = 0;
if (structuredType->hasUnion() || !field->switchField().isEmpty())
indentOffset = 1;
if (!structuredType->hasUnion() && !isFirst) {
if (structuredType->hasSwitchfield())
output << Util::indent(1 + indentOffset) << "if (firstFieldPrinted)" << Util::lineBreak();
output << Util::indent(1 + (structuredType->hasSwitchfield() ? indentOffset + 1 : 0)) << "debug << \", \";" << Util::lineBreak();
}
output << Util::indent(1 + indentOffset) << "debug << \"" << field->name() << ": \";" << Util::lineBreak();
// Not all types have an operator<< for QDebug...
const auto isPrecoded = std::find_if(StringIdentifier::opcUaPrecodedTypes.constBegin(), StringIdentifier::opcUaPrecodedTypes.constEnd(),
[&](const auto &entry) { return entry.name() == field->typeNameSecondPart(); });
if (isPrecoded == StringIdentifier::opcUaPrecodedTypes.constEnd() || StringIdentifier::precodedTypesWithDebugOperator.contains(field->typeNameSecondPart())) {
output << Util::indent(1 + indentOffset) << "debug << v." << field->lowerFirstName() << "();" << Util::lineBreak();
parameterUsed = true;
} else {
if (field->needContainer()) {
const auto tempListName = u"%1%2"_s.arg(field->lowerFirstName(), u"Strings"_s);
output << Util::indent(1 + indentOffset) << "QStringList " << tempListName << ";" << Util::lineBreak();
output << Util::indent(1 + indentOffset) << "using namespace Qt::Literals::StringLiterals;" << Util::lineBreak();
output << Util::indent(1 + indentOffset) << "for (int i = 0; i < v." << field->lowerFirstName() << "().size(); ++i)" << Util::lineBreak();
output << Util::indent(2 + indentOffset) << tempListName << ".push_back(u\"" << isPrecoded->className() << "(...)\"_s" << ");" << Util::lineBreak();
output << Util::indent(1 + indentOffset) << "debug << \"QList(\" << " << tempListName << ".join(u\", \"_s) << \")\";";
parameterUsed = true;
} else {
output << Util::indent(1 + indentOffset) << "debug << \"" << isPrecoded->className() << "(...)\";";
}
output << Util::lineBreak();
}
if (!structuredType->hasUnion() && structuredType->hasSwitchfield() && field != typeFields.constLast())
output << Util::indent(1 + indentOffset) << "firstFieldPrinted = true;" << Util::lineBreak();
if (structuredType->hasUnion() || !field->switchField().isEmpty())
output << Util::indent(1) << "}" << Util::lineBreak();
output << Util::lineBreak();
isFirst = false;
}
if (!parameterUsed)
output << Util::lineBreak() << Util::indent(1) << "Q_UNUSED(v);" << Util::lineBreak(2);
output << Util::indent(1) << "debug << \")\";" << Util::lineBreak();
output << Util::indent(1) << "return debug;" << Util::lineBreak();
output << "}" << Util::lineBreak();
}
DataTypeFileWriter::GeneratingError DataTypeFileWriter::writeEnumeratedTypes()
{
QFile file;
const auto fileName = u"%1enumerations.h"_s.arg(m_prefix.toLower());
QDir dir(m_path);
if (!dir.exists(m_path))
if (!dir.mkpath(m_path))
return UnableToWrite;
file.setFileName(dir.absoluteFilePath(fileName));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return UnableToWrite;
QTextStream output(&file);
writeLicenseHeader(output);
if (!m_generatedEnumeratedTypeFilenames.contains(
u"%1enumerations"_s.arg(m_prefix.toLower())))
m_generatedEnumeratedTypeFilenames.push_back(
u"%1enumerations"_s.arg(m_prefix.toLower()));
output << "#pragma once"
<< Util::lineBreak();
output << "#include <QMetaType>"
<< Util::lineBreak(2);
output << "namespace " << m_prefix << " {"
<< Util::lineBreak();
output << "Q_NAMESPACE"
<< Util::lineBreak(2);
for (const auto &enumeratedType : std::as_const(m_enumeratedTypes)) {
output << "enum class " << enumeratedType->name() << " {" << Util::lineBreak(1);
const auto tempValues = enumeratedType->values();
for (const auto &enumeratedValue : tempValues) {
output << Util::indent(1) << enumeratedValue->name() << " = " << enumeratedValue->value();
if (!tempValues.endsWith(enumeratedValue))
output << ",";
output << Util::lineBreak();
}
output << "};"
<< Util::lineBreak();
output << "Q_ENUM_NS(" << enumeratedType->name() << ")"
<< Util::lineBreak(2);
}
output << Util::lineBreak();
output << "}"
<< Util::lineBreak();
m_generatedHeaderFileNames.push_back(fileName);
return GeneratingError::NoError;
}
DataTypeFileWriter::GeneratingError DataTypeFileWriter::generateFile(const XmlElement *type,
const QString &fileExtension)
{
QFile file;
const auto fileName = u"%1%2%3"_s.arg(m_prefix.toLower(),
type->name().toLower(),
fileExtension);
QDir dir(m_path);
if (!dir.exists(m_path))
if (!dir.mkpath(m_path))
return UnableToWrite;
file.setFileName(dir.absoluteFilePath(fileName));
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
return UnableToWrite;
QTextStream output(&file);
writeLicenseHeader(output);
auto structuredType = dynamic_cast<const StructuredType *>(type);
if (structuredType) {
if (fileExtension == StringIdentifier::headerIdentifier) {
writeStructuredTypeHeader(structuredType, output);
if (!m_generatedHeaderFileNames.contains(fileName))
m_generatedHeaderFileNames.push_back(fileName);
} else {
if (fileExtension == StringIdentifier::cppIdentifier) {
writeStructuredTypeCpp(structuredType, output);
if (!m_generatedSourceFileNames.contains(fileName))
m_generatedSourceFileNames.push_back(fileName);
}
}
}
file.close();
return NoError;
}
DataTypeFileWriter::GeneratingError DataTypeFileWriter::generateTypes(
const QList<XmlElement *> &types)
{
m_generateMapping.append(types);
for (const auto &type : std::as_const(m_generateMapping)) {
const auto enumeratedType = dynamic_cast<EnumeratedType *>(type);
if (enumeratedType)
if (!m_enumeratedTypes.contains(enumeratedType))
m_enumeratedTypes.append(enumeratedType);
}
if (writeEnumeratedTypes() != GeneratingError::NoError) {
return GeneratingError::UnableToWrite;
}
for (const auto &type : std::as_const(m_generateMapping)) {
const auto structuredType = dynamic_cast<StructuredType *>(type);
if (structuredType) {
if (generateFile(type, StringIdentifier::headerIdentifier) != GeneratingError::NoError)
return GeneratingError::UnableToWrite;
if (generateFile(type, StringIdentifier::cppIdentifier) != GeneratingError::NoError)
return GeneratingError::UnableToWrite;
}
}
return NoError;
}
QList<XmlElement *> DataTypeFileWriter::generateMapping() const
{
return m_generateMapping;
}
void DataTypeFileWriter::setGenerateMapping(const QList<XmlElement *> &generateMapping)
{
m_generateMapping = generateMapping;
}
bool DataTypeFileWriter::writeBundleFiles()
{
const auto collectionHeaderName = u"%1datatypes%2"_s
.arg(m_prefix.toLower(), StringIdentifier::headerIdentifier);
const auto collectionSourceName = u"%1datatypes%2"_s
.arg(m_prefix.toLower(), StringIdentifier::cppIdentifier);
if (!m_generatedHeaderFileNames.isEmpty()) {
QFile file(QDir(m_path).absoluteFilePath(collectionHeaderName));
if (!file.open(QFile::WriteOnly))
return false;
QTextStream out(&file);
writeLicenseHeader(out);
out << "#pragma once" << Util::lineBreak(2);
for (const auto &header : std::as_const(m_generatedHeaderFileNames))
out << "#include \"" << header << "\"" << Util::lineBreak();
}
const auto enumHeaderCandidate = u"%1enumerations.h"_s.arg(m_prefix.toLower());
const bool hasEnums = m_generatedHeaderFileNames.contains(enumHeaderCandidate);
if (!m_generatedSourceFileNames.isEmpty() || hasEnums) {
QFile file(QDir(m_path).absoluteFilePath(collectionSourceName));
if (!file.open(QFile::WriteOnly))
return false;
QTextStream out(&file);
writeLicenseHeader(out);
if (hasEnums)
out << "#include \"" << u"moc_%1enumerations.cpp"_s.arg(m_prefix.toLower()) << "\"" << Util::lineBreak();
for (const auto &source : std::as_const(m_generatedSourceFileNames))
out << "#include \"" << source << "\"" << Util::lineBreak();
}
return true;
}