Explorar o código

Add cycling nested support

- Add lazy intialization of message fields
- Add cycling dependency support between nested types
- Fix nested types support in QML
- Rework namespaces specification for nested types
- Add extra QML test
- Add and update tests

Fixes: #126
Alexey Edelev %!s(int64=4) %!d(string=hai) anos
pai
achega
d021b32657

+ 23 - 3
src/generator/generatorcommon.cpp

@@ -26,7 +26,6 @@
 #include "generatorcommon.h"
 #include "generatoroptions.h"
 
-#include "templates.h"
 #include <assert.h>
 
 using namespace ::QtProtobuf::generator;
@@ -96,8 +95,12 @@ TypeMap common::produceQtTypeMap(const ::Descriptor *type, const Descriptor *sco
 TypeMap common::produceMessageTypeMap(const ::Descriptor *type, const Descriptor *scope)
 {
     std::vector<std::string> namespaceList = getNamespaces(type);
+    std::vector<std::string> nestedNamespaceList = namespaceList;
+    if(isNested(type)) {
+        nestedNamespaceList = getNestedNamespaces(type);
+    }
     std::string namespaces = getNamespacesString(namespaceList, "::");
-    std::string scopeNamespaces = getScopeNamespacesString(namespaces, getNamespacesString(getNamespaces(scope), "::"));
+    std::string scopeNamespaces = getScopeNamespacesString(getNamespacesString(nestedNamespaceList, "::"), getNamespacesString(getNamespaces(scope), "::"));
     std::string qmlPackage = getNamespacesString(namespaceList, ".");
 
     std::string name = utils::upperCaseName(type->name());
@@ -425,11 +428,13 @@ void common::iterateNestedMessages(const ::google::protobuf::Descriptor *message
         auto nestedMessage = message->nested_type(i);
         if (message->field_count() <= 0) {
             callback(nestedMessage);
+            continue;
         }
         for (int j = 0; j < message->field_count(); j++) {
             auto field = message->field(j);
             if (!field->is_map() && field->message_type() == nestedMessage) { //Probably there is more correct way to detect map in nested messages.                                                                              //TODO: Have idea to make maps nested classes instead of typedefs.
                 callback(nestedMessage);
+                break;
             }
         }
     }
@@ -456,7 +461,22 @@ bool common::hasNestedMessages(const ::google::protobuf::Descriptor *message)
 
 bool common::isNested(const ::google::protobuf::Descriptor *message)
 {
-    return message->containing_type() != nullptr;
+    if (message->containing_type() == nullptr) {
+        return false;
+    }
+
+    auto containingType = message->containing_type();
+    bool isNested = true;
+
+    for (int i = 0; i < containingType->field_count(); i++) {
+        auto field = containingType->field(i);
+        if (field->message_type() == message) {
+            isNested = !field->is_map();
+            break;
+        }
+    }
+
+    return isNested;
 }
 
 const ::google::protobuf::Descriptor *common::findHighestMessage(const ::google::protobuf::Descriptor *message)

+ 11 - 0
src/generator/generatorcommon.h

@@ -31,6 +31,7 @@
 #include <functional>
 
 #include "utils.h"
+#include "templates.h"
 
 #include <google/protobuf/descriptor.h>
 
@@ -61,6 +62,16 @@ struct common {
         return namespacesList;
     }
 
+    static std::vector<std::string> getNestedNamespaces(const ::google::protobuf::Descriptor *type) {
+        auto namespaces = getNamespaces(type);
+        auto package = utils::split(type->file()->package(), '.');
+        for (size_t i = package.size(); i < namespaces.size(); i++) {
+                namespaces[i] += Templates::QtProtobufNestedNamespace;
+        }
+        return namespaces;
+    }
+
+
     static std::string getNamespacesString(const std::vector<std::string> &namespacesList, const std::string &separator);
     static std::string getScopeNamespacesString(std::string original, const std::string &scope);
     static TypeMap produceQtTypeMap(const ::google::protobuf::Descriptor *type, const ::google::protobuf::Descriptor *scope);

+ 24 - 3
src/generator/messagedeclarationprinter.cpp

@@ -248,12 +248,10 @@ void MessageDeclarationPrinter::printProperties()
 void MessageDeclarationPrinter::printGetters()
 {
     Indent();
-
     common::iterateMessageFields(mDescriptor, [&](const FieldDescriptor *field, const PropertyMap &propertyMap) {
         printComments(field);
         mPrinter->Print("\n");
         if (common::isPureMessage(field)) {
-            mPrinter->Print(propertyMap, Templates::GetterPrivateMessageDeclarationTemplate);
             mPrinter->Print(propertyMap, Templates::GetterMessageDeclarationTemplate);
         } else {
             mPrinter->Print(propertyMap, Templates::GetterTemplate);
@@ -277,7 +275,6 @@ void MessageDeclarationPrinter::printSetters()
         switch (field->type()) {
         case FieldDescriptor::TYPE_MESSAGE:
             if (!field->is_map() && !field->is_repeated() && !common::isQtType(field)) {
-                mPrinter->Print(propertyMap, Templates::SetterPrivateTemplateDeclarationMessageType);
                 mPrinter->Print(propertyMap, Templates::SetterTemplateDeclarationMessageType);
             } else {
                 mPrinter->Print(propertyMap, Templates::SetterTemplateDeclarationComplexType);
@@ -295,6 +292,28 @@ void MessageDeclarationPrinter::printSetters()
     Outdent();
 }
 
+void MessageDeclarationPrinter::printPrivateGetters()
+{
+    Indent();
+    common::iterateMessageFields(mDescriptor, [&](const FieldDescriptor *field, const PropertyMap &propertyMap) {
+        if (common::isPureMessage(field)) {
+            mPrinter->Print(propertyMap, Templates::GetterPrivateMessageDeclarationTemplate);
+        }
+    });
+    Outdent();
+}
+
+void MessageDeclarationPrinter::printPrivateSetters()
+{
+    Indent();
+    common::iterateMessageFields(mDescriptor, [&](const FieldDescriptor *field, const PropertyMap &propertyMap) {
+        if (common::isPureMessage(field)) {
+            mPrinter->Print(propertyMap, Templates::SetterPrivateTemplateDeclarationMessageType);
+        }
+    });
+    Outdent();
+}
+
 void MessageDeclarationPrinter::printSignals()
 {
     Indent();
@@ -379,6 +398,8 @@ void MessageDeclarationPrinter::printClassBody()
     printSignals();
 
     printPrivateBlock();
+    printPrivateGetters();
+    printPrivateSetters();
     printPrivateMethods();
 
     printPrivateBlock();

+ 2 - 0
src/generator/messagedeclarationprinter.h

@@ -53,6 +53,8 @@ private:
     void printProperties();
     void printGetters();
     void printSetters();
+    void printPrivateGetters();
+    void printPrivateSetters();
     void printSignals();
     void printPrivateMethods();
     void printClassMembers();

+ 2 - 2
src/generator/messagedefinitionprinter.cpp

@@ -231,7 +231,7 @@ void MessageDefinitionPrinter::printCopyFunctionality()
     Indent();
     common::iterateMessageFields(mDescriptor, [&](const FieldDescriptor *field, const PropertyMap &propertyMap) {
         if (common::isPureMessage(field)) {
-            mPrinter->Print(propertyMap, Templates::CopyComplexFieldTemplate);
+            mPrinter->Print(propertyMap, Templates::AssignComplexFieldTemplate);
         } else {
             mPrinter->Print(propertyMap, Templates::CopyFieldTemplate);
         }
@@ -292,7 +292,7 @@ void MessageDefinitionPrinter::printMoveSemantic()
                 || field->type() == FieldDescriptor::TYPE_BYTES
                 || field->is_repeated()) {
             if (common::isPureMessage(field)) {
-                mPrinter->Print(propertyMap, Templates::MoveMessageFieldTemplate);
+                mPrinter->Print(propertyMap, Templates::MoveAssignMessageFieldTemplate);
             } else {
                 mPrinter->Print(propertyMap, Templates::MoveComplexFieldTemplate);
             }

+ 58 - 10
src/generator/multifilegenerator.cpp

@@ -88,21 +88,50 @@ bool MultiFileGenerator::Generate(const FileDescriptor *file,
             printInclude(headerPrinter, message, message->field(i), existingIncludes);
         }
 
-        //Print dependency classes forward declaration
+        auto deps = findNestedDependency(message);
+
         for (int i = 0; i < message->field_count(); i++) {
             auto field = message->field(i);
             if (common::isPureMessage(field)) {
                 auto dependency = field->message_type();
-                if (!common::isNested(dependency)) {//TODO: need to check class relations and apply namespaces accordingly.
-                    auto namespaces = common::getNamespaces(dependency);
-                    printNamespaces(headerPrinter, namespaces);
-                    headerPrinter->Print({{"classname", utils::upperCaseName(dependency->name())}}, Templates::ProtoClassForwardDeclarationTemplate);
-                    headerPrinter->Print("\n");
-                    for (size_t i = 0; i < namespaces.size(); ++i) {
-                        headerPrinter->Print(Templates::SimpleBlockEnclosureTemplate);
-                    }
-                    headerPrinter->Print("\n");
+                std::cerr << "Found dependency: " << dependency->name() << std::endl;
+                deps.push_back(dependency);
+            }
+        }
+
+        for (auto dependency : deps) {
+            std::string outFileBasename = "";
+            std::string fieldPackage = dependency->file()->package();
+            if (fieldPackage != message->file()->package()) {
+                std::vector<std::string> packages = utils::split(fieldPackage, '.');
+                for (auto package : packages) {
+                    outFileBasename += package + "/";
+                }
+            }
+
+            auto parentType = common::findHighestMessage(dependency);
+            std::string typeName = parentType->name();
+            utils::tolower(typeName);
+            auto newInclude = outFileBasename + typeName;
+            if (existingIncludes.find(newInclude) == std::end(existingIncludes)) {
+                headerPrinter->Print({{"include", newInclude}}, Templates::InternalIncludeTemplate);
+                existingIncludes.insert(newInclude);
+            }
+        }
+
+        for (auto dependency : deps) {
+            if (!common::isNestedOf(dependency, message)) {
+                auto namespaces = common::getNamespaces(dependency);
+                if (common::isNested(dependency)) {
+                    namespaces = common::getNestedNamespaces(dependency);
+                }
+                printNamespaces(headerPrinter, namespaces);
+                headerPrinter->Print({{"classname", utils::upperCaseName(dependency->name())}}, Templates::ProtoClassForwardDeclarationTemplate);
+                headerPrinter->Print("\n");
+                for (size_t i = 0; i < namespaces.size(); ++i) {
+                    headerPrinter->Print(Templates::SimpleBlockEnclosureTemplate);
                 }
+                headerPrinter->Print("\n");
             }
         }
 
@@ -155,6 +184,25 @@ bool MultiFileGenerator::Generate(const FileDescriptor *file,
     return true;
 }
 
+std::list<const ::google::protobuf::Descriptor *> MultiFileGenerator::findNestedDependency(const ::google::protobuf::Descriptor *message) const
+{
+    std::list<const ::google::protobuf::Descriptor *> dependencies;
+    common::iterateNestedMessages(message, [&dependencies, this] (const ::google::protobuf::Descriptor *message) {
+        for (int i = 0; i < message->field_count(); i++) {
+            auto field = message->field(i);
+            if (common::isPureMessage(field)) {
+                auto dependency = field->message_type();
+                std::cerr << "Found dependency: " << dependency->name() << std::endl;
+                dependencies.push_back(dependency);
+            }
+        }
+        auto nextLevelDependencies = findNestedDependency(message);
+        dependencies.insert(dependencies.end(), nextLevelDependencies.begin(), nextLevelDependencies.end());
+    });
+    return dependencies;
+}
+
+
 bool MultiFileGenerator::GenerateAll(const std::vector<const FileDescriptor *> &files, const string &parameter, GeneratorContext *generatorContext, string *error) const
 {
     std::string globalEnumsFilename = "globalenums";

+ 3 - 0
src/generator/multifilegenerator.h

@@ -28,6 +28,7 @@
 #include <google/protobuf/compiler/code_generator.h>
 #include <google/protobuf/io/zero_copy_stream.h>
 #include <string>
+#include <list>
 #include <memory>
 
 #include "generatorbase.h"
@@ -58,6 +59,8 @@ public:
                              const std::string &parameter,
                              ::google::protobuf::compiler::GeneratorContext *generatorContext,
                              std::string *error) const override;
+private:
+    std::list<const ::google::protobuf::Descriptor *> findNestedDependency(const ::google::protobuf::Descriptor *message) const;
 };
 
 } //namespace generator

+ 72 - 68
src/generator/templates.cpp

@@ -34,6 +34,7 @@ const std::vector<std::string> Templates::ListOfQmlExeptions{"id", "property", "
 const char *Templates::DefaultProtobufIncludesTemplate = "#include <QMetaType>\n"
                                                          "#include <QList>\n"
                                                          "#include <QProtobufObject>\n"
+                                                         "#include <QProtobufLazyMessagePointer>\n"
                                                          "#include <QSharedPointer>\n"
                                                          "\n"
                                                          "#include <memory>\n"
@@ -48,7 +49,7 @@ const char *Templates::GlobalEnumClassNameTemplate = "GlobalEnums";
 const char *Templates::DisclaimerTemplate = "/* This file is autogenerated. DO NOT CHANGE. All changes will be lost */\n\n";
 
 const char *Templates::PreambleTemplate = "#pragma once\n\n"
-                                      "#include <QObject>\n";
+                                          "#include <QObject>\n";
 
 const char *Templates::InternalIncludeTemplate =  "#include \"$include$.h\"\n";
 const char *Templates::ExternalIncludeTemplate = "#include <$include$>\n";
@@ -59,30 +60,30 @@ const char *Templates::ManualRegistrationDeclaration = "static void registerType
 const char *Templates::ManualRegistrationComplexTypeDefinition = "void $type$::registerTypes()\n{\n"
                                                                  "    qRegisterMetaType<$type$>(\"$full_type$\");\n"
                                                                  "    qRegisterMetaType<$type$*>(\"$full_type$*\");\n" //Somehow for aliastypes qRegisterMetaType logic doesn't work for pointer type registration
-                                                                 "    qRegisterMetaType<$list_type$>(\"$full_list_type$\");\n"
-                                                                 "";
+        "    qRegisterMetaType<$list_type$>(\"$full_list_type$\");\n"
+        "";
 const char *Templates::ManualRegistrationGlobalEnumDefinition = "void $enum_gadget$::registerTypes()\n{\n"
-                                                                 "";
+                                                                "";
 const char *Templates::ComplexGlobalEnumFieldRegistrationTemplate = "qRegisterMetaType<$type$>(\"$full_type$\");\n";
 const char *Templates::ComplexListTypeUsingTemplate = "using $classname$Repeated = QList<QSharedPointer<$classname$>>;\n";
 const char *Templates::MapTypeUsingTemplate = "using $type$ = QMap<$key_type$, $value_type$>;\n";
 const char *Templates::MessageMapTypeUsingTemplate = "using $type$ = QMap<$key_type$, QSharedPointer<$value_type$>>;\n";
-const char *Templates::NestedMessageUsingTemplate = "using $type$ = $scope_namespaces$_QtProtobufNested::$type$;\n"
-                                                    "using $list_type$ = $scope_namespaces$_QtProtobufNested::$list_type$;\n";
+const char *Templates::NestedMessageUsingTemplate = "using $type$ = $scope_namespaces$::$type$;\n"
+                                                    "using $list_type$ = $scope_namespaces$::$list_type$;\n";
 
 const char *Templates::EnumTypeRepeatedTemplate = "using $list_type$ = QList<$type$>;\n";
 
 const char *Templates::NamespaceTemplate = "namespace $namespace$ {\n";
 const char *Templates::UsingNamespaceTemplate = "using namespace $namespace$;\n";
 const char *Templates::ClassDeclarationTemplate = "\nclass $classname$ : public QObject\n"
-                                                         "{\n"
-                                                         "    Q_OBJECT\n";
+                                                  "{\n"
+                                                  "    Q_OBJECT\n";
 const char *Templates::ProtoClassForwardDeclarationTemplate = "class $classname$;\n";
 const char *Templates::ProtoClassDeclarationBeginTemplate = "\nclass $classname$ : public QObject\n"
-                                                      "{\n"
-                                                      "    Q_OBJECT\n"
-                                                      "    Q_PROTOBUF_OBJECT\n"
-                                                      "    Q_DECLARE_PROTOBUF_SERIALIZERS($classname$)\n";
+                                                            "{\n"
+                                                            "    Q_OBJECT\n"
+                                                            "    Q_PROTOBUF_OBJECT\n"
+                                                            "    Q_DECLARE_PROTOBUF_SERIALIZERS($classname$)\n";
 
 const char *Templates::PropertyTemplate = "Q_PROPERTY($property_type$ $property_name$ READ $property_name$ WRITE set$property_name_cap$ NOTIFY $property_name$Changed SCRIPTABLE $scriptable$)\n";
 const char *Templates::RepeatedPropertyTemplate = "Q_PROPERTY($property_list_type$ $property_name$ READ $property_name$ WRITE set$property_name_cap$ NOTIFY $property_name$Changed SCRIPTABLE $scriptable$)\n";
@@ -99,7 +100,7 @@ const char *Templates::ProtoConstructorEndTemplate = "QObject *parent = nullptr)
 
 const char *Templates::MemberTemplate = "$scope_type$ m_$property_name$;\n";
 const char *Templates::ListMemberTemplate = "$scope_list_type$ m_$property_name$;\n";
-const char *Templates::ComplexMemberTemplate = "std::unique_ptr<$scope_type$> m_$property_name$;\n";
+const char *Templates::ComplexMemberTemplate = "QProtobufLazyMessagePointer<$scope_type$> m_$property_name$;\n";
 const char *Templates::PublicBlockTemplate = "\npublic:\n";
 const char *Templates::PrivateBlockTemplate = "\nprivate:\n";
 const char *Templates::EnumDefinitionTemplate = "enum $type$ {\n";
@@ -121,16 +122,26 @@ const char *Templates::EmptyMoveConstructorDefinitionTemplate = "$classname$::$c
 const char *Templates::DeletedCopyConstructorTemplate = "$classname$(const $classname$ &) = delete;\n";
 const char *Templates::DeletedMoveConstructorTemplate = "$classname$($classname$ &&) = delete;\n";
 const char *Templates::CopyFieldTemplate = "set$property_name_cap$(other.m_$property_name$);\n";
-const char *Templates::CopyComplexFieldTemplate = "set$property_name_cap$(*other.m_$property_name$);\n";
-const char *Templates::MoveMessageFieldTemplate = "*m_$property_name$ = std::move(*other.m_$property_name$);\n";
+const char *Templates::CopyComplexFieldTemplate = "if (m_$property_name$ != other.m_$property_name$) {\n"
+                                                  "    *m_$property_name$ = *other.m_$property_name$;\n"
+                                                  "}\n";
+const char *Templates::AssignComplexFieldTemplate = "if (m_$property_name$ != other.m_$property_name$) {\n"
+                                                    "    *m_$property_name$ = *other.m_$property_name$;\n"
+                                                    "    $property_name$Changed();\n"
+                                                    "}\n";
+const char *Templates::MoveMessageFieldTemplate = "if (m_$property_name$ != other.m_$property_name$) {\n"
+                                                  "    *m_$property_name$ = std::move(*other.m_$property_name$);\n"
+                                                  "}\n";
+const char *Templates::MoveAssignMessageFieldTemplate = "if (m_$property_name$ != other.m_$property_name$) {\n"
+                                                        "    *m_$property_name$ = std::move(*other.m_$property_name$);\n"
+                                                        "    $property_name$Changed();\n"
+                                                        "    other.$property_name$Changed();\n"
+                                                        "}\n";
 const char *Templates::MoveComplexFieldTemplate = "if (m_$property_name$ != other.m_$property_name$) {\n"
                                                   "    m_$property_name$ = std::move(other.m_$property_name$);\n"
                                                   "    $property_name$Changed();\n"
                                                   "    other.$property_name$Changed();\n"
-                                                  "} else {\n"
-                                                  "    m_$property_name$ = std::move(other.m_$property_name$);\n"
-                                                  "    other.$property_name$Changed();\n"
-                                                  "}\n";
+                                                  "}";
 
 const char *Templates::MoveComplexFieldConstructorTemplate = "m_$property_name$ = std::move(other.m_$property_name$);\n"
                                                              "other.$property_name$Changed();\n";
@@ -155,82 +166,76 @@ const char *Templates::EmptyEqualOperatorDefinitionTemplate = "bool $classname$:
                                                               "    return true;\n"
                                                               "}\n\n";
 const char *Templates::EqualOperatorPropertyTemplate = "m_$property_name$ == other.m_$property_name$";
-const char *Templates::EqualOperatorMessagePropertyTemplate = "*m_$property_name$ == *other.m_$property_name$";
+const char *Templates::EqualOperatorMessagePropertyTemplate = "m_$property_name$ == other.m_$property_name$\n";
 
 const char *Templates::NotEqualOperatorDeclarationTemplate = "bool operator !=(const $classname$ &other) const;\n";
 const char *Templates::NotEqualOperatorDefinitionTemplate = "bool $classname$::operator !=(const $classname$ &other) const\n{\n"
-                                                  "    return !this->operator ==(other);\n"
-                                                  "}\n\n";
+                                                            "    return !this->operator ==(other);\n"
+                                                            "}\n\n";
 
 const char *Templates::GetterPrivateMessageDeclarationTemplate = "$getter_type$ *$property_name$_p() const;\n";
 const char *Templates::GetterPrivateMessageDefinitionTemplate = "$getter_type$ *$classname$::$property_name$_p() const\n{\n"
-                                        "    return m_$property_name$.get();\n"
-                                        "}\n\n";
+                                                                "    return m_$property_name$.get();\n"
+                                                                "}\n\n";
 
 const char *Templates::GetterMessageDeclarationTemplate = "const $getter_type$ &$property_name$() const;\n";
 const char *Templates::GetterMessageDefinitionTemplate = "const $getter_type$ &$classname$::$property_name$() const\n{\n"
-                                        "    return *m_$property_name$;\n"
-                                        "}\n\n";
+                                                         "    return *m_$property_name$;\n"
+                                                         "}\n\n";
 
 const char *Templates::GetterTemplate = "$getter_type$ $property_name$() const {\n"
                                         "    return m_$property_name$;\n"
                                         "}\n\n";
 
 const char *Templates::NonScriptableGetterTemplate = "$qml_alias_type$ $property_name$_p() const {\n"
-                                        "    return m_$property_name$;\n"
-                                        "}\n\n";
+                                                     "    return m_$property_name$;\n"
+                                                     "}\n\n";
 
 const char *Templates::GetterContainerExtraTemplate = "$getter_type$ &$property_name$() {\n"
-                                        "    return m_$property_name$;\n"
-                                        "}\n\n";
+                                                      "    return m_$property_name$;\n"
+                                                      "}\n\n";
 
 const char *Templates::GetterQmlListDeclarationTemplate = "QQmlListProperty<$scope_type$> $property_name$_l();\n";
 const char *Templates::GetterQmlListDefinitionTemplate = "QQmlListProperty<$full_type$> $classname$::$property_name$_l()\n{\n"
-                                               "    return QtProtobuf::constructQmlListProperty<$scope_type$>(this, &m_$property_name$);\n"
-                                               "}\n\n";
+                                                         "    return QtProtobuf::constructQmlListProperty<$scope_type$>(this, &m_$property_name$);\n"
+                                                         "}\n\n";
 
 const char *Templates::SetterPrivateTemplateDeclarationMessageType = "void set$property_name_cap$_p($setter_type$ *$property_name$);\n";
 const char *Templates::SetterPrivateTemplateDefinitionMessageType = "void $classname$::set$property_name_cap$_p($setter_type$ *$property_name$)\n{\n"
-                                                   "    if ($property_name$ == nullptr) {\n"
-                                                   "        *m_$property_name$ = {};\n"
-                                                   "        return;\n"
-                                                   "    }\n"
-                                                   "    if (*m_$property_name$ != *$property_name$) {\n"
-                                                   "        *m_$property_name$ = *$property_name$;\n"
-                                                   "        $property_name$Changed();\n"
-                                                   "    }\n"
-                                                   "    //NOTE: take ownership of value\n"
-                                                   "    delete $property_name$;\n"
-                                                   "}\n\n";
+                                                                    "    if (m_$property_name$.get() != $property_name$) {\n"
+                                                                    "        m_$property_name$.reset($property_name$);\n"
+                                                                    "        $property_name$Changed();\n"
+                                                                    "    }\n"
+                                                                    "}\n\n";
 
 const char *Templates::SetterTemplateDeclarationMessageType = "void set$property_name_cap$(const $setter_type$ &$property_name$);\n";
 const char *Templates::SetterTemplateDefinitionMessageType = "void $classname$::set$property_name_cap$(const $setter_type$ &$property_name$)\n{\n"
-                                                   "    if (*m_$property_name$ != $property_name$) {\n"
-                                                   "        *m_$property_name$ = $property_name$;\n"
-                                                   "        $property_name$Changed();\n"
-                                                   "    }\n"
-                                                   "}\n\n";
+                                                             "    if (*m_$property_name$ != $property_name$) {\n"
+                                                             "        *m_$property_name$ = $property_name$;\n"
+                                                             "        $property_name$Changed();\n"
+                                                             "    }\n"
+                                                             "}\n\n";
 
 const char *Templates::SetterTemplateDeclarationComplexType = "void set$property_name_cap$(const $setter_type$ &$property_name$);\n";
 const char *Templates::SetterTemplateDefinitionComplexType = "void $classname$::set$property_name_cap$(const $setter_type$ &$property_name$)\n{\n"
-                                                   "    if (m_$property_name$ != $property_name$) {\n"
-                                                   "        m_$property_name$ = $property_name$;\n"
-                                                   "        $property_name$Changed();\n"
-                                                   "    }\n"
-                                                   "}\n\n";
+                                                             "    if (m_$property_name$ != $property_name$) {\n"
+                                                             "        m_$property_name$ = $property_name$;\n"
+                                                             "        $property_name$Changed();\n"
+                                                             "    }\n"
+                                                             "}\n\n";
 
 const char *Templates::SetterTemplate = "void set$property_name_cap$(const $setter_type$ &$property_name$) {\n"
-                                                   "    if (m_$property_name$ != $property_name$) {\n"
-                                                   "        m_$property_name$ = $property_name$;\n"
-                                                   "        $property_name$Changed();\n"
-                                                   "    }\n"
-                                                   "}\n\n";
+                                        "    if (m_$property_name$ != $property_name$) {\n"
+                                        "        m_$property_name$ = $property_name$;\n"
+                                        "        $property_name$Changed();\n"
+                                        "    }\n"
+                                        "}\n\n";
 const char *Templates::NonScriptableSetterTemplate = "void set$property_name_cap$_p(const $qml_alias_type$ &$property_name$) {\n"
-                                                   "    if (m_$property_name$ != $property_name$) {\n"
-                                                   "        m_$property_name$ = $property_name$;\n"
-                                                   "        $property_name$Changed();\n"
-                                                   "    }\n"
-                                                   "}\n\n";
+                                                     "    if (m_$property_name$ != $property_name$) {\n"
+                                                     "        m_$property_name$ = $property_name$;\n"
+                                                     "        $property_name$Changed();\n"
+                                                     "    }\n"
+                                                     "}\n\n";
 
 const char *Templates::SignalsBlockTemplate = "\nsignals:\n";
 const char *Templates::SignalTemplate = "void $property_name$Changed();\n";
@@ -247,7 +252,7 @@ const char *Templates::EmptyBlockTemplate = "{}\n\n";
 const char *Templates::PropertyInitializerTemplate = "\n    , m_$property_name$($property_name$)";
 const char *Templates::PropertyDefaultInitializerTemplate = "\n    , m_$property_name$($initializer$)";
 const char *Templates::MessagePropertyInitializerTemplate = "\n    , m_$property_name$(new $scope_type$($property_name$))";
-const char *Templates::MessagePropertyDefaultInitializerTemplate = "\n    , m_$property_name$(new $scope_type$)";
+const char *Templates::MessagePropertyDefaultInitializerTemplate = "\n    , m_$property_name$(nullptr)";
 const char *Templates::ConstructorContentTemplate = "\n{\n}\n";
 
 const char *Templates::DeclareMetaTypeTemplate = "Q_DECLARE_METATYPE($full_type$)\n";
@@ -292,8 +297,8 @@ const char *Templates::ServerMethodDeclarationTemplate = "Q_INVOKABLE virtual $r
 
 
 const char *Templates::ClientConstructorDefinitionTemplate = "\n$classname$::$classname$(QObject *parent) : $parent_class$(\"$service_name$\", parent)\n"
-                                                           "{\n"
-                                                           "}\n";
+                                                             "{\n"
+                                                             "}\n";
 const char *Templates::ClientMethodDefinitionSyncTemplate = "\nQtProtobuf::QGrpcStatus $classname$::$method_name$(const $param_type$ &$param_name$, const QPointer<$return_type$> &$return_name$)\n"
                                                             "{\n"
                                                             "    return call(\"$method_name$\", $param_name$, $return_name$);\n"
@@ -338,8 +343,7 @@ const char *Templates::RegisterSerializersTemplate = "qRegisterProtobufType<$cla
 const char *Templates::RegisterEnumSerializersTemplate = "qRegisterProtobufEnumType<$full_type$>();\n";
 const char *Templates::RegistrarTemplate = "static QtProtobuf::ProtoTypeRegistrar<$classname$> ProtoTypeRegistrar$classname$(qRegisterProtobufType<$classname$>);\n";
 const char *Templates::EnumRegistrarTemplate = "static QtProtobuf::ProtoTypeRegistrar<$enum_gadget$> ProtoTypeRegistrar$enum_gadget$($enum_gadget$::registerTypes);\n";
-const char *Templates::QmlRegisterTypeTemplate = "qmlRegisterType<$full_type$>(\"$qml_package$\", 1, 0, \"$type$\");\n";
-const char *Templates::QmlRegisterTypeUncreatableTemplate = "qmlRegisterUncreatableType<$full_name$>(\"$qml_package$\", 1, 0, \"$type$\", \"$full_type$ Could not be created from qml context\");\n";
+const char *Templates::QmlRegisterTypeTemplate = "qmlRegisterType<$scope_type$>(\"$qml_package$\", 1, 0, \"$type$\");\n";
 const char *Templates::QmlRegisterEnumTypeTemplate = "qmlRegisterUncreatableType<$enum_gadget$>(\"$qml_package$\", 1, 0, \"$type$\", \"$full_type$ Could not be created from qml context\");\n";
 
 

+ 2 - 1
src/generator/templates.h

@@ -107,7 +107,9 @@ public:
     static const char *DeletedMoveConstructorTemplate;
     static const char *CopyFieldTemplate;
     static const char *CopyComplexFieldTemplate;
+    static const char *AssignComplexFieldTemplate;
     static const char *MoveMessageFieldTemplate;
+    static const char *MoveAssignMessageFieldTemplate;
     static const char *MoveComplexFieldTemplate;
     static const char *MoveComplexFieldConstructorTemplate;
     static const char *MoveFieldTemplate;
@@ -135,7 +137,6 @@ public:
     static const char *GetterContainerExtraTemplate;
     static const char *GetterQmlListDeclarationTemplate;
     static const char *GetterQmlListDefinitionTemplate;
-
     static const char *SetterPrivateTemplateDeclarationMessageType;
     static const char *SetterPrivateTemplateDefinitionMessageType;
     static const char *SetterTemplateDeclarationMessageType;

+ 4 - 2
src/protobuf/CMakeLists.txt

@@ -57,7 +57,8 @@ file(GLOB HEADERS
     qprotobufselfcheckiterator.h
     qprotobufmetaproperty.h
     qprotobufmetaobject.h
-    qprotobufserializationplugininterface.h)
+    qprotobufserializationplugininterface.h
+    qprotobuflazymessagepointer.h)
 
 file(GLOB PUBLIC_HEADER
     qtprotobufglobal.h
@@ -72,7 +73,8 @@ file(GLOB PUBLIC_HEADER
     qprotobufselfcheckiterator.h
     qprotobufmetaproperty.h
     qprotobufmetaobject.h
-    qprotobufserializationplugininterface.h)
+    qprotobufserializationplugininterface.h
+    qprotobuflazymessagepointer.h)
 
 protobuf_generate_qt_headers(PUBLIC_HEADER ${PUBLIC_HEADER} COMPONENT ${TARGET})
 

+ 109 - 0
src/protobuf/qprotobuflazymessagepointer.h

@@ -0,0 +1,109 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once //QProtobufLazyMessagePointer
+
+#include "qtprotobufglobal.h"
+#include <QQmlEngine>
+#include <QObject>
+
+#include <memory>
+#include <type_traits>
+
+template <typename T>
+class QProtobufLazyMessagePointer {//TODO: final?
+public:
+    QProtobufLazyMessagePointer(T *p = nullptr) : m_ptr(p) {}
+
+    virtual ~QProtobufLazyMessagePointer() {
+        checkAndRelease();
+        QObject::disconnect(m_destroyed);
+    }
+
+    typename std::add_lvalue_reference<T>::type operator *() const {
+        if (m_ptr == nullptr) {
+            m_ptr.reset(new T);
+        }
+        return *m_ptr;
+    }
+
+    T *operator->() const noexcept {
+        if (m_ptr == nullptr) {
+            m_ptr.reset(new T);
+        }
+        m_ptr.operator->();
+    }
+
+    T *get() const {
+        if (m_ptr == nullptr) {
+            m_ptr.reset(new T);
+        }
+        return m_ptr.get();
+    }
+
+    bool operator ==(const QProtobufLazyMessagePointer &other) const {
+        return (m_ptr == nullptr && other.m_ptr == nullptr)
+                || (other.m_ptr == nullptr && *m_ptr == T{})
+                || (m_ptr == nullptr && *other.m_ptr == T{})
+                || (m_ptr != nullptr && other.m_ptr != nullptr && *m_ptr == *other.m_ptr);
+    }
+
+    bool operator !=(const QProtobufLazyMessagePointer &other) const {
+        return !operator ==(other);
+    }
+
+    void reset(T *p) const {
+        checkAndRelease();
+        QObject::disconnect(m_destroyed);
+        m_destroyed = QObject::connect(p, &QObject::destroyed, [this] {
+            QObject::disconnect(m_destroyed);
+            m_ptr.release();
+        });
+
+        m_ptr.reset(p);
+    }
+
+    QProtobufLazyMessagePointer(QProtobufLazyMessagePointer &&other) : m_ptr(std::move(other.m_ptr)) {}
+    QProtobufLazyMessagePointer &operator =(QProtobufLazyMessagePointer &&other) {
+        m_ptr = std::move(other.m_ptr);
+    }
+
+    explicit operator bool() const noexcept {
+        return m_ptr.operator bool();
+    }
+
+private:
+    void checkAndRelease() const {
+        if (m_ptr != nullptr && QQmlEngine::objectOwnership(m_ptr.get()) == QQmlEngine::JavaScriptOwnership) {
+            m_ptr.release();//In case if object owned by QML it's qml responsibility to clean it up
+            QObject::disconnect(m_destroyed);
+        }
+    }
+
+    QProtobufLazyMessagePointer(const QProtobufLazyMessagePointer&) = delete;
+    QProtobufLazyMessagePointer &operator =(const QProtobufLazyMessagePointer&) = delete;
+    mutable std::unique_ptr<T> m_ptr;
+    mutable QMetaObject::Connection m_destroyed;
+};

+ 3 - 0
tests/test_protobuf/CMakeLists.txt

@@ -12,6 +12,9 @@ file(GLOB SOURCES
     jsondeserializationtest.cpp
     duplicatedmetatypestest.cpp
     nestedtest.cpp)
+if(NOT WIN32)
+    list(APPEND SOURCES internalstest.cpp)
+endif()
 
 add_test_target(TARGET ${TARGET}
     SOURCES ${SOURCES}

+ 71 - 0
tests/test_protobuf/internalstest.cpp

@@ -0,0 +1,71 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define private public
+#define protected public
+#include "simpletest.qpb.h"
+#include "sequencetest.qpb.h"
+#include "externalpackagetest.qpb.h"
+#include "globalenums.qpb.h"
+#include "globalenumssamenamespace.qpb.h"
+#include "nestedmessages.qpb.h"
+#undef private
+#undef protected
+
+#include <gtest/gtest.h>
+#include "../testscommon.h"
+
+using namespace qtprotobufnamespace::tests;
+using namespace qtprotobufnamespace::tests::nested;
+using namespace qtprotobufnamespace::tests::sequence;
+
+namespace QtProtobuf {
+namespace tests {
+
+class InternalsTest : public ::testing::Test
+{
+public:
+    InternalsTest() = default;
+};
+
+TEST_F(InternalsTest, NullPointerMessageTest)
+{
+    ComplexMessage msg(0, {QString("not null")});
+    msg.setTestComplexField_p(nullptr);
+    ASSERT_TRUE(msg.testComplexField().testFieldString().isEmpty());
+    ASSERT_TRUE(msg.testComplexField_p() != nullptr);
+}
+
+TEST_F(InternalsTest, NullPointerGetterMessageTest)
+{
+    ComplexMessage msg;
+    ASSERT_TRUE(msg.testComplexField_p() != nullptr);
+    msg.setTestComplexField_p(nullptr);
+    ASSERT_TRUE(msg.testComplexField().testFieldString().isEmpty());
+    ASSERT_TRUE(msg.testComplexField_p() != nullptr);
+}
+
+}
+}

+ 11 - 0
tests/test_protobuf/nestedtest.cpp.inc

@@ -199,5 +199,16 @@ TEST_F(NestedTest, NestedNoFieldsTest)
     ASSERT_TRUE(test.testFieldInt() == 55);
 }
 
+TEST_F(NestedTest, NestedCyclingTest)
+{
+    assertMessagePropertyRegistered<NestedCyclingA::NestedCyclingB, qtprotobufnamespace::tests::nested::NestedCyclingAA::NestedCyclingBB*>(1, "qtprotobufnamespace::tests::nested::NestedCyclingAA::NestedCyclingBB*", "testField");
+    assertMessagePropertyRegistered<NestedCyclingAA::NestedCyclingBB, qtprotobufnamespace::tests::nested::NestedCyclingA::NestedCyclingB*>(1, "qtprotobufnamespace::tests::nested::NestedCyclingA::NestedCyclingB*", "testField");
+
+    NestedCyclingA::NestedCyclingB test;
+    NestedCyclingAA::NestedCyclingBB test2;
+    test.setTestField(test2);
+    test2.setTestField(test);
+}
+
 }
 }

+ 11 - 0
tests/test_protobuf/proto/nestedmessages.proto

@@ -37,3 +37,14 @@ message NestedNoFields {
   }
 }
 
+message NestedCyclingA {
+  message NestedCyclingB {
+    NestedCyclingAA.NestedCyclingBB testField = 1;
+  }
+}
+
+message NestedCyclingAA {
+  message NestedCyclingBB {
+    NestedCyclingA.NestedCyclingB testField = 1;
+  }
+}

+ 4 - 7
tests/test_protobuf/simpletest.cpp.inc

@@ -698,13 +698,6 @@ TEST_F(SimpleTest, EmptyMessageTest)
     ASSERT_EQ(EmptyMessage::staticMetaObject.propertyCount(), 1);
 }
 
-TEST_F(SimpleTest, NullPointerMessageTest)
-{
-    ComplexMessage msg(0, {QString("not null")});
-    msg.setTestComplexField_p(nullptr);
-    ASSERT_TRUE(msg.testComplexField().testFieldString().isEmpty());
-}
-
 TEST_F(SimpleTest, AssignmentOperatorTest)
 {
     const char *propertyName = "testFieldInt";
@@ -790,6 +783,10 @@ TEST_F(SimpleTest, CyclingTest)
 {
     assertMessagePropertyRegistered<sequence::CyclingFirstDependency, sequence::CyclingSecondDependency*>(1, "qtprotobufnamespace::tests::sequence::CyclingSecondDependency*", "testField");
     assertMessagePropertyRegistered<sequence::CyclingSecondDependency, sequence::CyclingFirstDependency*>(1, "qtprotobufnamespace::tests::sequence::CyclingFirstDependency*", "testField");
+    sequence::CyclingFirstDependency test;
+    sequence::CyclingSecondDependency test2;
+    test.setTestField(test2);
+    test2.setTestField(test);
 }
 
 TEST_F(SimpleTest, UpperCaseTest)

+ 2 - 0
tests/test_protobuf_multifile/nestedtest.cpp

@@ -28,5 +28,7 @@
 #include "qtprotobufnamespace/tests/nested/nestedfieldmessage2.h"
 #include "qtprotobufnamespace/tests/nested/nestedexternal.h"
 #include "qtprotobufnamespace/tests/nested/nestednofields.h"
+#include "qtprotobufnamespace/tests/nested/nestedcyclinga.h"
+#include "qtprotobufnamespace/tests/nested/nestedcyclingaa.h"
 
 #include "../test_protobuf/nestedtest.cpp.inc"

+ 43 - 0
tests/test_qml/qml/tst_simple.qml

@@ -28,6 +28,8 @@ import QtTest 1.0
 
 import QtProtobuf 0.5
 import qtprotobufnamespace.tests 1.0
+import qtprotobufnamespace.tests.nested 1.0
+import qtprotobufnamespace.tests.nested.NestedFieldMessage 1.0 as NestedFieldMessage_
 
 TestCase {
     name: "Simple values assignment"
@@ -76,6 +78,29 @@ TestCase {
         id: lowerCaseMsg
     }
 
+    ComplexMessage {
+        id: complexMsg
+        testComplexField: SimpleStringMessage {
+            id: innerMessage
+            testFieldString: "inner"
+        }
+    }
+
+    SimpleStringMessage {
+        id: outerMessage
+        testFieldString: "outer"
+    }
+
+    NestedFieldMessage {
+        id: nestedParent
+        nested: nestedMsg
+    }
+
+    NestedFieldMessage_.NestedMessage {
+        id: nestedMsg
+        testFieldInt: 100
+    }
+
     function test_1initialization() {
         compare(int32Msg.testFieldInt, 2147483647, "SimpleIntMessage initialization")
         compare(sint32Msg.testFieldInt, 2147483647, "SimpleSIntMessage initialization")
@@ -289,4 +314,22 @@ TestCase {
         compare(sfixed32Msg.testFieldFixedInt32.toLocaleString(Qt.locale()), Number(sfixed32Msg.testFieldFixedInt32).toLocaleString(Qt.locale()),
                 "Locale number string is not match " + sfixed32Msg.testFieldFixedInt32.toLocaleString(Qt.locale()) + " != " + Number(sfixed32Msg.testFieldFixedInt32).toLocaleString(Qt.locale()))
     }
+
+    function test_complexMessage() {
+        compare(complexMsg.testComplexField, innerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "inner", "Invalid value of object inside complex message")
+
+        complexMsg.testComplexField = outerMessage
+        compare(complexMsg.testComplexField, outerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "outer", "Invalid value of object inside complex message")
+
+        complexMsg.testComplexField = innerMessage
+        compare(complexMsg.testComplexField, innerMessage, "Invalid object is inside complex message")
+        compare(complexMsg.testComplexField.testFieldString, "inner", "Invalid value of object inside complex message")
+    }
+
+    function test_nestedMessage() {
+        compare(nestedMsg.testFieldInt, 100, "Nested message initialization failed");
+        compare(nestedParent.nested, nestedMsg, "Nested message assignment failed");
+    }
 }