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