0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
diagram_filter.h
Go to the documentation of this file.
1/**
2 * @file src/common/model/filters/diagram_filter.h
3 *
4 * Copyright (c) 2021-2024 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
23#include "common/clang_utils.h"
26#include "common/model/enums.h"
29#include "common/model/tvl.h"
30#include "config/config.h"
33
34#include <filesystem>
35#include <utility>
36
38
39class diagram_filter_factory;
40
43
44/**
45 * Diagram filters can be add in 2 modes:
46 * - inclusive - the elements that match are included in the diagram
47 * - exclusive - the elements that match are excluded from the diagram
48 */
49enum class filter_t {
50 kInclusive, /*!< Filter is inclusive */
51 kExclusive /*!< Filter is exclusve */
52};
53
54namespace detail {
55template <typename ElementT, typename DiagramT>
57
58template <typename ElementT, typename DiagramT>
60 const DiagramT &d, const std::string &full_name);
61
62template <typename ElementT> eid_t destination_comparator(const ElementT &e)
63{
64 return e.id();
65}
66
68} // namespace detail
69
70/**
71 * @brief Base class for any diagram filter.
72 *
73 * This class acts as a visitor for diagram elements. It provides a set of
74 * common methods which can be overriden by specific filters. If a filter
75 * does not implement a specific method, it is ignored through the 3 value
76 * logic implemented in @see clanguml::common::model::tvl
77 *
78 * @embed{filter_visitor_hierarchy_class.svg}
79 */
81public:
83
84 virtual ~filter_visitor() = default;
85
86 virtual tvl::value_t match(
87 const diagram &d, const common::model::element &e) const;
88
89 virtual tvl::value_t match(
90 const diagram &d, const common::model::relationship &r) const;
91
92 virtual tvl::value_t match(
93 const diagram &d, const common::model::relationship_t &r) const;
94
95 virtual tvl::value_t match(
96 const diagram &d, const common::model::access_t &a) const;
97
98 virtual tvl::value_t match(
99 const diagram &d, const common::model::namespace_ &ns) const;
100
101 virtual tvl::value_t match(
102 const diagram &d, const common::model::source_file &f) const;
103
104 virtual tvl::value_t match(
105 const diagram &d, const common::model::source_location &f) const;
106
107 virtual tvl::value_t match(
108 const diagram &d, const class_diagram::model::class_method &m) const;
109
110 virtual tvl::value_t match(
111 const diagram &d, const class_diagram::model::class_member &m) const;
112
113 virtual tvl::value_t match(
114 const diagram &d, const sequence_diagram::model::participant &p) const;
115
116 bool is_inclusive() const;
117 bool is_exclusive() const;
118
119 filter_t type() const;
120 filter_mode_t mode() const;
121 void set_mode(filter_mode_t mode);
122
123private:
125 filter_mode_t mode_{filter_mode_t::basic};
126};
127
130 filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters);
131
132 ~anyof_filter() override = default;
133
135 const diagram &d, const common::model::element &e) const override;
136
137 tvl::value_t match(const diagram &d,
138 const common::model::relationship_t &r) const override;
139
141 const diagram &d, const common::model::access_t &a) const override;
142
144 const diagram &d, const common::model::namespace_ &ns) const override;
145
147 const diagram &d, const common::model::source_file &f) const override;
148
149 tvl::value_t match(const diagram &d,
150 const common::model::source_location &f) const override;
151
152 tvl::value_t match(const diagram &d,
153 const class_diagram::model::class_method &m) const override;
154
155 tvl::value_t match(const diagram &d,
156 const class_diagram::model::class_member &m) const override;
157
158 tvl::value_t match(const diagram &d,
159 const sequence_diagram::model::participant &p) const override;
160
161private:
162 template <typename E>
163 tvl::value_t match_anyof(const diagram &d, const E &element) const
164 {
165 auto result = tvl::any_of(filters_.begin(), filters_.end(),
166 [&d, &element](const auto &f) { return f->match(d, element); });
167
168 if (mode() == filter_mode_t::advanced && !d.complete())
169 return type() == filter_t::kInclusive;
170
171 return result;
172 }
173
174 std::vector<std::unique_ptr<filter_visitor>> filters_;
175};
176
179 filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters);
180
181 ~allof_filter() override = default;
182
184 const diagram &d, const common::model::element &e) const override;
185
186 tvl::value_t match(const diagram &d,
187 const common::model::relationship_t &r) const override;
188
190 const diagram &d, const common::model::access_t &a) const override;
191
193 const diagram &d, const common::model::namespace_ &ns) const override;
194
196 const diagram &d, const common::model::source_file &f) const override;
197
198 tvl::value_t match(const diagram &d,
199 const common::model::source_location &f) const override;
200
201 tvl::value_t match(const diagram &d,
202 const class_diagram::model::class_method &m) const override;
203
204 tvl::value_t match(const diagram &d,
205 const class_diagram::model::class_member &m) const override;
206
207 tvl::value_t match(const diagram &d,
208 const sequence_diagram::model::participant &p) const override;
209
210private:
211 template <typename E>
212 tvl::value_t match_allof(const diagram &d, const E &element) const
213 {
214 return tvl::all_of(filters_.begin(), filters_.end(),
215 [&d, &element](const auto &f) { return f->match(d, element); });
216 }
217
218 std::vector<std::unique_ptr<filter_visitor>> filters_;
219};
220
221/**
222 * Match namespace or diagram element to a set of specified namespaces or
223 * regex patterns.
224 */
227 filter_t type, std::vector<common::namespace_or_regex> namespaces);
228
229 ~namespace_filter() override = default;
230
231 tvl::value_t match(const diagram &d, const namespace_ &ns) const override;
232
233 tvl::value_t match(const diagram &d, const element &e) const override;
234
235 tvl::value_t match(const diagram &d,
236 const sequence_diagram::model::participant &p) const override;
237
238private:
239 std::vector<common::namespace_or_regex> namespaces_;
240};
241
242/**
243 * Match diagram elements to a set of specified modules or
244 * module regex patterns.
245 */
247 modules_filter(filter_t type, std::vector<common::string_or_regex> modules);
248
249 ~modules_filter() override = default;
250
251 tvl::value_t match(const diagram &d, const element &e) const override;
252
253private:
254 std::vector<common::string_or_regex> modules_;
255};
256
257/**
258 * Match element's name to a set of names or regex patterns.
259 */
262 filter_t type, std::vector<common::string_or_regex> elements);
263
264 ~element_filter() override = default;
265
266 tvl::value_t match(const diagram &d, const element &e) const override;
267
268 tvl::value_t match(const diagram &d,
269 const sequence_diagram::model::participant &p) const override;
270
271private:
272 std::vector<common::string_or_regex> elements_;
273};
274
275/**
276 * Match diagram elements based on elements type (e.g. class).
277 */
279 element_type_filter(filter_t type, std::vector<std::string> element_types);
280
281 ~element_type_filter() override = default;
282
283 tvl::value_t match(const diagram &d, const element &e) const override;
284
285private:
286 std::vector<std::string> element_types_;
287};
288
289/**
290 * Match class methods based on their category (e.g. operator).
291 */
294 filter_t type, std::vector<config::method_type> method_types);
295
296 ~method_type_filter() override = default;
297
298 tvl::value_t match(const diagram &d,
299 const class_diagram::model::class_method &e) const override;
300
301private:
302 std::vector<config::method_type> method_types_;
303};
304
305/**
306 * Sequence diagram callee type filter.
307 */
309 callee_filter(filter_t type, std::vector<config::callee_type> callee_types);
310
311 ~callee_filter() override = default;
312
313 tvl::value_t match(const diagram &d,
314 const sequence_diagram::model::participant &p) const override;
315
316private:
317 std::vector<config::callee_type> callee_types_;
318};
319
320/**
321 * Match element based on whether it is a subclass of a set of base classes,
322 * or one of them.
323 */
325 subclass_filter(filter_t type, std::vector<common::string_or_regex> roots);
326
327 ~subclass_filter() override = default;
328
329 tvl::value_t match(const diagram &d, const element &e) const override;
330
331private:
332 std::vector<common::string_or_regex> roots_;
333};
334
335/**
336 * Match element based on whether it is a parent of a set of children, or one
337 * of them.
338 */
340 parents_filter(filter_t type, std::vector<common::string_or_regex> roots);
341
342 ~parents_filter() override = default;
343
344 tvl::value_t match(const diagram &d, const element &e) const override;
345
346private:
347 std::vector<common::string_or_regex> children_;
348};
349
350/**
351 * @brief Common template for filters involving traversing relationship graph.
352 *
353 * This class template provides a common implementation of a diagram
354 * relationship graph traversal. It is used for filters, which need to check
355 * for instance, whether an element is in some kind of relationship with other
356 * element.
357 *
358 * @tparam DiagramT Diagram type
359 * @tparam ElementT Element type
360 * @tparam ConfigEntryT Type of configuration option used to specify initial
361 * elements for traversal
362 * @tparam MatchOverrideT Type of the matched element
363 */
364template <typename DiagramT, typename ElementT,
365 typename ConfigEntryT = std::string,
366 typename MatchOverrideT = common::model::element>
368 edge_traversal_filter(filter_t type, std::vector<ConfigEntryT> roots,
369 relationship_t relationship, bool forward = false)
371 , roots_{std::move(roots)}
373 , forward_{forward}
374 {
375 }
376
377 ~edge_traversal_filter() override = default;
378
379 tvl::value_t match(const diagram &d, const MatchOverrideT &e) const override
380 {
381 // This filter should only be run only on diagram models after the
382 // entire AST has been visited
383 if (!d.complete())
384 return {};
385
386 if (!check_diagram_type<DiagramT>(d.type()))
387 return {};
388
389 if (roots_.empty())
390 return {};
391
392 const auto &cd = dynamic_cast<const DiagramT &>(d);
393
394 // Calculate the set of matching elements
395 init(cd);
396
397 const auto &fn = e.full_name(false);
398 auto element_ref = detail::get<ElementT>(cd, fn);
399
400 if (!element_ref.has_value())
401 // Couldn't find the element in the diagram
402 return false;
403
404 // Now check if the e element is contained in the calculated set
405 return std::any_of(matching_elements_.begin(), matching_elements_.end(),
406 [&e](const auto &te) {
407 std::string tes = te.get().full_name(false);
408 std::string es = e.full_name(false);
409
410 if (tes == es)
411 return true;
412
413 return false;
414 });
415 }
416
417private:
418 template <typename C, typename D>
419 bool add_adjacent(const C &from, const D &to,
420 const std::vector<relationship_t> &relationships) const
421 {
422 bool added_new_element{false};
423
424 for (const auto &from_el : from) {
425 // Check if any of its relationships of type relationship_
426 // points to an element already in the matching_elements_
427 // set
428 for (const auto &rel : from_el.get().relationships()) {
429 // Consider only if connected by one of specified relationships
430 if (util::contains(relationships, rel.type())) {
431 for (const auto &to_el : to) {
432 if (rel.destination() ==
433 detail::destination_comparator(to_el.get())) {
434 const auto &to_add = forward_ ? to_el : from_el;
435 if (matching_elements_.insert(to_add).second)
436 added_new_element = true;
437 }
438 }
439 }
440 }
441 }
442
443 return added_new_element;
444 }
445
446 void add_parents(const DiagramT &cd) const
447 {
448 decltype(matching_elements_) parents;
449
451 matching_elements_, [&cd, &parents](const auto &element) {
452 auto parent = detail::get<ElementT, DiagramT>(
453 cd, element.get().path().to_string());
454
455 while (parent.has_value()) {
456 parents.emplace(parent.value());
457 parent = detail::get<ElementT, DiagramT>(
458 cd, parent.value().path().to_string());
459 }
460 });
461
462 matching_elements_.insert(std::begin(parents), std::end(parents));
463 }
464
465 void init(const DiagramT &cd) const
466 {
467 if (initialized_)
468 return;
469
470 // First get all elements specified in the filter configuration
471 // which will serve as starting points for the search
472 // of matching elements
473 for (const auto &root_pattern : roots_) {
474 if constexpr (std::is_same_v<ConfigEntryT,
476 auto root_refs = cd.template find<ElementT>(root_pattern);
477
478 for (auto &root : root_refs) {
479 if (root.has_value())
480 matching_elements_.emplace(root.value());
481 }
482 }
483 else {
484 auto root_ref = detail::get<ElementT>(cd, root_pattern);
485 if (root_ref.has_value()) {
486 matching_elements_.emplace(root_ref.value());
487 }
488 }
489 }
490
491 bool keep_looking{!matching_elements_.empty()};
492 while (keep_looking) {
493 keep_looking = false;
494 if (forward_) {
495 if (add_adjacent(matching_elements_, detail::view<ElementT>(cd),
496 {relationship_}))
497 keep_looking = true;
498 }
499 else {
500 if (add_adjacent(detail::view<ElementT>(cd), matching_elements_,
501 {relationship_}))
502 keep_looking = true;
503 }
504 }
505
506 // For nested diagrams, include also parent elements
507 if ((type() == filter_t::kInclusive) &&
508 (cd.type() == common::model::diagram_t::kPackage)) {
509 add_parents(cd);
510 }
511
512 initialized_ = true;
513 }
514
515 std::vector<ConfigEntryT> roots_;
517 mutable bool initialized_{false};
520};
521
522/**
523 * Match relationship types.
524 */
527 filter_t type, std::vector<relationship_t> relationships);
528
529 ~relationship_filter() override = default;
530
532 const diagram &d, const relationship_t &r) const override;
533
534private:
535 std::vector<relationship_t> relationships_;
536};
537
538/**
539 * Match class members and methods based on access (public, protected, private).
540 */
542 access_filter(filter_t type, std::vector<access_t> access);
543
544 ~access_filter() override = default;
545
546 tvl::value_t match(const diagram &d, const access_t &a) const override;
547
548private:
549 std::vector<access_t> access_;
550};
551
552/**
553 * Match diagram elements based on module access (public or private).
554 */
556 module_access_filter(filter_t type, std::vector<module_access_t> access);
557
558 ~module_access_filter() override = default;
559
560 tvl::value_t match(const diagram &d, const element &a) const override;
561
562private:
563 std::vector<module_access_t> access_;
564};
565
566/**
567 * Match diagram elements which are in within a 'radius' distance relationship
568 * to any of the elements specified in context.
569 */
571 context_filter(filter_t type, std::vector<config::context_config> context);
572
573 ~context_filter() override = default;
574
575 tvl::value_t match(const diagram &d, const element &r) const override;
576
577private:
578 void initialize(const diagram &d) const;
579
580 void initialize_effective_context(const diagram &d, unsigned idx) const;
581
582 bool is_inward(relationship_t r) const;
583
584 bool is_outward(relationship_t r) const;
585
586 template <typename ElementT>
588 const config::context_config &context_cfg,
589 std::set<eid_t> &effective_context,
590 std::set<eid_t> &current_iteration_context) const
591 {
592 static_assert(std::is_same_v<ElementT, class_diagram::model::class_> ||
593 std::is_same_v<ElementT, class_diagram::model::enum_> ||
594 std::is_same_v<ElementT, class_diagram::model::concept_>,
595 "ElementT must be either class_ or enum_ or concept_");
596
597 const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(d);
598
599 for (const auto &el : cd.elements<ElementT>()) {
600 // First search all elements of type ElementT in the diagram
601 // which have a relationship to any of the effective_context
602 // elements
603 for (const relationship &rel : el.get().relationships()) {
604 if (!should_include(context_cfg, rel.type()) ||
605 !d.should_include(rel.type())) {
606 continue;
607 }
608 // At the moment aggregation and composition are added in the
609 // model in reverse direction, so we don't consider them here
610 if (context_cfg.direction ==
612 (rel.type() == relationship_t::kAggregation ||
613 rel.type() == relationship_t::kComposition)) {
614 continue;
615 }
616 if (context_cfg.direction ==
618 (rel.type() != relationship_t::kAggregation &&
619 rel.type() != relationship_t::kComposition)) {
620 continue;
621 }
622 for (const auto &element_id : effective_context) {
623 if (rel.destination() == element_id)
624 current_iteration_context.emplace(el.get().id());
625 }
626 }
627
628 // Now search current effective_context elements and add any
629 // elements of any type in the diagram which have a relationship
630 // to that element
631 for (const auto element_id : effective_context) {
632 const auto &maybe_element = cd.get(element_id);
633
634 if (!maybe_element)
635 continue;
636
637 for (const relationship &rel :
638 maybe_element.value().relationships()) {
639 if (!should_include(context_cfg, rel.type()) ||
640 !d.should_include(rel.type())) {
641 continue;
642 }
643
644 if ((context_cfg.direction ==
646 (rel.type() != relationship_t::kAggregation &&
647 rel.type() != relationship_t::kComposition)) {
648 continue;
649 }
650 if (context_cfg.direction ==
652 (rel.type() == relationship_t::kAggregation ||
653 rel.type() == relationship_t::kComposition)) {
654 continue;
655 }
656
657 if (rel.destination() == el.get().id())
658 current_iteration_context.emplace(el.get().id());
659 }
660 }
661 }
662 }
663
664 bool should_include(
665 const config::context_config &context_cfg, relationship_t r) const;
666
668 const config::context_config &context_cfg,
669 std::set<eid_t> &effective_context,
670 std::set<eid_t> &current_iteration_context) const;
671
673 std::set<eid_t> &effective_context,
674 std::set<eid_t> &current_iteration_context,
676 const std::reference_wrapper<class_diagram::model::class_> &c) const;
677
678 void find_elements_sub_classes(std::set<eid_t> &effective_context,
679 std::set<eid_t> &current_iteration_context,
681 const std::reference_wrapper<class_diagram::model::class_> &c) const;
682
683 std::vector<config::context_config> context_;
684
685 /*!
686 * Represents all elements which should belong to the diagram based
687 * on this filter. It is populated by the initialize() method.
688 */
689 mutable std::vector<std::set<eid_t>> effective_contexts_;
690
691 /*! Flag to mark whether the filter context has been computed */
692 mutable bool initialized_{false};
693};
694
695/**
696 * Match elements based on their source location, whether it matches to
697 * a specified file paths.
698 */
700 paths_filter(filter_t type, const std::vector<std::string> &p,
701 const std::filesystem::path &root);
702
703 ~paths_filter() override = default;
704
706 const diagram &d, const common::model::source_file &r) const override;
707
708 tvl::value_t match(const diagram &d,
709 const common::model::source_location &sl) const override;
710
711private:
712 std::vector<std::filesystem::path> paths_;
713 std::filesystem::path root_;
714};
715
716/**
717 * Match class method based on specified method categories.
718 */
720 class_method_filter(filter_t type, std::unique_ptr<access_filter> af,
721 std::unique_ptr<method_type_filter> mtf);
722
723 ~class_method_filter() override = default;
724
725 tvl::value_t match(const diagram &d,
726 const class_diagram::model::class_method &m) const override;
727
728private:
729 std::unique_ptr<access_filter> access_filter_;
730 std::unique_ptr<method_type_filter> method_type_filter_;
731};
732
733/**
734 * Match class members.
735 */
737 class_member_filter(filter_t type, std::unique_ptr<access_filter> af);
738
739 ~class_member_filter() override = default;
740
741 tvl::value_t match(const diagram &d,
742 const class_diagram::model::class_member &m) const override;
743
744private:
745 std::unique_ptr<access_filter> access_filter_;
746};
747
749
750/**
751 * @brief Composite of all diagrams filters.
752 *
753 * Instances of this class contain all filters specified in configuration file
754 * for a given diagram.
755 *
756 * @embed{diagram_filter_context_class.svg}
757 *
758 * @see clanguml::common::model::filter_visitor
759 */
761private:
763
764public:
767
768 void add_filter(filter_t filter_type, std::unique_ptr<filter_visitor> fv);
769
770 /**
771 * Add inclusive filter.
772 *
773 * @param fv Filter visitor.
774 */
775 void add_inclusive_filter(std::unique_ptr<filter_visitor> fv);
776
777 /** Add exclusive filter.
778 *
779 * @param fv Filter visitor.
780 */
781 void add_exclusive_filter(std::unique_ptr<filter_visitor> fv);
782
783 /**
784 * `should_include` overload for namespace and name.
785 *
786 * @param ns Namespace
787 * @param name Name
788 * @return Match result.
789 */
790 bool should_include(const namespace_ &ns, const std::string &name) const;
791
792 /**
793 * Generic `should_include` overload for various diagram elements.
794 *
795 * @tparam T Type to to match - must match one of filter_visitor's match(T)
796 * @param e Value of type T to match
797 * @return Match result.
798 */
799 template <typename T> bool should_include(const T &e) const
800 {
801 auto exc = tvl::any_of(
802 exclusive_.begin(), exclusive_.end(), [this, &e](const auto &ex) {
803 assert(ex.get() != nullptr);
804
805 return ex->match(diagram_, e);
806 });
807
808 if (tvl::is_true(exc))
809 return false;
810
811 auto inc = tvl::all_of(
812 inclusive_.begin(), inclusive_.end(), [this, &e](const auto &in) {
813 assert(in.get() != nullptr);
814
815 return in->match(diagram_, e);
816 });
817
818 return static_cast<bool>(tvl::is_undefined(inc) || tvl::is_true(inc));
819 }
820
822
823private:
824 /*! List of inclusive filters */
825 std::vector<std::unique_ptr<filter_visitor>> inclusive_;
826
827 /*! List of exclusive filters */
828 std::vector<std::unique_ptr<filter_visitor>> exclusive_;
829
830 /*! Reference to the diagram model */
832};
833
834template <typename Collection>
835void apply_filter(Collection &col, const diagram_filter &filter)
836{
837 col.erase(std::remove_if(col.begin(), col.end(),
838 [&filter](auto &&element) {
839 return !filter.should_include(element);
840 }),
841 col.end());
842}
843
844template <typename T>
846 std::vector<std::reference_wrapper<T>> &col, const diagram_filter &filter)
847{
848 col.erase(std::remove_if(col.begin(), col.end(),
849 [&filter](auto &&element) {
850 return !filter.should_include(element.get());
851 }),
852 col.end());
853}
854
855template <>
856bool diagram_filter::should_include<std::string>(const std::string &name) const;
857} // namespace clanguml::common::model