diff --git a/src/class_diagram/generators/plantuml/class_diagram_generator.cc b/src/class_diagram/generators/plantuml/class_diagram_generator.cc index 417baf42..18d7aa3b 100644 --- a/src/class_diagram/generators/plantuml/class_diagram_generator.cc +++ b/src/class_diagram/generators/plantuml/class_diagram_generator.cc @@ -498,7 +498,6 @@ void generator::generate(std::ostream &ostr) const generate(dynamic_cast(*p), ostr); } } - ostr << '\n'; } for (const auto &p : m_model) { @@ -515,7 +514,6 @@ void generator::generate(std::ostream &ostr) const generate_relationships(dynamic_cast(*p), ostr); } } - ostr << '\n'; } generate_config_layout_hints(ostr); diff --git a/src/common/model/diagram_filter.cc b/src/common/model/diagram_filter.cc index 2f87a20d..c7211afa 100644 --- a/src/common/model/diagram_filter.cc +++ b/src/common/model/diagram_filter.cc @@ -174,6 +174,85 @@ 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} @@ -280,7 +359,6 @@ paths_filter::paths_filter(filter_t type, const std::filesystem::path &root, tvl::value_t paths_filter::match( const diagram &d, const common::model::source_file &p) const { - if (paths_.empty()) { return {}; } @@ -344,6 +422,8 @@ 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, c.include().context)); @@ -365,6 +445,8 @@ 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, c.exclude().context)); } @@ -377,5 +459,4 @@ bool diagram_filter::should_include(const std::string &name) const return should_include(ns, n); } - } diff --git a/src/common/model/diagram_filter.h b/src/common/model/diagram_filter.h index f1e3ad96..0701ea49 100644 --- a/src/common/model/diagram_filter.h +++ b/src/common/model/diagram_filter.h @@ -102,6 +102,21 @@ private: std::vector roots_; }; +struct specialization_filter : public filter_visitor { + specialization_filter(filter_t type, std::vector roots); + + tvl::value_t match(const diagram &d, const element &e) const override; + +private: + void init(const class_diagram::model::diagram &d) const; + + std::vector roots_; + mutable bool initialized_{false}; + mutable std::unordered_set< + type_safe::object_ref> + templates_; +}; + struct relationship_filter : public filter_visitor { relationship_filter( filter_t type, std::vector relationships); diff --git a/src/config/config.cc b/src/config/config.cc index 6764d069..bfa4b1d8 100644 --- a/src/config/config.cc +++ b/src/config/config.cc @@ -408,6 +408,10 @@ template <> struct convert { if (node["subclasses"]) rhs.subclasses = node["subclasses"].as(); + if (node["specializations"]) + rhs.specializations = + node["specializations"].as(); + if (node["context"]) rhs.context = node["context"].as(); diff --git a/src/config/config.h b/src/config/config.h index 8ba0693d..11c44339 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -70,6 +70,8 @@ struct filter { std::vector subclasses; + std::vector specializations; + std::vector context; std::vector paths; diff --git a/tests/t00042/.clang-uml b/tests/t00042/.clang-uml new file mode 100644 index 00000000..2f94a9ac --- /dev/null +++ b/tests/t00042/.clang-uml @@ -0,0 +1,19 @@ +compilation_database_dir: .. +output_directory: puml +diagrams: + t00042_class: + type: class + generate_packages: false + glob: + - ../../tests/t00042/t00042.cc + using_namespace: + - clanguml::t00042 + include: + specializations: + - clanguml::t00042::A + - clanguml::t00042::B + relationships: + - instantiation + exclude: + specializations: + - clanguml::t00042::C \ No newline at end of file diff --git a/tests/t00042/t00042.cc b/tests/t00042/t00042.cc new file mode 100644 index 00000000..2ec55e44 --- /dev/null +++ b/tests/t00042/t00042.cc @@ -0,0 +1,31 @@ +#include + +namespace clanguml::t00042 { + +template struct A { + T a; +}; + +template <> struct A { + void *a; +}; + +template struct B { + T b; + K bb; +}; + +template struct C { + T c; +}; + +struct R { + A a_double; + A a_string; + + B b_int_float; + + C c_int; +}; + +} // namespace clanguml::t00042 diff --git a/tests/t00042/test_case.h b/tests/t00042/test_case.h new file mode 100644 index 00000000..51c8e7a7 --- /dev/null +++ b/tests/t00042/test_case.h @@ -0,0 +1,44 @@ +/** + * tests/t00042/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("t00042", "[test-case][class]") +{ + auto [config, db] = load_config("t00042"); + + auto diagram = config.diagrams["t00042_class"]; + + REQUIRE(diagram->name == "t00042_class"); + REQUIRE(diagram->generate_packages() == false); + + auto model = generate_class_diagram(db, diagram); + + REQUIRE(model->name() == "t00042_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, IsClassTemplate("A", "T")); + REQUIRE_THAT(puml, IsClassTemplate("B", "T,K")); + REQUIRE_THAT(puml, !IsClassTemplate("C", "T")); + + save_puml( + "./" + config.output_directory() + "/" + diagram->name + ".puml", puml); +} diff --git a/tests/test_cases.cc b/tests/test_cases.cc index 99d7ff38..a8c07851 100644 --- a/tests/test_cases.cc +++ b/tests/test_cases.cc @@ -223,6 +223,7 @@ using namespace clanguml::test::matchers; #include "t00039/test_case.h" #include "t00040/test_case.h" #include "t00041/test_case.h" +#include "t00042/test_case.h" // // Sequence diagram tests diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 08962477..68bd3ab6 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -120,6 +120,8 @@ test_cases: - name: t00041 title: Context diagram filter test description: + - name: t00042 + title: Specialization class template diagram filter test Sequence diagrams: - name: t20001 title: Basic sequence diagram test case