diff --git a/include/Logger.h b/include/Logger.h new file mode 100644 index 0000000..92962b0 --- /dev/null +++ b/include/Logger.h @@ -0,0 +1,89 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class Logger { +public: + enum class Severity { + Debug, + Info, + Warning, + Error + }; + + // Получить логгер по категории (создаёт, если нет) + static Logger& get(const std::string& category) { + std::lock_guard lock(registry_mutex_); + auto it = registry_.find(category); + if (it == registry_.end()) { + auto logger = std::unique_ptr(new Logger(category)); + auto [inserted_it, _] = registry_.emplace(category, std::move(logger)); + return *(inserted_it->second); + } + return *(it->second); + } + + // Настроить suppression для категории + static void suppressCategory(const std::string& category) { + get(category).suppressed_ = true; + } + static void unsuppressCategory(const std::string& category) { + get(category).suppressed_ = false; + } + static void setMinSeverity(const std::string& category, Severity level) { + get(category).min_severity_ = level; + } + static Severity getMinSeverity(const std::string& category) { + return get(category).min_severity_; + } + static bool isSuppressed(const std::string& category) { + return get(category).suppressed_; + } + + void log(const std::string& message, Severity severity = Severity::Info) const { + if (suppressed_) return; + if (severity < min_severity_) return; + std::cout << "[" << category_ << "] " << severityToString(severity) << ": " << message << std::endl << std::flush; + } + + // Удобные методы для логирования по уровню + void dbg(const std::string& message) const { log(message, Severity::Debug); } + void info(const std::string& message) const { log(message, Severity::Info); } + void warn(const std::string& message) const { log(message, Severity::Warning); } + void err(const std::string& message) const { log(message, Severity::Error); } + + static std::string severityToString(Severity severity) { + switch (severity) { + case Severity::Debug: return "DEBUG"; + case Severity::Info: return "INFO"; + case Severity::Warning: return "WARNING"; + case Severity::Error: return "ERROR"; + default: return "UNKNOWN"; + } + } + + const std::string& category() const { return category_; } + Severity minSeverity() const { return min_severity_; } + void setMinSeverity(Severity s) { min_severity_ = s; } + bool suppressed() const { return suppressed_; } + void setSuppressed(bool s) { suppressed_ = s; } + +private: + explicit Logger(std::string category) + : category_(std::move(category)), min_severity_(Severity::Debug), suppressed_(false) {} + + std::string category_; + Severity min_severity_; + bool suppressed_; + + static std::unordered_map> registry_; + static std::mutex registry_mutex_; +}; + +// Определения статических членов +inline std::unordered_map> Logger::registry_{}; +inline std::mutex Logger::registry_mutex_; diff --git a/include/ifaces/ILink.h b/include/ifaces/ILink.h index 844a517..0350e47 100644 --- a/include/ifaces/ILink.h +++ b/include/ifaces/ILink.h @@ -13,4 +13,5 @@ public: virtual const std::vector>& getChildren() const = 0; virtual void addChild(const std::shared_ptr& child) = 0; virtual void removeChild(const std::shared_ptr& child) = 0; + virtual void replaceChild(const std::shared_ptr& oldChild, const std::shared_ptr& newChild) = 0; }; diff --git a/include/ifaces/INode.h b/include/ifaces/INode.h index fa67972..42c313f 100644 --- a/include/ifaces/INode.h +++ b/include/ifaces/INode.h @@ -10,4 +10,5 @@ class INode : public virtual ILinkMixin { public: ~INode() = default; virtual const std::string& name() const = 0; + virtual const std::string& kind() const = 0; }; diff --git a/include/links/BaseLink.h b/include/links/BaseLink.h index b60d1b4..1dc4d30 100644 --- a/include/links/BaseLink.h +++ b/include/links/BaseLink.h @@ -3,6 +3,7 @@ #include #include "ifaces/ILink.h" +#include "Logger.h" template class BaseLink : public ILink { @@ -12,12 +13,24 @@ public: std::shared_ptr getParent() const override { return parent_.lock(); } void setParent(const std::shared_ptr& parent) override { parent_ = parent; } const std::vector>& getChildren() const override { return children_; } + + void addChild(const std::shared_ptr& child) override { + this->children_.push_back(child); + } + void removeChild(const std::shared_ptr& child) override { children_.erase(std::remove(children_.begin(), children_.end(), child), children_.end()); } + void replaceChild(const std::shared_ptr& oldChild, const std::shared_ptr& newChild) override { + auto it = std::find(children_.begin(), children_.end(), oldChild); + if (it != children_.end()) { + *it = newChild; + } + } + ~BaseLink() override { - std::cout << "--- Destructor called for: " << "BaseLink" << "\n"; + Logger::get("Link").dbg("--- Destructor called for: BaseLink"); } protected: std::vector> children_; diff --git a/include/links/LeafLink.h b/include/links/LeafLink.h index 081f44e..a4dd067 100644 --- a/include/links/LeafLink.h +++ b/include/links/LeafLink.h @@ -5,5 +5,7 @@ class LeafLink : public BaseLink { public: using BaseLink::BaseLink; - void addChild(const NodePtr&) override { throw std::logic_error("LeafLink cannot have children"); } + void addChild(const NodePtr&) override { + throw std::logic_error("LeafLink cannot have children"); + } }; diff --git a/include/links/NotImplementedLink.h b/include/links/NotImplementedLink.h new file mode 100644 index 0000000..ed38c65 --- /dev/null +++ b/include/links/NotImplementedLink.h @@ -0,0 +1,24 @@ +#pragma once +#include + +#include "ifaces/ILink.h" +#include "Logger.h" + +template +class NotImplementedLink : public ILink { + using ElemPtr = std::shared_ptr>; +public: + NotImplementedLink(ElemPtr node) {} + ElemPtr getNode() const override { return nullptr; } + ElemPtr getParent() const override { return nullptr; } + void setParent(const ElemPtr& parent) override { } + const std::vector& getChildren() const override { return empty_; } + void addChild(const ElemPtr& child) override { } + void removeChild(const ElemPtr& child) override { } + + ~NotImplementedLink() override { + Logger::get("Link").dbg("--- Destructor called for: NotImplementedLink"); + } +private: + std::vector empty_; +}; diff --git a/include/links/OneToManyLink.h b/include/links/OneToManyLink.h index bb18308..5d39026 100644 --- a/include/links/OneToManyLink.h +++ b/include/links/OneToManyLink.h @@ -5,5 +5,10 @@ template class OneToManyLink : public BaseLink { public: OneToManyLink(std::shared_ptr e) : BaseLink(e) {} - void addChild(const std::shared_ptr& child) override { this->children_.push_back(child); } + void addChild(const std::shared_ptr& child) override { + /* Each child must be exactly the same type as parent */ + if (typeid(*child) != typeid(*this->owner_node_.lock())) + throw std::logic_error("Foundling child"); + BaseLink::addChild(child); + } }; diff --git a/include/links/OneToOneLink.h b/include/links/OneToOneLink.h new file mode 100644 index 0000000..684e258 --- /dev/null +++ b/include/links/OneToOneLink.h @@ -0,0 +1,14 @@ +#pragma once +#include "links/BaseLink.h" +#include + +template +class OneToOneLink : public BaseLink { +public: + OneToOneLink(std::shared_ptr e) : BaseLink(e) {} + void addChild(const std::shared_ptr& child) override { + if (!this->children_.empty()) + throw std::logic_error("OneToOneLink cannot have more than one child"); + BaseLink::addChild(child); + } +}; diff --git a/include/mixins/BaseLinkMixin.h b/include/mixins/BaseLinkMixin.h index 299cfe4..507513b 100644 --- a/include/mixins/BaseLinkMixin.h +++ b/include/mixins/BaseLinkMixin.h @@ -3,15 +3,16 @@ #include #include "ifaces/ILinkMixin.h" +#include "Logger.h" class BaseLinkMixin : public virtual ILinkMixin, public std::enable_shared_from_this { public: void linkChild(const MixinPtr& child) override { + getLink()->addChild(child); + LinkPtr childLink = child->getLink(); childLink->setParent(getNode()); - - getLink()->addChild(child); } void unlinkParent() override { @@ -28,7 +29,7 @@ public: } ~BaseLinkMixin() override { - std::cout << "--- Destructor called for: " << "BaseLinkMixin" << "\n"; + Logger::get("Mixin").dbg("--- Destructor called for: BaseLinkMixin"); } protected: diff --git a/include/mixins/HierarchicalLinkMixin.h b/include/mixins/HierarchicalLinkMixin.h new file mode 100644 index 0000000..2bfb3ab --- /dev/null +++ b/include/mixins/HierarchicalLinkMixin.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include "mixins/LazyLinkMixin.h" +#include +#include "links/OneToManyLink.h" +#include "links/OneToOneLink.h" +#include "Logger.h" + +class HierarchicalLinkMixin : public LazyLinkMixin> { +public: + HierarchicalLinkMixin() {} + + ~HierarchicalLinkMixin() override { + Logger::get("Mixin").dbg("--- Destructor called for: HierarchicalLinkMixin"); + } + + void linkChild(const MixinPtr& child) override { + hierarchicalInit(child); + LazyLinkMixin>::linkChild(child); + } + +protected: + void hierarchicalInit(const MixinPtr& child) { + Logger::get("Mixin").dbg("--- hierarchicalInit called"); + if (this->link_ && !this->link_->getChildren().empty()) + return; // already have children, do nothing + + + /* 1. Have no link_ — + * 2. Has OneToOne with parent or not + * 3. Has OneToOne with child + */ + + LinkPtr newLink; + + if (typeid(*child) == typeid(*this)) { + Logger::get("Mixin").dbg("--- Mutate to OneToMany"); + newLink = std::make_shared>(this->getNode()); + } else { + Logger::get("Mixin").dbg("--- Mutate to OneToOne"); + newLink = std::make_shared>(this->getNode()); + } + + if (newLink && this->link_) + newLink->setParent(this->link_->getParent()); + + this->link_ = newLink; + } +}; diff --git a/include/mixins/LazyLinkMixin.h b/include/mixins/LazyLinkMixin.h index 25765f2..eeec3ac 100644 --- a/include/mixins/LazyLinkMixin.h +++ b/include/mixins/LazyLinkMixin.h @@ -2,6 +2,7 @@ #include #include "mixins/BaseLinkMixin.h" #include +#include "Logger.h" template class LazyLinkMixin : public BaseLinkMixin { @@ -22,7 +23,7 @@ public: LazyLinkMixin() {} ~LazyLinkMixin() override { - std::cout << "--- Destructor called for: " << "LazyLinkMixin" << "\n"; + Logger::get("Mixin").dbg("--- Destructor called for: LazyLinkMixin"); } protected: diff --git a/include/nodes/BaseNode.h b/include/nodes/BaseNode.h index 1c9e9dc..8499cbc 100644 --- a/include/nodes/BaseNode.h +++ b/include/nodes/BaseNode.h @@ -2,16 +2,19 @@ #include #include "ifaces/INode.h" +#include "Logger.h" class BaseNode : public virtual INode { public: BaseNode(std::string name) : name_(std::move(name)) { - std::cout << "--- Base constructor called for: " << name_ << "\n"; + Logger::get("Node").dbg(std::string("--- Base constructor called for: ") + name_); } const std::string& name() const override { return name_; } + const std::string& kind() const override { return kind_; } ~BaseNode() { - std::cout << "--- Base destructor called for: " << name_ << "\n"; + Logger::get("Node").dbg(std::string("--- Base destructor called for: ") + name_); } protected: std::string name_; + std::string kind_; }; diff --git a/include/nodes/ComplexNode.h b/include/nodes/ComplexNode.h new file mode 100644 index 0000000..61ee232 --- /dev/null +++ b/include/nodes/ComplexNode.h @@ -0,0 +1,17 @@ +#pragma once +#include + +#include "nodes/BaseNode.h" +#include "mixins/HierarchicalLinkMixin.h" +#include "Logger.h" + +class ComplexNode : public BaseNode, + virtual public HierarchicalLinkMixin { +public: + ~ComplexNode() { + Logger::get("Node").dbg(std::string("--- Complex destructor called for: ") + name_); + } + ComplexNode(std::string name) : BaseNode(std::move(name)) { + Logger::get("Node").dbg(std::string("--- Complex constructor called for: ") + name_); + } +}; diff --git a/include/nodes/SimpleNode.h b/include/nodes/SimpleNode.h index e225ddd..af8bf10 100644 --- a/include/nodes/SimpleNode.h +++ b/include/nodes/SimpleNode.h @@ -3,15 +3,16 @@ #include "nodes/BaseNode.h" #include "mixins/LazyLinkMixin.h" -#include "links/OneToManyLink.h" +#include "links/OneToOneLink.h" +#include "Logger.h" class SimpleNode : public BaseNode, - virtual public LazyLinkMixin> { + virtual public LazyLinkMixin> { public: ~SimpleNode() { - std::cout << "--- Simple destructor called for: " << name_ << "\n"; + Logger::get("Node").dbg(std::string("--- Simple destructor called for: ") + name_); } SimpleNode(std::string name) : BaseNode(std::move(name)) { - std::cout << "--- Simple constructor called for: " << name_ << "\n"; + Logger::get("Node").dbg(std::string("--- Simple constructor called for: ") + name_); } }; diff --git a/src/main.cpp b/src/main.cpp index 9321e10..05e0c39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,63 +1,126 @@ #include "nodes/SimpleNode.h" +#include "nodes/ComplexNode.h" #include +#include "Logger.h" -void printTree(const MixinPtr& start, int indent = 0) { - if (!start) return; - auto startNode = std::dynamic_pointer_cast(start); - if (!startNode) return; +void printTree(const NodePtr& startNode, int indent = 0) { + if (!startNode) { + std::cout << "No node" << "\n"; + return; + } for (int i = 0; i < indent; ++i) std::cout << " "; std::cout << startNode->name() << "\n"; LinkPtr nodeLink = startNode->getLink(); for (const auto& child : nodeLink->getChildren()) { - printTree(child, indent + 1); + auto childNode = std::dynamic_pointer_cast(child); + printTree(childNode, indent + 1); } } -int main2() { - std::cout << "Creatin...\n\n"; - auto root = std::make_shared("Root"); - std::cout << "Nice!\n\n"; - return 0; -} - int main() { - std::cout << "Entering main scope...\n\n"; + Logger::setMinSeverity("MAIN", Logger::Severity::Debug); + //Logger::suppressCategory("Node"); + Logger::suppressCategory("Link"); + Logger::suppressCategory("Mixin"); + auto& logger = Logger::get("MAIN"); + logger.info("Entering main scope..."); { - auto root = std::make_shared("Root"); - auto child2 = std::make_shared("Child1"); + auto root = std::make_shared("ComplexRoot"); + auto child1 = std::make_shared("ComplexChild1"); + + root->linkChild(child1); - root->linkChild(child2); - std::cout << "\nInit tree:\n\n\n"; + std::cout << "\nInit tree:\n"; printTree(root); + std::cout << "\n"; { - auto child1 = std::make_shared("Child2"); - root->linkChild(child1); - auto subchild = std::make_shared("SubChild2"); - child1->linkChild(subchild); - auto child3 = std::make_shared("Child3"); + auto child2 = std::make_shared("ComplexChild2"); + root->linkChild(child2); + + auto subchild2 = std::make_shared("SimpleSubChild2"); + child2->linkChild(subchild2); + + auto child3 = std::make_shared("ComplexChild3"); root->linkChild(child3); - auto subchild2 = std::make_shared("SubChild3"); - child3->linkChild(subchild2); - std::cout << "\nAnother tree:\n\n\n"; + auto subchild3 = std::make_shared("SimpleSubChild3"); + child3->linkChild(subchild3); + + { + // Негативный сценарий 1: попытка добавить второй SimpleNode к ComplexNode + try { + auto anotherSimple = std::make_shared("ShouldFail"); + child2->linkChild(anotherSimple); + logger.err("[ERROR] Не должно было получиться добавить второй SimpleNode к ComplexNode!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + + // Негативный сценарий 2: попытка добавить ComplexNode к SimpleNode + // Это допустимо: SimpleNode может иметь ComplexNode в качестве единственного ребёнка + auto goodComplex = std::make_shared("GoodComplex"); + subchild2->linkChild(goodComplex); + logger.info("[OK] ComplexNode успешно добавлен к SimpleNode как единственный ребёнок."); + + // Негативный сценарий: попытка добавить второго ребёнка к SimpleNode + try { + auto anotherSimple = std::make_shared("ShouldFail2"); + subchild2->linkChild(anotherSimple); + logger.err("[ERROR] Не должно было получиться добавить второго ребёнка к SimpleNode!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + + // Негативный сценарий: попытка добавить SimpleNode в ComplexNode, который уже содержит несколько ComplexNode-дочерних + try { + auto badSimple = std::make_shared("BadSimple"); + root->linkChild(badSimple); + logger.err("[ERROR] Не должно было получиться добавить SimpleNode в ComplexNode с несколькими ComplexNode-дочерними!"); + } catch (const std::logic_error& e) { + logger.warn(std::string("[Ожидаемое исключение] ") + e.what()); + } + } + + std::cout << "\nAnother tree:\n"; printTree(root); + std::cout << "\n"; + + logger.info("Unlinking ComplexChild2...\n"); + child2->unlinkParent(); - std::cout << "\nUnlinking Child1...\n"; - child1->unlinkParent(); - std::cout << "\nTree after unlink:\n\n\n"; + std::cout << "\nTree after unlink:\n"; printTree(root); - std::cout << "\n\n"; + std::cout << "\n"; + + logger.info("Put refs of ComplexChild2, ComplexChild3 and its children"); } - std::cout << "\nTree after scope out:\n\n\n"; + std::cout << "\nTree after scope out:\n"; + printTree(root); + std::cout << "\n"; + + logger.info("Unlinking ComplexChild1 and ComplexChild3...\n"); + child1->unlinkParent(); + for (auto child : root->getLink()->getChildren()) { + NodePtr childNode = std::dynamic_pointer_cast(child); + if (childNode->name() == "ComplexChild3") { + childNode->unlinkParent(); + break; + } + } + + auto child4 = std::make_shared("SimpleChild4"); + root->linkChild(child4); + + std::cout << "\nTree flush and link SimpleChild4:\n"; printTree(root); - std::cout << "\n\n"; + std::cout << "\n"; } - std::cout << "\nExited main scope. All smart pointers destroyed.\n"; + logger.info("Exited main scope. All smart pointers destroyed."); return 0; }