From bd921822c283633d58cd61c0f8088d5ca3cca1dd Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Thu, 13 Jun 2024 14:19:45 +0200 Subject: [PATCH] Added relationship direction flag to context diagram filter (#274) --- src/common/model/diagram_filter.cc | 94 ++++++++++++++++++++---------- src/common/model/diagram_filter.h | 33 ++++++++++- src/config/config.cc | 14 +++++ src/config/config.h | 5 ++ src/config/schema.h | 5 ++ src/config/yaml_decoders.cc | 27 ++++++++- tests/t00076/.clang-uml | 16 +++++ tests/t00076/t00076.cc | 36 ++++++++++++ tests/t00076/test_case.h | 40 +++++++++++++ tests/test_cases.cc | 3 +- tests/test_cases.yaml | 2 + 11 files changed, 240 insertions(+), 35 deletions(-) create mode 100644 tests/t00076/.clang-uml create mode 100644 tests/t00076/t00076.cc create mode 100644 tests/t00076/test_case.h diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 95689ba02..fb4def129 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -659,10 +659,10 @@ void context_filter::initialize_effective_context( auto &effective_context = effective_contexts_[idx]; // First add to effective context all elements matching context_ patterns - const auto &context = context_.at(idx); + const auto &context_cfg = context_.at(idx); const auto &context_matches = dynamic_cast(d) - .find(context.pattern); + .find(context_cfg.pattern); for (const auto &maybe_match : context_matches) { if (maybe_match) @@ -671,7 +671,7 @@ void context_filter::initialize_effective_context( const auto &context_enum_matches = dynamic_cast(d) - .find(context.pattern); + .find(context_cfg.pattern); for (const auto &maybe_match : context_enum_matches) { if (maybe_match) @@ -680,7 +680,7 @@ void context_filter::initialize_effective_context( const auto &context_concept_matches = dynamic_cast(d) - .find(context.pattern); + .find(context_cfg.pattern); for (const auto &maybe_match : context_concept_matches) { if (maybe_match) @@ -689,7 +689,7 @@ void context_filter::initialize_effective_context( // Now repeat radius times - extend the effective context with elements // matching in direct relationship to what is in context - auto radius_counter = context.radius; + auto radius_counter = context_cfg.radius; std::set current_iteration_context; while (radius_counter > 0 && effective_context_extended) { @@ -701,18 +701,18 @@ void context_filter::initialize_effective_context( // For each class in the model find_elements_in_direct_relationship( - d, effective_context, current_iteration_context); + d, context_cfg, effective_context, current_iteration_context); find_elements_inheritance_relationship( - d, effective_context, current_iteration_context); + d, context_cfg, effective_context, current_iteration_context); // For each concept in the model find_elements_in_direct_relationship( - d, effective_context, current_iteration_context); + d, context_cfg, effective_context, current_iteration_context); // For each enum in the model find_elements_in_direct_relationship( - d, effective_context, current_iteration_context); + d, context_cfg, effective_context, current_iteration_context); for (auto id : current_iteration_context) { if (effective_context.count(id) == 0) { @@ -725,6 +725,7 @@ void context_filter::initialize_effective_context( } void context_filter::find_elements_inheritance_relationship(const diagram &d, + const config::context_config &context_cfg, std::set &effective_context, std::set ¤t_iteration_context) const { @@ -733,36 +734,57 @@ void context_filter::find_elements_inheritance_relationship(const diagram &d, for (const auto &c : cd.classes()) { // Check if any of the elements parents are already in the // effective context... - for (const class_diagram::model::class_parent &p : c.get().parents()) { - for (const auto &ec : effective_context) { - const auto &maybe_parent = - cd.find(ec); - if (!maybe_parent) - continue; - - if (d.should_include(relationship_t::kExtension) && - maybe_parent.value().full_name(false) == p.name()) - current_iteration_context.emplace(c.get().id()); - } - } + if (context_cfg.direction != config::context_direction_t::outward) + find_elements_base_classes( + d, effective_context, current_iteration_context, cd, c); // .. or vice-versa - for (const auto &ec : effective_context) { - const auto &maybe_child = cd.find(ec); + if (context_cfg.direction != config::context_direction_t::inward) + find_elements_sub_classes( + effective_context, current_iteration_context, cd, c); + } +} - // The element might not exist because it might have been - // something other than a class - if (!maybe_child) - continue; +void context_filter::find_elements_sub_classes( + std::set &effective_context, + std::set ¤t_iteration_context, + const class_diagram::model::diagram &cd, + const std::reference_wrapper &c) const +{ + for (const auto &ec : effective_context) { + const auto &maybe_child = cd.find(ec); - for (const auto &p : maybe_child.value().parents()) { - if (p.name() == c.get().full_name(false)) { - current_iteration_context.emplace(c.get().id()); - } + // The element might not exist because it might have been + // something other than a class + if (!maybe_child) + continue; + + for (const auto &p : maybe_child.value().parents()) { + if (p.name() == c.get().full_name(false)) { + current_iteration_context.emplace(c.get().id()); } } } } +void context_filter::find_elements_base_classes(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context, + const class_diagram::model::diagram &cd, + const std::reference_wrapper &c) const +{ + for (const class_diagram::model::class_parent &p : c.get().parents()) { + for (const auto &ec : effective_context) { + const auto &maybe_parent = + cd.find(ec); + if (!maybe_parent) + continue; + + if (d.should_include(relationship_t::kExtension) && + maybe_parent.value().full_name(false) == p.name()) + current_iteration_context.emplace(c.get().id()); + } + } +} void context_filter::initialize(const diagram &d) const { @@ -802,6 +824,16 @@ tvl::value_t context_filter::match(const diagram &d, const element &e) const return false; } +bool context_filter::is_inward(relationship_t r) const +{ + return r == relationship_t::kAssociation; +} + +bool context_filter::is_outward(relationship_t r) const +{ + return r != relationship_t::kAssociation; +} + paths_filter::paths_filter(filter_t type, const std::filesystem::path &root, const std::vector &p) : filter_visitor{type} diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index 6dee4a369..177582887 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -494,8 +494,13 @@ struct context_filter : public filter_visitor { void initialize_effective_context(const diagram &d, unsigned idx) const; + bool is_inward(relationship_t r) const; + + bool is_outward(relationship_t r) const; + template void find_elements_in_direct_relationship(const diagram &d, + const config::context_config &context_cfg, std::set &effective_context, std::set ¤t_iteration_context) const { @@ -511,6 +516,12 @@ struct context_filter : public filter_visitor { // which have a relationship to any of the effective_context // elements for (const relationship &rel : el.get().relationships()) { + if (context_cfg.direction == + config::context_direction_t::outward/* && + !is_inward(rel.type())*/) { + continue; + } + for (const auto &element_id : effective_context) { if (d.should_include(rel.type()) && rel.destination() == element_id) @@ -519,7 +530,9 @@ struct context_filter : public filter_visitor { } // Now search current effective_context elements and add any - // elements of any type in the diagram which to that element + // elements of any type in the diagram which have a relationship + // to that element + for (const auto element_id : effective_context) { const auto &maybe_element = cd.get(element_id); @@ -528,6 +541,12 @@ struct context_filter : public filter_visitor { for (const relationship &rel : maybe_element.value().relationships()) { + if ((context_cfg.direction == + config::context_direction_t::inward) && + (rel.type() != relationship_t::kAggregation && + rel.type() != relationship_t::kComposition)) { + continue; + } if (d.should_include(rel.type()) && rel.destination() == el.get().id()) @@ -538,9 +557,21 @@ struct context_filter : public filter_visitor { } void find_elements_inheritance_relationship(const diagram &d, + const config::context_config &context_cfg, std::set &effective_context, std::set ¤t_iteration_context) const; + void find_elements_base_classes(const diagram &d, + std::set &effective_context, + std::set ¤t_iteration_context, + const class_diagram::model::diagram &cd, + const std::reference_wrapper &c) const; + + void find_elements_sub_classes(std::set &effective_context, + std::set ¤t_iteration_context, + const class_diagram::model::diagram &cd, + const std::reference_wrapper &c) const; + std::vector context_; /*! diff --git a/src/config/config.cc b/src/config/config.cc index 03b472abc..b02b72571 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -171,6 +171,20 @@ std::string to_string(member_order_t mo) return ""; } } +std::string to_string(context_direction_t cd) +{ + switch (cd) { + case context_direction_t::inward: + return "inward"; + case context_direction_t::outward: + return "outward"; + case context_direction_t::any: + return "any"; + default: + assert(false); + return ""; + } +} std::optional plantuml::get_style( const common::model::relationship_t relationship_type) const diff --git a/src/config/config.h b/src/config/config.h index 5dff63993..36503b776 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -159,9 +159,14 @@ struct mermaid { void append(const mermaid &r); }; +enum class context_direction_t { inward, outward, any }; + +std::string to_string(context_direction_t cd); + struct context_config { common::string_or_regex pattern; unsigned radius{0}; + context_direction_t direction{context_direction_t::any}; }; /** diff --git a/src/config/schema.h b/src/config/schema.h index c1fcd8f52..b19a611b5 100644 --- a/src/config/schema.h +++ b/src/config/schema.h @@ -114,10 +114,15 @@ const std::string schema_str = R"( - lambda - cuda_kernel - cuda_device + direction_t: !variant + - inward + - outward + - any context_filter_match_t: match: radius: int pattern: regex_or_string_t + direction: !optional direction_t context_filter_t: - regex_or_string_t - context_filter_match_t diff --git a/src/config/yaml_decoders.cc b/src/config/yaml_decoders.cc index b73eed326..8d4a01481 100644 --- a/src/config/yaml_decoders.cc +++ b/src/config/yaml_decoders.cc @@ -35,6 +35,7 @@ using clanguml::config::callee_type; using clanguml::config::class_diagram; using clanguml::config::config; using clanguml::config::context_config; +using clanguml::config::context_direction_t; using clanguml::config::diagram_template; using clanguml::config::filter; using clanguml::config::generate_links_config; @@ -259,6 +260,25 @@ template <> struct convert { } }; +// +// config context_direction_t decoder +// +template <> struct convert { + static bool decode(const Node &node, context_direction_t &rhs) + { + if (node.as() == "inward") + rhs = context_direction_t::inward; + else if (node.as() == "outward") + rhs = context_direction_t::outward; + else if (node.as() == "any") + rhs = context_direction_t::any; + else + return false; + + return true; + } +}; + // // config method_type decoder // @@ -460,8 +480,11 @@ template <> struct convert { { using namespace std::string_literals; if (node.IsMap() && has_key(node, "match")) { - rhs.radius = node["match"]["radius"].as(); - rhs.pattern = node["match"]["pattern"].as(); + const auto &match = node["match"]; + rhs.radius = match["radius"].as(); + rhs.pattern = match["pattern"].as(); + if (has_key(match, "direction")) + rhs.direction = match["direction"].as(); } else { rhs.radius = 1; diff --git a/tests/t00076/.clang-uml b/tests/t00076/.clang-uml new file mode 100644 index 000000000..dc8d36fd6 --- /dev/null +++ b/tests/t00076/.clang-uml @@ -0,0 +1,16 @@ +diagrams: + t00076_class: + type: class + glob: + - t00076.cc + include: + namespaces: + - clanguml::t00076 + context: + - match: + radius: 1 + pattern: clanguml::t00076::B + direction: inward + #relationships: + # - any + using_namespace: clanguml::t00076 \ No newline at end of file diff --git a/tests/t00076/t00076.cc b/tests/t00076/t00076.cc new file mode 100644 index 000000000..37da15201 --- /dev/null +++ b/tests/t00076/t00076.cc @@ -0,0 +1,36 @@ +namespace clanguml { +namespace t00076 { + +enum Color { red, green, blue }; + +struct F; +struct G { }; +struct H { }; +struct J { }; + +struct A { }; + +struct B : public A { + F *f; + Color c; + G g; + /// @uml{composition[0..1:1..*]} + J j; + + void a(H *h) { (void)h; } +}; + +struct C : public B { }; + +struct D : public C { }; + +struct E { + B *b; +}; +struct F { }; + +struct I { + void i(B *b) { (void)b; } +}; +} +} \ No newline at end of file diff --git a/tests/t00076/test_case.h b/tests/t00076/test_case.h new file mode 100644 index 000000000..7292f6324 --- /dev/null +++ b/tests/t00076/test_case.h @@ -0,0 +1,40 @@ +/** + * tests/t00076/test_case.h + * + * Copyright (c) 2021-2024 Bartek Kryza + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +TEST_CASE("t00076") +{ + using namespace clanguml::test; + using namespace std::string_literals; + + auto [config, db, diagram, model] = + CHECK_CLASS_MODEL("t00076", "t00076_class"); + + CHECK_CLASS_DIAGRAM(*config, diagram, *model, [](const auto &src) { + REQUIRE(IsClass(src, "B")); + REQUIRE(IsClass(src, "C")); + REQUIRE(IsClass(src, "E")); + REQUIRE(IsClass(src, "G")); + REQUIRE(IsClass(src, "I")); + REQUIRE(IsClass(src, "J")); + REQUIRE(IsEnum(src, "Color")); + REQUIRE(!IsClass(src, "A")); + REQUIRE(!IsClass(src, "D")); + REQUIRE(!IsClass(src, "F")); + REQUIRE(!IsClass(src, "H")); + }); +} \ No newline at end of file diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 7bb8d8e15..b99e76eb1 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -549,6 +549,7 @@ void CHECK_INCLUDE_DIAGRAM(const clanguml::config::config &config, #include "t00074/test_case.h" #include "t00075/test_case.h" #endif +#include "t00076/test_case.h" /// /// Sequence diagram tests @@ -664,7 +665,7 @@ int main(int argc, char *argv[]) std::vector argvv = { "clang-uml", "--config", "./test_config_data/simple.yml"}; - argvv.push_back("-q"); + argvv.push_back("-vvv"); clih.handle_options(argvv.size(), argvv.data()); diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index b4abab7b9..3475a86d7 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -222,6 +222,8 @@ test_cases: - name: t00075 title: Test case for class diagram styles in config file description: + - name: t00076 + title: Test case for context diagram with inward direction flag Sequence diagrams: - name: t20001 title: Basic sequence diagram test case