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