0.6.1
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
diagram_filter.cc
Go to the documentation of this file.
1/**
2 * @file src/common/model/diagram_filter.cc
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
19#include "diagram_filter.h"
20
21#include <utility>
22
25#include "glob/glob.hpp"
29
31
32namespace detail {
33
34template <>
37{
38 return d.classes();
39}
40
41template <>
44{
45 return d.enums();
46}
47
48template <>
51{
52 return d.packages();
53}
54
55template <>
58{
59 return d.files();
60}
61
62template <>
64 const class_diagram::model::diagram &d, const std::string &full_name)
65{
66 return d.find<class_diagram::model::class_>(full_name);
67}
68
69template <>
71 const package_diagram::model::diagram &d, const std::string &full_name)
72{
73 return d.find<package>(full_name);
74}
75
76template <>
78 const include_diagram::model::diagram &d, const std::string &full_name)
79{
80 return d.find<source_file>(full_name);
81}
82
83template <>
86{
87 return f.id();
88}
89} // namespace detail
90
92 : type_{type}
93{
94}
95
97 const diagram & /*d*/, const common::model::element & /*e*/) const
98{
99 return {};
100}
101
103 const diagram &d, const common::model::relationship &r) const
104{
105 return match(d, r.type());
106}
107
109 const diagram & /*d*/, const common::model::relationship_t & /*r*/) const
110{
111 return {};
112}
113
115 const diagram & /*d*/, const common::model::access_t & /*a*/) const
116{
117 return {};
118}
119
121 const diagram & /*d*/, const common::model::namespace_ & /*ns*/) const
122{
123 return {};
124}
125
127 const diagram & /*d*/, const common::model::source_file & /*f*/) const
128{
129 return {};
130}
131
133 const diagram & /*d*/, const common::model::source_location & /*f*/) const
134{
135 return {};
136}
137
139 const diagram &d, const class_diagram::model::class_method &m) const
140{
141 return match(d, m.access());
142}
143
145 const diagram &d, const class_diagram::model::class_member &m) const
146{
147 return match(d, m.access());
148}
149
151 const diagram &d, const class_diagram::model::objc_method &m) const
152{
153 return match(d, m.access());
154}
155
157 const diagram &d, const class_diagram::model::objc_member &m) const
158{
159 return match(d, m.access());
160}
161
163 const sequence_diagram::model::participant & /*p*/) const
164{
165 return {};
166}
167
169{
170 return type_ == filter_t::kInclusive;
171}
172
174{
175 return type_ == filter_t::kExclusive;
176}
177
179
180filter_mode_t filter_visitor::mode() const { return mode_; }
181
182void filter_visitor::set_mode(filter_mode_t mode) { mode_ = mode; }
183
185
187 filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters)
188 : filter_visitor{type}
189 , filters_{std::move(filters)}
190{
191}
192
194{
195 for (auto &f : filters_)
196 f->reset();
197}
198
200 const diagram &d, const common::model::element &e) const
201{
202 return match_anyof(d, e);
203}
204
206 const diagram &d, const common::model::relationship_t &r) const
207{
208 return match_anyof(d, r);
209}
210
212 const diagram &d, const common::model::access_t &a) const
213{
214 return match_anyof(d, a);
215}
216
218 const diagram &d, const common::model::namespace_ &ns) const
219{
220 return match_anyof(d, ns);
221}
222
224 const diagram &d, const common::model::source_file &f) const
225{
226 return match_anyof(d, f);
227}
228
230 const diagram &d, const common::model::source_location &f) const
231{
232 return match_anyof(d, f);
233}
234
236 const diagram &d, const class_diagram::model::class_method &m) const
237{
238 return match_anyof(d, m);
239}
240
242 const diagram &d, const class_diagram::model::class_member &m) const
243{
244 return match_anyof(d, m);
245}
246
248 const diagram &d, const sequence_diagram::model::participant &p) const
249{
250 return match_anyof(d, p);
251}
252
254 filter_t type, std::vector<std::unique_ptr<filter_visitor>> filters)
255 : filter_visitor{type}
256 , filters_{std::move(filters)}
257{
258}
260{
261 for (auto &f : filters_)
262 f->reset();
263}
264
266 const diagram &d, const common::model::element &e) const
267{
268 return match_allof(d, e);
269}
270
272 const diagram &d, const common::model::relationship_t &r) const
273{
274 return match_allof(d, r);
275}
276
278 const diagram &d, const common::model::access_t &a) const
279{
280 return match_allof(d, a);
281}
282
284 const diagram &d, const common::model::namespace_ &ns) const
285{
286 return match_allof(d, ns);
287}
288
290 const diagram &d, const common::model::source_file &f) const
291{
292 return match_allof(d, f);
293}
294
296 const diagram &d, const common::model::source_location &f) const
297{
298 return match_allof(d, f);
299}
300
302 const diagram &d, const class_diagram::model::class_method &m) const
303{
304 return match_allof(d, m);
305}
306
308 const diagram &d, const class_diagram::model::class_member &m) const
309{
310 return match_allof(d, m);
311}
312
314 const diagram &d, const sequence_diagram::model::participant &p) const
315{
316 return match_allof(d, p);
317}
318
320 filter_t type, std::vector<common::namespace_or_regex> namespaces)
321 : filter_visitor{type}
322 , namespaces_{std::move(namespaces)}
323{
324}
325
327 const diagram & /*d*/, const namespace_ &ns) const
328{
329 if (ns.is_empty())
330 return {};
331
332 auto res = tvl::any_of(namespaces_.begin(), namespaces_.end(),
333 [&ns, is_inclusive = is_inclusive()](const auto &nsit) {
334 if (std::holds_alternative<namespace_>(nsit.value())) {
335 const auto &ns_pattern = std::get<namespace_>(nsit.value());
336 if (is_inclusive)
337 return ns.starts_with(ns_pattern) ||
338 ns_pattern.starts_with(ns);
339
340 return ns.starts_with(ns_pattern);
341 }
342
343 const auto &regex = std::get<common::regex>(nsit.value());
344 return regex %= ns.to_string();
345 });
346
347 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
348 (type() == filter_t::kExclusive && tvl::is_true(res))) {
349 LOG_TRACE("Namespace {} rejected by namespace_filter", ns.to_string());
350 }
351
352 return res;
353}
354
356{
357 tvl::value_t res;
358 if (d.type() != diagram_t::kPackage &&
359 dynamic_cast<const package *>(&e) != nullptr) {
360 res = tvl::any_of(namespaces_.begin(), namespaces_.end(),
361 [&e, is_inclusive = is_inclusive()](const auto &nsit) {
362 if (std::holds_alternative<namespace_>(nsit.value())) {
363 const auto &ns_pattern = std::get<namespace_>(nsit.value());
364
365 auto element_full_name_starts_with_namespace =
366 namespace_{e.name_and_ns()}.starts_with(ns_pattern);
367
368 auto element_full_name_equals_pattern =
369 namespace_{e.name_and_ns()} == ns_pattern;
370
371 auto pattern_starts_with_element_full_name =
372 ns_pattern.starts_with(namespace_{e.name_and_ns()});
373
374 auto result = element_full_name_starts_with_namespace ||
375 element_full_name_equals_pattern;
376
377 if (is_inclusive)
378 result =
379 result || pattern_starts_with_element_full_name;
380
381 return result;
382 }
383
384 return std::get<common::regex>(nsit.value()) %=
385 e.full_name(false);
386 });
387
388 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
389 (type() == filter_t::kExclusive && tvl::is_true(res))) {
390 LOG_TRACE(
391 "Element {} rejected by namespace_filter", e.full_name(false));
392 }
393
394 return res;
395 }
396
397 if (d.type() == diagram_t::kPackage) {
398 res = tvl::any_of(namespaces_.begin(), namespaces_.end(),
399 [&e, is_inclusive = is_inclusive()](const auto &nsit) {
400 if (std::holds_alternative<namespace_>(nsit.value())) {
401 auto e_ns = namespace_{e.full_name(false)};
402 auto nsit_ns = std::get<namespace_>(nsit.value());
403
404 if (is_inclusive)
405 return e_ns.starts_with(nsit_ns) ||
406 nsit_ns.starts_with(e_ns) || e_ns == nsit_ns;
407
408 return e_ns.starts_with(nsit_ns) || e_ns == nsit_ns;
409 }
410
411 return std::get<common::regex>(nsit.value()) %=
412 e.full_name(false);
413 });
414
415 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
416 (type() == filter_t::kExclusive && tvl::is_true(res))) {
417 LOG_TRACE(
418 "Element {} rejected by namespace_filter", e.full_name(false));
419 }
420 return res;
421 }
422
423 res = tvl::any_of(
424 namespaces_.begin(), namespaces_.end(), [&e](const auto &nsit) {
425 if (std::holds_alternative<namespace_>(nsit.value())) {
426 return e.get_namespace().starts_with(
427 std::get<namespace_>(nsit.value()));
428 }
429
430 return std::get<common::regex>(nsit.value()) %= e.full_name(false);
431 });
432
433 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
434 (type() == filter_t::kExclusive && tvl::is_true(res))) {
435 LOG_TRACE(
436 "Element {} rejected by namespace_filter", e.full_name(false));
437 }
438 return res;
439}
440
441tvl::value_t namespace_filter::match(
442 const diagram &d, const sequence_diagram::model::participant &p) const
443{
444 return match(d, dynamic_cast<const element &>(p));
445}
446
447modules_filter::modules_filter(
448 filter_t type, std::vector<common::string_or_regex> modules)
449 : filter_visitor{type}
450 , modules_{std::move(modules)}
451{
452}
453
455 const diagram & /*d*/, const element &e) const
456{
457 if (modules_.empty())
458 return {};
459
460 if (!e.module().has_value())
461 return {false};
462
463 auto module_toks =
464 path::split(e.module().value(), path_type::kModule); // NOLINT
465
466 if (dynamic_cast<const package *>(&e) != nullptr &&
468 module_toks.push_back(e.name());
469 }
470
471 auto result = tvl::any_of(modules_.begin(), modules_.end(),
472 [&e, &module_toks](const auto &modit) {
473 if (std::holds_alternative<std::string>(modit.value())) {
474 const auto &modit_str = std::get<std::string>(modit.value());
475 const auto modit_toks =
476 path::split(modit_str, path_type::kModule);
477
478 return e.module() == modit_str ||
479 util::starts_with(module_toks, modit_toks);
480 }
481
482 return std::get<common::regex>(modit.value()) %= e.module().value();
483 });
484
485 if ((type() == filter_t::kInclusive && tvl::is_false(result)) ||
486 (type() == filter_t::kExclusive && tvl::is_true(result))) {
487 LOG_TRACE("Element {} rejected by modules_filter", e.full_name(false));
488 }
489
490 return result;
491}
492
494 filter_t type, std::vector<config::element_filter_t> elements)
495 : filter_visitor{type}
496 , elements_{std::move(elements)}
497{
498}
499
501{
502 // Do not apply element filter to packages in class diagrams
503 if (d.type() == diagram_t::kClass && e.type_name() == "package")
504 return std::nullopt;
505
506 auto res =
507 tvl::any_of(elements_.begin(), elements_.end(), [&e](const auto &el) {
508 // First check if elements type matches the filter
509 if ((el.type != config::element_filter_t::filtered_type::any) &&
510 (config::to_string(el.type) != e.type_name())) {
511 return false;
512 }
513
514 return ((el.name == e.full_name(false)) ||
515 (el.name == fmt::format("::{}", e.full_name(false))));
516 });
517
518 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
519 (type() == filter_t::kExclusive && tvl::is_true(res))) {
520 LOG_TRACE("Element {} rejected by element_filter", e.full_name(false));
521 }
522
523 return res;
524}
525
527 const diagram & /*d*/, const class_diagram::model::class_method &m) const
528{
529 auto res = tvl::any_of(elements_.begin(), elements_.end(),
530 [&m](const auto &ef) -> tvl::value_t {
531 // Apply this filter only if it had `method` type, do not apply
532 // `any` filters to methods for backward compatibility
533 if (ef.type != config::element_filter_t::filtered_type::method)
534 return {};
535
536 return ef.name == m.qualified_name();
537 });
538
539 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
540 (type() == filter_t::kExclusive && tvl::is_true(res))) {
541 LOG_TRACE(
542 "Class method {} rejected by element_filter", m.display_name());
543 }
544
545 return res;
546}
547
549 const diagram & /*d*/, const class_diagram::model::class_member &m) const
550{
551 auto res = tvl::any_of(elements_.begin(), elements_.end(),
552 [&m](const auto &ef) -> tvl::value_t {
553 // Apply this filter only if it had `member` type, do not apply
554 // `any` filters to methods for backward compatibility
555 if (ef.type != config::element_filter_t::filtered_type::member)
556 return {};
557
558 return ef.name == m.qualified_name();
559 });
560
561 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
562 (type() == filter_t::kExclusive && tvl::is_true(res))) {
563 LOG_TRACE(
564 "Class member {} rejected by element_filter", m.qualified_name());
565 }
566
567 return res;
568}
569
571 const diagram & /*d*/, const class_diagram::model::objc_method &m) const
572{
573 auto res = tvl::any_of(elements_.begin(), elements_.end(),
574 [&m](const auto &ef) -> tvl::value_t {
575 // Apply this filter only if it had `objc_method` type, do not apply
576 // `any` filters to methods for backward compatibility
577 if (ef.type != config::element_filter_t::filtered_type::objc_method)
578 return {};
579
580 return ef.name == m.qualified_name();
581 });
582
583 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
584 (type() == filter_t::kExclusive && tvl::is_true(res))) {
585 LOG_TRACE(
586 "ObjC method {} rejected by element_filter", m.qualified_name());
587 }
588
589 return res;
590}
591
593 const diagram & /*d*/, const class_diagram::model::objc_member &m) const
594{
595 auto res = tvl::any_of(elements_.begin(), elements_.end(),
596 [&m](const auto &ef) -> tvl::value_t {
597 // Apply this filter only if it had `method` type, do not apply
598 // `any` filters to methods for backward compatibility
599 if (ef.type != config::element_filter_t::filtered_type::objc_member)
600 return {};
601
602 return ef.name == m.qualified_name();
603 });
604
605 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
606 (type() == filter_t::kExclusive && tvl::is_true(res))) {
607 LOG_TRACE(
608 "ObjC member {} rejected by element_filter", m.qualified_name());
609 }
610
611 return res;
612}
613
615 const diagram &d, const sequence_diagram::model::participant &p) const
616{
619
620 if (d.type() != diagram_t::kSequence)
621 return {};
622
623 const auto &sequence_model =
624 dynamic_cast<const sequence_diagram::model::diagram &>(d);
625 auto res = tvl::any_of(elements_.begin(), elements_.end(),
626 [&sequence_model, &p](const auto &el) {
627 // First check if elements type matches the filter
628 if (el.type != config::element_filter_t::filtered_type::any &&
629 config::to_string(el.type) != p.type_name()) {
630 return false;
631 }
632
633 if (p.type_name() == "method") {
634 const auto &m = dynamic_cast<const method &>(p);
635 const auto class_id = m.class_id();
636 const auto &class_participant =
637 sequence_model.get_participant<participant>(class_id)
638 .value();
639
640 return (el.name == p.name_and_ns()) ||
641 (el.name == p.full_name(false)) ||
642 (el.name == class_participant.full_name(false));
643 }
644
645 if (p.type_name() == "objc_method") {
646 const auto &m =
647 dynamic_cast<const sequence_diagram::model::objc_method &>(
648 p);
649 const auto class_id = m.class_id();
650 const auto &class_participant =
651 sequence_model.get_participant<participant>(class_id)
652 .value();
653
654 return (el.name == p.name_and_ns()) ||
655 (el.name == p.full_name(false)) ||
656 (el.name == class_participant.full_name(false));
657 }
658
659 return el.name == p.full_name(false);
660 });
661
662 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
663 (type() == filter_t::kExclusive && tvl::is_true(res))) {
664 LOG_TRACE(
665 "Participant {} rejected by element_filter", p.full_name(false));
666 }
667
668 return res;
669}
670
672 filter_t type, std::vector<std::string> element_types)
673 : filter_visitor{type}
674 , element_types_{std::move(element_types)}
675{
676}
677
679 ,
680 const element &e) const
681{
682 auto res = tvl::any_of(element_types_.begin(), element_types_.end(),
683 [&e](const auto &element_type) {
684 return e.type_name() == element_type;
685 });
686
687 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
688 (type() == filter_t::kExclusive && tvl::is_true(res))) {
689 LOG_TRACE(
690 "Element {} rejected by element_type_filter", e.full_name(false));
691 }
692
693 return res;
694}
695
697 filter_t type, std::vector<config::method_type> method_types)
698 : filter_visitor{type}
699 , method_types_{std::move(method_types)}
700{
701}
702
704 const diagram & /*d*/, const class_diagram::model::class_method &m) const
705{
706 auto res =
707 tvl::any_of(method_types_.begin(), method_types_.end(), [&m](auto mt) {
708 switch (mt) {
709 case config::method_type::constructor:
710 return m.is_constructor();
711 case config::method_type::destructor:
712 return m.is_destructor();
713 case config::method_type::assignment:
714 return m.is_copy_assignment() || m.is_move_assignment();
715 case config::method_type::operator_:
716 return m.is_operator();
717 case config::method_type::defaulted:
718 return m.is_defaulted();
719 case config::method_type::deleted:
720 return m.is_deleted();
721 case config::method_type::static_:
722 return m.is_static();
723 }
724
725 return false;
726 });
727
728 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
729 (type() == filter_t::kExclusive && tvl::is_true(res))) {
730 LOG_TRACE("Class method {} rejected by method_type_filter",
731 m.qualified_name());
732 }
733
734 return res;
735}
736
738 filter_t type, std::vector<config::callee_type> callee_types)
739 : filter_visitor{type}
740 , callee_types_{std::move(callee_types)}
741{
742}
743
745 const diagram &d, const sequence_diagram::model::participant &p) const
746{
751
752 auto is_lambda = [&d](const method &m) {
753 auto class_participant =
754 dynamic_cast<const sequence_diagram::model::diagram &>(d)
755 .get_participant<class_>(m.class_id());
756 if (!class_participant)
757 return false;
758
759 return class_participant.value().is_lambda();
760 };
761
763 callee_types_.begin(), callee_types_.end(), [&p, is_lambda](auto ct) {
764 auto is_function = [](const participant *p) {
765 return dynamic_cast<const function *>(p) != nullptr;
766 };
767
768 auto is_cuda_kernel = [](const participant *p) {
769 const auto *f = dynamic_cast<const function *>(p);
770 return (f != nullptr) && (f->is_cuda_kernel());
771 };
772
773 auto is_cuda_device = [](const participant *p) {
774 const auto *f = dynamic_cast<const function *>(p);
775 return (f != nullptr) && (f->is_cuda_device());
776 };
777
778 switch (ct) {
780 return p.type_name() == "method";
782 return p.type_name() == "method" &&
783 ((method &)p).is_constructor();
785 return p.type_name() == "method" &&
786 ((method &)p).is_assignment();
788 return is_function(&p) && ((function &)p).is_operator();
790 return p.type_name() == "method" &&
791 ((method &)p).is_defaulted();
793 return is_function(&p) && ((function &)p).is_static();
795 return p.type_name() == "function";
797 return p.type_name() == "function_template";
799 return p.type_name() == "method" && is_lambda((method &)p);
801 return is_cuda_kernel(&p);
803 return is_cuda_device(&p);
804 }
805
806 return false;
807 });
808
809 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
810 (type() == filter_t::kExclusive && tvl::is_true(res))) {
811 LOG_TRACE(
812 "Participant {} rejected by callee_filter", p.full_name(false));
813 }
814
815 return res;
816}
817
819 filter_t type, std::vector<common::string_or_regex> roots)
820 : filter_visitor{type}
821 , roots_{std::move(roots)}
822{
823}
824
826{
827 if (d.type() != diagram_t::kClass)
828 return {};
829
830 if (roots_.empty())
831 return {};
832
833 if (!d.complete())
834 return {};
835
836 const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(d);
837
838 // First get all parents of element e
840
841 const auto &fn = e.full_name(false);
842 auto class_ref = cd.find<class_diagram::model::class_>(fn);
843
844 if (!class_ref.has_value())
845 return false;
846
847 parents.emplace(class_ref.value());
848
849 cd.get_parents(parents);
850
851 std::vector<std::string> parents_names;
852 for (const auto p : parents)
853 parents_names.push_back(p.get().full_name(false));
854
855 // Now check if any of the parents matches the roots specified in the
856 // filter config
857 for (const auto &root : roots_) {
858 for (const auto &parent : parents) {
859 auto full_name = parent.get().full_name(false);
860 if (root == full_name) {
861 if (type() == filter_t::kExclusive)
862 LOG_TRACE("Element {} rejected by subclass_filter",
863 e.full_name(false));
864
865 return true;
866 }
867 }
868 }
869
870 if (type() == filter_t::kInclusive)
871 LOG_TRACE("Element {} rejected by subclass_filter", e.full_name(false));
872
873 return false;
874}
875
877 filter_t type, std::vector<common::string_or_regex> children)
878 : filter_visitor{type}
879 , children_{std::move(children)}
880{
881}
882
884{
885 if (d.type() != diagram_t::kClass)
886 return {};
887
888 if (children_.empty())
889 return {};
890
891 if (!d.complete())
892 return {};
893
894 const auto &cd = dynamic_cast<const class_diagram::model::diagram &>(d);
895
896 // First get all parents of element e
898
899 for (const auto &child_pattern : children_) {
900 auto child_refs = cd.find<class_diagram::model::class_>(child_pattern);
901
902 for (auto &child : child_refs) {
903 if (child.has_value())
904 parents.emplace(child.value());
905 }
906 }
907
908 cd.get_parents(parents);
909
910 for (const auto &parent : parents) {
911 if (e == parent.get())
912 return true;
913 }
914
915 LOG_TRACE("Element {} rejected by parents_filter", e.full_name(false));
916
917 return false;
918}
919
921 filter_t type, std::vector<relationship_t> relationships)
922 : filter_visitor{type}
923 , relationships_{std::move(relationships)}
924{
925}
926
928 const diagram & /*d*/, const relationship_t &r) const
929{
930 return tvl::any_of(relationships_.begin(), relationships_.end(),
931 [&r](const auto &rel) { return r == rel; });
932}
933
934access_filter::access_filter(filter_t type, std::vector<access_t> access)
935 : filter_visitor{type}
936 , access_{std::move(access)}
937{
938}
939
941 const diagram & /*d*/, const access_t &a) const
942{
943 return tvl::any_of(access_.begin(), access_.end(),
944 [&a](const auto &access) { return a == access; });
945}
946
948 filter_t type, std::vector<module_access_t> access)
949 : filter_visitor{type}
950 , access_{std::move(access)}
951{
952}
953
955 const diagram & /*d*/, const element &e) const
956{
957 if (!e.module().has_value())
958 return {};
959
960 if (access_.empty())
961 return {};
962
963 return tvl::any_of(
964 access_.begin(), access_.end(), [&e](const auto &access) {
965 if (access == module_access_t::kPublic)
966 return !e.module_private();
967
968 return e.module_private();
969 });
970}
971
973 filter_t type, std::vector<config::context_config> context)
974 : filter_visitor{type}
975 , context_{std::move(context)}
976{
977}
978
980 const diagram &d, unsigned idx) const
981{
982 bool effective_context_extended{true};
983
984 auto &effective_context = effective_contexts_[idx];
985
986 // First add to effective context all elements matching context_
987 // patterns
988 const auto &context_cfg = context_.at(idx);
989 const auto &context_matches =
990 dynamic_cast<const class_diagram::model::diagram &>(d)
991 .find<class_diagram::model::class_>(context_cfg.pattern);
992
993 for (const auto &maybe_match : context_matches) {
994 if (maybe_match) {
995 effective_context.emplace(maybe_match.value().id());
996 }
997 }
998
999 const auto &context_enum_matches =
1000 dynamic_cast<const class_diagram::model::diagram &>(d)
1001 .find<class_diagram::model::enum_>(context_cfg.pattern);
1002
1003 for (const auto &maybe_match : context_enum_matches) {
1004 if (maybe_match)
1005 effective_context.emplace(maybe_match.value().id());
1006 }
1007
1008 const auto &context_concept_matches =
1009 dynamic_cast<const class_diagram::model::diagram &>(d)
1010 .find<class_diagram::model::concept_>(context_cfg.pattern);
1011
1012 for (const auto &maybe_match : context_concept_matches) {
1013 if (maybe_match)
1014 effective_context.emplace(maybe_match.value().id());
1015 }
1016
1017 // Now repeat radius times - extend the effective context with elements
1018 // matching in direct relationship to what is in context
1019 auto radius_counter = context_cfg.radius;
1020 std::set<eid_t> current_iteration_context;
1021
1022 while (radius_counter > 0 && effective_context_extended) {
1023 // If at any iteration the effective context was not extended - we
1024 // don't to need to continue
1025 radius_counter--;
1026 effective_context_extended = false;
1027 current_iteration_context.clear();
1028
1029 // For each class in the model
1030 find_elements_in_direct_relationship<class_diagram::model::diagram>(
1031 d, context_cfg, effective_context, current_iteration_context);
1032
1033 for (auto id : current_iteration_context) {
1034 if (effective_context.count(id) == 0) {
1035 // Found new element to add to context
1036 effective_context.emplace(id);
1037 effective_context_extended = true;
1038 }
1039 }
1040 }
1041}
1042
1044 const diagram &d, unsigned idx) const
1045{
1046 assert(d.type() == diagram_t::kPackage);
1047
1048 bool effective_context_extended{true};
1049
1050 auto &effective_context = effective_contexts_[idx];
1051
1052 // First add to effective context all elements matching context_
1053 // patterns
1054 const auto &context_cfg = context_.at(idx);
1055 const auto &context_matches =
1056 dynamic_cast<const package_diagram::model::diagram &>(d)
1057 .find<package_diagram::model::package>(context_cfg.pattern);
1058
1059 for (const auto &maybe_match : context_matches) {
1060 if (maybe_match)
1061 effective_context.emplace(maybe_match.value().id());
1062 }
1063
1064 // Now repeat radius times - extend the effective context with elements
1065 // matching in direct relationship to what is in context
1066 auto radius_counter = context_cfg.radius;
1067 std::set<eid_t> current_iteration_context;
1068
1069 while (radius_counter > 0 && effective_context_extended) {
1070 // If at any iteration the effective context was not extended - we
1071 // don't to need to continue
1072 radius_counter--;
1073 effective_context_extended = false;
1074 current_iteration_context.clear();
1075
1076 // For each class in the model
1077 find_elements_in_direct_relationship<package_diagram::model::diagram>(
1078 d, context_cfg, effective_context, current_iteration_context);
1079
1080 for (auto id : current_iteration_context) {
1081 if (effective_context.count(id) == 0) {
1082 // Found new element to add to context
1083 effective_context.emplace(id);
1084 effective_context_extended = true;
1085 }
1086 }
1087 }
1088}
1089
1091 const diagram &d, unsigned idx) const
1092{
1093 if (d.type() == diagram_t::kClass)
1095 else if (d.type() == diagram_t::kPackage) {
1097 }
1098}
1099
1101 const config::context_config &context_cfg, relationship_t r) const
1102{
1103 return context_cfg.relationships.empty() ||
1104 util::contains(context_cfg.relationships, r);
1105}
1106
1108{
1109 if (initialized_)
1110 return;
1111
1112 initialized_ = true;
1113
1114 // Prepare effective_contexts_
1115 for (auto i = 0U; i < context_.size(); i++) {
1116 effective_contexts_.push_back({}); // NOLINT
1118 }
1119}
1120
1122{
1123 if (d.type() != diagram_t::kClass && d.type() != diagram_t::kPackage)
1124 return {};
1125
1126 // Running this filter makes sense only after the entire diagram is
1127 // generated - i.e. during diagram generation
1128 if (!d.complete())
1129 return {};
1130
1131 // Context filter only makes sense for packages which are empty, i.e. are
1132 // leafs in the package diagram tree
1133 if (d.type() == diagram_t::kPackage) {
1134 if (const auto *package_ptr =
1135 dynamic_cast<const common::model::package *>(&e);
1136 package_ptr != nullptr) {
1137 if (!package_ptr->is_empty(true)) {
1138 return {};
1139 }
1140 }
1141 }
1142
1143 initialize(d);
1144
1145 if (std::all_of(effective_contexts_.begin(), effective_contexts_.end(),
1146 [](const auto &ec) { return ec.empty(); }))
1147 return {};
1148
1149 for (const auto &ec : effective_contexts_) {
1150 if (ec.count(e.id()) > 0) {
1151 if (type() == filter_t::kExclusive)
1152 LOG_TRACE("Element {} rejected by context_filter",
1153 e.full_name(false));
1154 return true;
1155 }
1156 }
1157 if (type() == filter_t::kInclusive)
1158 LOG_TRACE("Element {} rejected by context_filter", e.full_name(false));
1159
1160 return false;
1161}
1162
1164{
1165 return r == relationship_t::kAssociation;
1166}
1167
1169{
1170 return r != relationship_t::kAssociation;
1171}
1172
1173paths_filter::paths_filter(filter_t type, const std::vector<std::string> &p,
1174 const std::filesystem::path &root)
1175 : filter_visitor{type}
1176 , root_{root}
1177{
1178 for (const auto &path : p) {
1179 std::filesystem::path absolute_path;
1180
1181 if (path.empty() || path == ".")
1182 absolute_path = root;
1183 else if (std::filesystem::path{path}.is_relative())
1184 absolute_path = root / path;
1185 else
1186 absolute_path = path;
1187
1188 bool match_successful{false};
1189 for (auto &resolved_glob_path :
1190 glob::glob(absolute_path.string(), true)) {
1191 try {
1192 auto resolved_absolute_path = absolute(resolved_glob_path);
1193 resolved_absolute_path =
1194 canonical(resolved_absolute_path.lexically_normal());
1195
1196 resolved_absolute_path.make_preferred();
1197
1198 LOG_DBG("Added path {} to paths_filter",
1199 resolved_absolute_path.string());
1200
1201 paths_.emplace_back(std::move(resolved_absolute_path));
1202
1203 match_successful = true;
1204 }
1205 catch (std::filesystem::filesystem_error &e) {
1206 LOG_WARN("Cannot add non-existent path {} to "
1207 "paths filter",
1208 absolute_path.string());
1209 continue;
1210 }
1211 }
1212
1213 if (!match_successful)
1214 LOG_WARN("Paths filter pattern '{}' did not match "
1215 "any files relative to '{}'",
1216 path, root_.string());
1217 }
1218}
1219
1221 const diagram & /*d*/, const common::model::source_file &p) const
1222{
1223 if (paths_.empty()) {
1224 return {};
1225 }
1226
1227 // Matching source paths doesn't make sense if they are not absolute
1228 if (!p.is_absolute()) {
1229 return {};
1230 }
1231
1232 const auto source_file_path = p.fs_path(root_);
1233
1234 auto res = memoize(
1235 true,
1236 [this](const std::filesystem::path &sfp) {
1237 return std::any_of(
1238 paths_.begin(), paths_.end(), [&](const auto &path) {
1239 return sfp.root_name().string() ==
1240 path.root_name().string() &&
1241 util::is_relative_to(
1242 sfp.relative_path(), path.relative_path());
1243 });
1244 },
1245 source_file_path);
1246
1247 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
1248 (type() == filter_t::kExclusive && tvl::is_true(res))) {
1249 LOG_TRACE("Source file {} [{}] rejected by paths_filter",
1250 p.full_name(false), source_file_path.string());
1251 }
1252
1253 if ((type() == filter_t::kInclusive && tvl::is_true(res)) ||
1254 (type() == filter_t::kExclusive && tvl::is_false(res))) {
1255 LOG_TRACE("Source file {} [{}] accepted by paths_filter",
1256 p.full_name(false), source_file_path.string());
1257 }
1258
1259 return res;
1260}
1261
1263 const diagram & /*d*/, const common::model::source_location &p) const
1264{
1265 if (paths_.empty()) {
1266 return {};
1267 }
1268
1269 const auto source_location_path = std::filesystem::path{p.file()};
1270
1271 // Matching source paths doesn't make sense if they are not absolute or
1272 // empty
1273 if (p.file().empty() || source_location_path.is_relative()) {
1274 return {};
1275 }
1276
1277 auto res = memoize(
1278 true,
1279 [this](const std::filesystem::path &p) {
1280 return std::any_of(
1281 paths_.begin(), paths_.end(), [&](const auto &path) {
1282 return p.root_name().string() ==
1283 path.root_name().string() &&
1284 util::is_relative_to(
1285 p.relative_path(), path.relative_path());
1286 });
1287 },
1288 source_location_path);
1289
1290 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
1291 (type() == filter_t::kExclusive && tvl::is_true(res))) {
1292 LOG_TRACE("Source location {} rejected by paths_filter", p.file());
1293 }
1294
1295 return res;
1296}
1297
1299 const diagram &d, const common::model::element &e) const
1300{
1301 return match(d, dynamic_cast<const common::model::source_location &>(e));
1302}
1303
1305 std::unique_ptr<access_filter> af, std::unique_ptr<method_type_filter> mtf)
1306 : filter_visitor{type}
1307 , access_filter_{std::move(af)}
1308 , method_type_filter_{std::move(mtf)}
1309{
1310}
1311
1313 const diagram &d, const class_diagram::model::class_method &m) const
1314{
1315 tvl::value_t res = tvl::or_(
1316 access_filter_->match(d, m.access()), method_type_filter_->match(d, m));
1317
1318 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
1319 (type() == filter_t::kExclusive && tvl::is_true(res))) {
1320 LOG_TRACE("Class method {} rejected by class_method_filter",
1321 m.qualified_name());
1322 }
1323
1324 return res;
1325}
1326
1328 filter_t type, std::unique_ptr<access_filter> af)
1329 : filter_visitor{type}
1330 , access_filter_{std::move(af)}
1331{
1332}
1333
1335 const diagram &d, const class_diagram::model::class_member &m) const
1336{
1337 auto res = access_filter_->match(d, m.access());
1338
1339 if ((type() == filter_t::kInclusive && tvl::is_false(res)) ||
1340 (type() == filter_t::kExclusive && tvl::is_true(res))) {
1341 LOG_TRACE("Class member {} rejected by class_member_filter",
1342 m.qualified_name());
1343 }
1344
1345 return res;
1346}
1347
1349 const config::diagram & /*c*/, private_constructor_tag_t /*unused*/)
1350 : diagram_{d}
1351{
1352}
1353
1355 filter_t filter_type, std::unique_ptr<filter_visitor> fv)
1356{
1357 if (filter_type == filter_t::kInclusive)
1358 add_inclusive_filter(std::move(fv));
1359 else
1360 add_exclusive_filter(std::move(fv));
1361}
1362
1363void diagram_filter::add_inclusive_filter(std::unique_ptr<filter_visitor> fv)
1364{
1365 inclusive_.emplace_back(std::move(fv));
1366}
1367
1368void diagram_filter::add_exclusive_filter(std::unique_ptr<filter_visitor> fv)
1369{
1370 exclusive_.emplace_back(std::move(fv));
1371}
1372
1374 const namespace_ &ns, const std::string &name) const
1375{
1376 if (should_include(ns)) {
1377 element e{namespace_{}};
1378 e.set_name(name);
1379 e.set_namespace(ns);
1380
1381 return should_include(e);
1382 }
1383
1384 return false;
1385}
1386
1387filter_mode_t diagram_filter::mode() const { return mode_; }
1388
1389void diagram_filter::set_mode(filter_mode_t mode) { mode_ = mode; }
1390
1392{
1393 for (auto &f : inclusive_)
1394 f->reset();
1395
1396 for (auto &f : exclusive_)
1397 f->reset();
1398}
1399
1400template <>
1401bool diagram_filter::should_include<std::string>(const std::string &name) const
1402{
1403 if (name.empty())
1404 return false;
1405
1406 auto [ns, n] = common::split_ns(name);
1407
1408 return should_include(ns, n);
1409}
1410} // namespace clanguml::common::model