From 11dccf1496c04001cd910630f39da624761b943e Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Mon, 18 Apr 2022 11:59:56 +0200 Subject: [PATCH] Added dependants diagram filter --- src/class_diagram/model/diagram.cc | 5 +- src/class_diagram/model/diagram.h | 4 +- src/common/model/diagram_filter.cc | 129 ++++++++++------------------- src/common/model/diagram_filter.h | 106 ++++++++++++++++++++++-- src/config/config.cc | 3 + src/config/config.h | 2 + tests/t00043/.clang-uml | 15 ++++ tests/t00043/t00043.cc | 32 +++++++ tests/t00043/test_case.h | 53 ++++++++++++ tests/test_cases.cc | 1 + 10 files changed, 255 insertions(+), 95 deletions(-) create mode 100644 tests/t00043/.clang-uml create mode 100644 tests/t00043/t00043.cc create mode 100644 tests/t00043/test_case.h diff --git a/src/class_diagram/model/diagram.cc b/src/class_diagram/model/diagram.cc index 700e294e..23f2147a 100644 --- a/src/class_diagram/model/diagram.cc +++ b/src/class_diagram/model/diagram.cc @@ -22,16 +22,15 @@ #include "util/util.h" #include -#include namespace clanguml::class_diagram::model { -const std::vector> diagram::classes() const +const std::vector> &diagram::classes() const { return classes_; } -const std::vector> diagram::enums() const +const std::vector> &diagram::enums() const { return enums_; } diff --git a/src/class_diagram/model/diagram.h b/src/class_diagram/model/diagram.h index d27ad37b..eb19cdd1 100644 --- a/src/class_diagram/model/diagram.h +++ b/src/class_diagram/model/diagram.h @@ -47,9 +47,9 @@ public: type_safe::optional_ref get( const std::string &full_name) const override; - const std::vector> classes() const; + const std::vector> &classes() const; - const std::vector> enums() const; + const std::vector> &enums() const; bool has_class(const class_ &c) const; diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index c7211afa..4be9dc29 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -18,8 +18,35 @@ #include "diagram_filter.h" +#include "class_diagram/model/class.h" + namespace clanguml::common::model { +namespace detail { + +template <> +const std::vector> & +view(const class_diagram::model::diagram &d) +{ + return d.classes(); +} + +template <> +const std::vector> & +view(const class_diagram::model::diagram &d) +{ + return d.enums(); +} + +template <> +const type_safe::optional_ref get( + const class_diagram::model::diagram &d, const std::string &full_name) +{ + return d.get_class(full_name); +} + +} + filter_visitor::filter_visitor(filter_t type) : type_{type} { @@ -174,85 +201,6 @@ tvl::value_t subclass_filter::match(const diagram &d, const element &e) const return false; } -specialization_filter::specialization_filter( - filter_t type, std::vector roots) - : filter_visitor{type} - , roots_{roots} -{ -} - -void specialization_filter::init(const class_diagram::model::diagram &cd) const -{ - if (initialized_) - return; - - // First get all templates specified in the configuration - for (const auto &template_root : roots_) { - auto template_ref = cd.get_class(template_root); - if (template_ref.has_value()) - templates_.emplace(template_ref.value()); - } - - // Iterate over the templates set, until no new template instantiations or - // specializations are found - bool found_new_template{true}; - while (found_new_template) { - found_new_template = false; - for (const auto &t : cd.classes()) { - auto tfn = t->full_name(false); - auto tfn_relative = t->full_name(true); - for (const auto &r : t->relationships()) { - if (r.type() == relationship_t::kInstantiation) { - auto r_dest = r.destination(); - for (const auto &t_dest : templates_) { - auto t_dest_full = t_dest->full_name(true); - if (r_dest == t_dest_full) { - auto inserted = templates_.insert(t); - if (inserted.second) - found_new_template = true; - } - } - } - } - } - } - - initialized_ = true; -} - -tvl::value_t specialization_filter::match( - const diagram &d, const element &e) const -{ - if (d.type() != diagram_t::kClass) - return {}; - - if (roots_.empty()) - return {}; - - if (!d.complete()) - return {}; - - const auto &cd = dynamic_cast(d); - - init(cd); - - const auto &fn = e.full_name(false); - auto class_ref = cd.get_class(fn); - - if (!class_ref.has_value()) - // Couldn't find the element in the diagram - return false; - - // Now check if the e element is contained in the calculated set - const auto &e_full_name = e.full_name(true); - bool res = std::find_if(templates_.begin(), templates_.end(), - [&e_full_name](const auto &te) { - return te->full_name(true) == e_full_name; - }) != templates_.end(); - - return res; -} - relationship_filter::relationship_filter( filter_t type, std::vector relationships) : filter_visitor{type} @@ -422,8 +370,17 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kInclusive, c.include().elements)); element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().subclasses)); - element_filters.emplace_back(std::make_unique( - filter_t::kInclusive, c.include().specializations)); + + element_filters.emplace_back( + std::make_unique>(filter_t::kInclusive, + relationship_t::kInstantiation, c.include().specializations)); + + element_filters.emplace_back( + std::make_unique>(filter_t::kInclusive, + relationship_t::kDependency, c.include().dependants)); + element_filters.emplace_back(std::make_unique( filter_t::kInclusive, c.include().context)); @@ -445,8 +402,14 @@ void diagram_filter::init_filters(const config::diagram &c) filter_t::kExclusive, c.exclude().access)); add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().subclasses)); - add_exclusive_filter(std::make_unique( - filter_t::kExclusive, c.exclude().specializations)); + add_exclusive_filter( + std::make_unique>(filter_t::kExclusive, + relationship_t::kInstantiation, c.exclude().specializations)); + add_exclusive_filter( + std::make_unique>(filter_t::kExclusive, + relationship_t::kDependency, c.exclude().dependants)); add_exclusive_filter(std::make_unique( filter_t::kExclusive, c.exclude().context)); } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index 0701ea49..9751dd2a 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -34,6 +34,16 @@ namespace clanguml::common::model { enum filter_t { kInclusive, kExclusive }; +namespace detail { +template +const std::vector> &view( + const DiagramT &d); + +template +const type_safe::optional_ref get( + const DiagramT &d, const std::string &full_name); +} // namespace detail + class filter_visitor { public: filter_visitor(filter_t type); @@ -102,19 +112,101 @@ private: std::vector roots_; }; -struct specialization_filter : public filter_visitor { - specialization_filter(filter_t type, std::vector roots); +template +struct tree_element_filter : public filter_visitor { + tree_element_filter(filter_t type, relationship_t relationship, + std::vector roots) + : filter_visitor{type} + , relationship_{relationship} + , roots_{roots} + { + } - tvl::value_t match(const diagram &d, const element &e) const override; + tvl::value_t match(const diagram &d, const element &e) const override + { + if (roots_.empty()) + return {}; + + // This filter should only be run on the completely generated diagram + // model by visitor + if (!d.complete()) + return {}; + + const auto &cd = dynamic_cast(d); + + // Calculate the set of matching elements + init(cd); + + const auto &fn = e.full_name(false); + auto element_ref = detail::get(cd, fn); + + if (!element_ref.has_value()) + // Couldn't find the element in the diagram + return false; + + // Now check if the e element is contained in the calculated set + const auto &e_full_name = e.full_name(false); + bool res = + std::find_if(matching_elements_.begin(), matching_elements_.end(), + [&e_full_name](const auto &te) { + return te->full_name(false) == e_full_name; + }) != matching_elements_.end(); + + return res; + } private: - void init(const class_diagram::model::diagram &d) const; + void init(const DiagramT &cd) const + { + if (initialized_) + return; + + // First get all elements specified in the filter configuration + for (const auto &template_root : roots_) { + auto template_ref = detail::get(cd, template_root); + if (template_ref.has_value()) + matching_elements_.emplace(template_ref.value()); + } + + // Iterate over the templates set, until no new template instantiations + // or specializations are found + bool found_new_template{true}; + + auto reverse_relationship_match = [&found_new_template, this]( + const relationship &rel, + const auto &el) { + if (rel.type() == relationship_) { + for (const auto &already_matching : matching_elements_) { + if (rel.destination() == + already_matching->full_name(false)) { + auto inserted = matching_elements_.insert(el); + if (inserted.second) + found_new_template = true; + } + } + } + }; + + while (found_new_template) { + found_new_template = false; + // For each element of type ElementT in the diagram + for (const auto &el : detail::view(cd)) { + // Check if any of its relationships of type relationship_ + // points to an element already in the matching_elements_ set + for (const auto &rel : el->relationships()) { + reverse_relationship_match(rel, el); + } + } + } + + initialized_ = true; + } std::vector roots_; + relationship_t relationship_; mutable bool initialized_{false}; - mutable std::unordered_set< - type_safe::object_ref> - templates_; + mutable std::unordered_set> + matching_elements_; }; struct relationship_filter : public filter_visitor { diff --git a/src/config/config.cc b/src/config/config.cc index bfa4b1d8..a4ee2ed8 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -412,6 +412,9 @@ template <> struct convert { rhs.specializations = node["specializations"].as(); + if (node["dependants"]) + rhs.dependants = node["dependants"].as(); + if (node["context"]) rhs.context = node["context"].as(); diff --git a/src/config/config.h b/src/config/config.h index 11c44339..09e4b155 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -72,6 +72,8 @@ struct filter { std::vector specializations; + std::vector dependants; + std::vector context; std::vector paths; diff --git a/tests/t00043/.clang-uml b/tests/t00043/.clang-uml new file mode 100644 index 00000000..f45e5fbc --- /dev/null +++ b/tests/t00043/.clang-uml @@ -0,0 +1,15 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00043_class: + type: class + generate_packages: true + glob: + - ../../tests/t00043/t00043.cc + using_namespace: + - clanguml::t00043 + include: + dependants: + - clanguml::t00043::dependants::A + relationships: + - dependency \ No newline at end of file diff --git a/tests/t00043/t00043.cc b/tests/t00043/t00043.cc new file mode 100644 index 00000000..15be26a6 --- /dev/null +++ b/tests/t00043/t00043.cc @@ -0,0 +1,32 @@ +namespace clanguml::t00043 { + +namespace dependants { +struct A { +}; + +struct B { + void b(A *a) { } +}; + +struct BB { + void bb(A *a) { } +}; + +struct C { + void c(B *b) { } +}; + +struct D { + void d(C *c) { } + void dd(BB *bb) { } +}; + +struct E { + void e(D *d) { } +}; + +struct F { +}; +} // namespace dependants + +} // namespace clanguml::t00043 diff --git a/tests/t00043/test_case.h b/tests/t00043/test_case.h new file mode 100644 index 00000000..484039b2 --- /dev/null +++ b/tests/t00043/test_case.h @@ -0,0 +1,53 @@ +/** + * tests/t00043/test_case.cc + * + * Copyright (c) 2021-2022 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("t00043", "[test-case][class]") +{ + auto [config, db] = load_config("t00043"); + + auto diagram = config.diagrams["t00043_class"]; + + REQUIRE(diagram->name == "t00043_class"); + REQUIRE(diagram->generate_packages() == true); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model->name() == "t00043_class"); + + auto puml = generate_class_puml(diagram, *model); + AliasMatcher _A(puml); + + REQUIRE_THAT(puml, StartsWith("@startuml")); + REQUIRE_THAT(puml, EndsWith("@enduml\n")); + + REQUIRE_THAT(puml, IsClass(_A("A"))); + REQUIRE_THAT(puml, IsClass(_A("B"))); + REQUIRE_THAT(puml, IsClass(_A("BB"))); + REQUIRE_THAT(puml, IsClass(_A("D"))); + REQUIRE_THAT(puml, IsClass(_A("E"))); + REQUIRE_THAT(puml, !IsClass(_A("F"))); + + REQUIRE_THAT(puml, IsDependency(_A("B"), _A("A"))); + REQUIRE_THAT(puml, IsDependency(_A("BB"), _A("A"))); + REQUIRE_THAT(puml, IsDependency(_A("C"), _A("B"))); + REQUIRE_THAT(puml, IsDependency(_A("D"), _A("C"))); + REQUIRE_THAT(puml, IsDependency(_A("E"), _A("D"))); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index a8c07851..ad07b853 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -224,6 +224,7 @@ using namespace clanguml::test::matchers; #include "t00040/test_case.h" #include "t00041/test_case.h" #include "t00042/test_case.h" +#include "t00043/test_case.h" // // Sequence diagram tests