Added dependants diagram filter
This commit is contained in:
@@ -22,16 +22,15 @@
|
|||||||
#include "util/util.h"
|
#include "util/util.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace clanguml::class_diagram::model {
|
namespace clanguml::class_diagram::model {
|
||||||
|
|
||||||
const std::vector<type_safe::object_ref<const class_>> diagram::classes() const
|
const std::vector<type_safe::object_ref<const class_>> &diagram::classes() const
|
||||||
{
|
{
|
||||||
return classes_;
|
return classes_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<type_safe::object_ref<const enum_>> diagram::enums() const
|
const std::vector<type_safe::object_ref<const enum_>> &diagram::enums() const
|
||||||
{
|
{
|
||||||
return enums_;
|
return enums_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ public:
|
|||||||
type_safe::optional_ref<const clanguml::common::model::diagram_element> get(
|
type_safe::optional_ref<const clanguml::common::model::diagram_element> get(
|
||||||
const std::string &full_name) const override;
|
const std::string &full_name) const override;
|
||||||
|
|
||||||
const std::vector<type_safe::object_ref<const class_>> classes() const;
|
const std::vector<type_safe::object_ref<const class_>> &classes() const;
|
||||||
|
|
||||||
const std::vector<type_safe::object_ref<const enum_>> enums() const;
|
const std::vector<type_safe::object_ref<const enum_>> &enums() const;
|
||||||
|
|
||||||
bool has_class(const class_ &c) const;
|
bool has_class(const class_ &c) const;
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,35 @@
|
|||||||
|
|
||||||
#include "diagram_filter.h"
|
#include "diagram_filter.h"
|
||||||
|
|
||||||
|
#include "class_diagram/model/class.h"
|
||||||
|
|
||||||
namespace clanguml::common::model {
|
namespace clanguml::common::model {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
const std::vector<type_safe::object_ref<const class_diagram::model::class_>> &
|
||||||
|
view(const class_diagram::model::diagram &d)
|
||||||
|
{
|
||||||
|
return d.classes();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
const std::vector<type_safe::object_ref<const class_diagram::model::enum_>> &
|
||||||
|
view(const class_diagram::model::diagram &d)
|
||||||
|
{
|
||||||
|
return d.enums();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
const type_safe::optional_ref<const class_diagram::model::class_> 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)
|
filter_visitor::filter_visitor(filter_t type)
|
||||||
: type_{type}
|
: type_{type}
|
||||||
{
|
{
|
||||||
@@ -174,85 +201,6 @@ tvl::value_t subclass_filter::match(const diagram &d, const element &e) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
specialization_filter::specialization_filter(
|
|
||||||
filter_t type, std::vector<std::string> 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<const class_diagram::model::diagram &>(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(
|
relationship_filter::relationship_filter(
|
||||||
filter_t type, std::vector<relationship_t> relationships)
|
filter_t type, std::vector<relationship_t> relationships)
|
||||||
: filter_visitor{type}
|
: filter_visitor{type}
|
||||||
@@ -422,8 +370,17 @@ void diagram_filter::init_filters(const config::diagram &c)
|
|||||||
filter_t::kInclusive, c.include().elements));
|
filter_t::kInclusive, c.include().elements));
|
||||||
element_filters.emplace_back(std::make_unique<subclass_filter>(
|
element_filters.emplace_back(std::make_unique<subclass_filter>(
|
||||||
filter_t::kInclusive, c.include().subclasses));
|
filter_t::kInclusive, c.include().subclasses));
|
||||||
element_filters.emplace_back(std::make_unique<specialization_filter>(
|
|
||||||
filter_t::kInclusive, c.include().specializations));
|
element_filters.emplace_back(
|
||||||
|
std::make_unique<tree_element_filter<class_diagram::model::diagram,
|
||||||
|
class_diagram::model::class_>>(filter_t::kInclusive,
|
||||||
|
relationship_t::kInstantiation, c.include().specializations));
|
||||||
|
|
||||||
|
element_filters.emplace_back(
|
||||||
|
std::make_unique<tree_element_filter<class_diagram::model::diagram,
|
||||||
|
class_diagram::model::class_>>(filter_t::kInclusive,
|
||||||
|
relationship_t::kDependency, c.include().dependants));
|
||||||
|
|
||||||
element_filters.emplace_back(std::make_unique<context_filter>(
|
element_filters.emplace_back(std::make_unique<context_filter>(
|
||||||
filter_t::kInclusive, c.include().context));
|
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));
|
filter_t::kExclusive, c.exclude().access));
|
||||||
add_exclusive_filter(std::make_unique<subclass_filter>(
|
add_exclusive_filter(std::make_unique<subclass_filter>(
|
||||||
filter_t::kExclusive, c.exclude().subclasses));
|
filter_t::kExclusive, c.exclude().subclasses));
|
||||||
add_exclusive_filter(std::make_unique<specialization_filter>(
|
add_exclusive_filter(
|
||||||
filter_t::kExclusive, c.exclude().specializations));
|
std::make_unique<tree_element_filter<class_diagram::model::diagram,
|
||||||
|
class_diagram::model::class_>>(filter_t::kExclusive,
|
||||||
|
relationship_t::kInstantiation, c.exclude().specializations));
|
||||||
|
add_exclusive_filter(
|
||||||
|
std::make_unique<tree_element_filter<class_diagram::model::diagram,
|
||||||
|
class_diagram::model::class_>>(filter_t::kExclusive,
|
||||||
|
relationship_t::kDependency, c.exclude().dependants));
|
||||||
add_exclusive_filter(std::make_unique<context_filter>(
|
add_exclusive_filter(std::make_unique<context_filter>(
|
||||||
filter_t::kExclusive, c.exclude().context));
|
filter_t::kExclusive, c.exclude().context));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ namespace clanguml::common::model {
|
|||||||
|
|
||||||
enum filter_t { kInclusive, kExclusive };
|
enum filter_t { kInclusive, kExclusive };
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename ElementT, typename DiagramT>
|
||||||
|
const std::vector<type_safe::object_ref<const ElementT>> &view(
|
||||||
|
const DiagramT &d);
|
||||||
|
|
||||||
|
template <typename ElementT, typename DiagramT>
|
||||||
|
const type_safe::optional_ref<const ElementT> get(
|
||||||
|
const DiagramT &d, const std::string &full_name);
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
class filter_visitor {
|
class filter_visitor {
|
||||||
public:
|
public:
|
||||||
filter_visitor(filter_t type);
|
filter_visitor(filter_t type);
|
||||||
@@ -102,19 +112,101 @@ private:
|
|||||||
std::vector<std::string> roots_;
|
std::vector<std::string> roots_;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct specialization_filter : public filter_visitor {
|
template <typename DiagramT, typename ElementT>
|
||||||
specialization_filter(filter_t type, std::vector<std::string> roots);
|
struct tree_element_filter : public filter_visitor {
|
||||||
|
tree_element_filter(filter_t type, relationship_t relationship,
|
||||||
|
std::vector<std::string> 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<const DiagramT &>(d);
|
||||||
|
|
||||||
|
// Calculate the set of matching elements
|
||||||
|
init(cd);
|
||||||
|
|
||||||
|
const auto &fn = e.full_name(false);
|
||||||
|
auto element_ref = detail::get<ElementT>(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:
|
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<ElementT>(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<ElementT>(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<std::string> roots_;
|
std::vector<std::string> roots_;
|
||||||
|
relationship_t relationship_;
|
||||||
mutable bool initialized_{false};
|
mutable bool initialized_{false};
|
||||||
mutable std::unordered_set<
|
mutable std::unordered_set<type_safe::object_ref<const ElementT, false>>
|
||||||
type_safe::object_ref<const class_diagram::model::class_, false>>
|
matching_elements_;
|
||||||
templates_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct relationship_filter : public filter_visitor {
|
struct relationship_filter : public filter_visitor {
|
||||||
|
|||||||
@@ -412,6 +412,9 @@ template <> struct convert<filter> {
|
|||||||
rhs.specializations =
|
rhs.specializations =
|
||||||
node["specializations"].as<decltype(rhs.specializations)>();
|
node["specializations"].as<decltype(rhs.specializations)>();
|
||||||
|
|
||||||
|
if (node["dependants"])
|
||||||
|
rhs.dependants = node["dependants"].as<decltype(rhs.dependants)>();
|
||||||
|
|
||||||
if (node["context"])
|
if (node["context"])
|
||||||
rhs.context = node["context"].as<decltype(rhs.context)>();
|
rhs.context = node["context"].as<decltype(rhs.context)>();
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ struct filter {
|
|||||||
|
|
||||||
std::vector<std::string> specializations;
|
std::vector<std::string> specializations;
|
||||||
|
|
||||||
|
std::vector<std::string> dependants;
|
||||||
|
|
||||||
std::vector<std::string> context;
|
std::vector<std::string> context;
|
||||||
|
|
||||||
std::vector<std::filesystem::path> paths;
|
std::vector<std::filesystem::path> paths;
|
||||||
|
|||||||
15
tests/t00043/.clang-uml
Normal file
15
tests/t00043/.clang-uml
Normal file
@@ -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
|
||||||
32
tests/t00043/t00043.cc
Normal file
32
tests/t00043/t00043.cc
Normal file
@@ -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
|
||||||
53
tests/t00043/test_case.h
Normal file
53
tests/t00043/test_case.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* tests/t00043/test_case.cc
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-2022 Bartek Kryza <bkryza@gmail.com>
|
||||||
|
*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
@@ -224,6 +224,7 @@ using namespace clanguml::test::matchers;
|
|||||||
#include "t00040/test_case.h"
|
#include "t00040/test_case.h"
|
||||||
#include "t00041/test_case.h"
|
#include "t00041/test_case.h"
|
||||||
#include "t00042/test_case.h"
|
#include "t00042/test_case.h"
|
||||||
|
#include "t00043/test_case.h"
|
||||||
|
|
||||||
//
|
//
|
||||||
// Sequence diagram tests
|
// Sequence diagram tests
|
||||||
|
|||||||
Reference in New Issue
Block a user